Repository navigation
cmd/compile: break false dependencies for x86 BSF/BSR instructions #82133
Description
Activity
- addedcompiler/runtimeIssues related to the Go compiler and/or runtime.Issues related to the Go compiler and/or runtime.
on Oct 10, 2026 - changed the title
[-]cmd/compile: avoid false destination dependencies for x86 BSF/BSR[/-][+]cmd/compile: break false dependencies for x86 BSF/BSR instructions[/+]on Oct 10, 2026 Related Issues
- cmd/compile: In bits.Len(x|1), compiler doesn't realize argument is never zero #51964 (closed)
- cmd/compile: on `AMD64` slow instruction sequence to Zext a < register size instruction output #76066
- cmd/compile: break false dependency on Zen 4 and 5 (compress/expand) #81609
- cmd/compile: optimize bitset tests #31904 (closed)
- cmd/compile: fix long RMW bit operations on AMD64 #45242 (closed)
Related Code Changes
- cmd/compile: remove a false dependency for TZCNT
- cmd/compile: add patterns for bit set/clear/complement on amd64
- cmd/compile: optimize TrailingZeros(8|16) on amd64
- runtime: use Haswell instructions in scanobject
- cmd/compile: break the false dependency of scalar SSE merges on amd64
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Change https://go.dev/cl/848189 mentions this issue:
cmd/compile: break false destination dependencies for BSF and BSR@zolstein Is there more information which Intel processors/generations are affected?
That information would be helpful for long-term maintenance when that is fixed in later generations or can be assumed fixed on well maintained machines because a microcode update did fix it.
Hmm. I was under the impression that this was exclusive to Intel chips, but this actually seems to be universal among x86_64 chips.
uops.infoshows a non-zero operand 1 → 1 latency on all processors tested. This is distinct from e.g.LZCNTwhere the latency is non-zero only on pre-Skylake Intel chips. Also, it seems like AMD and Intel both now document the behavior that the destination register is unchanged on a zero source, so the dependency is only false when the source is non-zero.Intel documentation: https://cdrdv2.intel.com/v1/dl/getContent/671199 (Page 3-107)
AMD documentaiton: https://kib.kiev.ua/x86docs/AMD/AMD64/24594_APM_v3-r3.37.pdf (Page 123)https://uops.info/html-instr/BSR_R64_R64.html
https://uops.info/html-instr/BSF_R64_R64.html
https://uops.info/html-instr/BSR_R32_R32.html
https://uops.info/html-instr/BSF_R32_R32.html
https://uops.info/html-instr/BSR_R16_R16.html
https://uops.info/html-instr/BSF_R16_R16.html
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status

Go version
go1.27.1 amd64/linux
Output of
go envin your module/workspace:GOVERSION='go1.27.1' GOOS=linux GOARCH=amd64 GOAMD64=v1What did you do?
Ran a benchmark of
bits.TrailingZeros64, inspected the generated assembly, and recorded performance counters on an Intel Core Ultra 7 258V processor.Benchmark program
Build with:
Inspect the generated scans with:
scanHighexercises BSRQ on amd64, butbits.Len64does not generate BSRL on 386.Measure the performance:
What did you see happen?
BSF and BSR have a dependency on the previous value of the destination register. The amd64 compiler emits
BSFQ DX, SIwithout first breaking this dependency.When register allocation reuses the destination register across loop iterations, otherwise independent bit scans become serialized, significantly reducing throughput.
What did you expect to see?
When the source and destination registers differ, the compiler should emit a zeroing idiom before BSF/BSR to break the false destination dependency:
The amd64 backend already uses this technique for POPCNT, TZCNT, and LZCNT. The same should be done for BSFQ, BSFL, BSRQ, and BSRL, with corresponding changes in the 386 backend.
I tested with a local compiler patch to emit the XORL instructions. One before/after measurement per target produced:
That is a 2.74× speedup on amd64 and 2.25× on 386, despite executing more instructions.
Real-world impact
I encountered this while optimizing the GC. Scanning performance was highly sensitive to whether register allocation produced dependencies between loop iterations. In one comparison, a build with the dependency showed the following regression relative to a baseline without it:
Tweaking the source code could make the compiler produce
BSFQ src, src, avoiding the dependency. However, small source changes can cause the compiler to choose different registers and reintroduce the dependency. This makes performance improvements fragile; changes that reduce instructions or register pressure can still introduce a costly dependency between loop iterations and cause regressions.