SYSTEM Cited by 1 source
Go assembler¶
The Go assembler (cmd/internal/obj/<arch>) is the final
stage of the Go toolchain: it consumes the obj.Prog
intermediate representation emitted by the compiler and
produces machine code for a specific architecture.
Immediate classification (arm64)¶
The arm64 backend lives in
cmd/internal/obj/arm64/asm7.go.
The obj.Prog IR "is not aware of immediate length
limitations" — the compiler emits a single logical ADD $n,
RSP, RSP regardless of how wide n is. It's the assembler's
job to classify the immediate in
conclass
and expand the obj.Prog into one or more machine opcodes.
Typical patterns on arm64:
- Immediate fits in 12 bits → single
ADD $n, RSP, RSP. - Immediate fits in 16 bits →
(MOV, ADD)pair. - Immediate fits in 24 bits →
(ADD, ADD + LSL 12)pair. - Wider → load via
(MOV + MOVK)into scratch, then register-formADD.
See systems/arm64-isa for why this decomposition is forced by ARM64's fixed-length 32-bit instruction encoding.
Why the decomposition mattered¶
The Go compiler's function epilogue on arm64 adjusts the stack
pointer by the frame size. For frames > 1<<12 bytes, the
assembler (pre-fix) produced the 24-bit-pair variant:
ADD $8, RSP, R29
ADD $(16<<12), R29, R29
ADD $16, RSP, RSP
ADD $(16<<12), RSP, RSP ; ← race window begins after previous ADD
RET
Async preemption landing between the two ADD-to-RSP
opcodes produced a goroutine whose stack pointer was
partially adjusted — invalid for
stack unwinding and reachable by Go's garbage collector. See
sources/2025-10-08-cloudflare-we-found-a-bug-in-gos-arm64-compiler
for the full crash shape.
The fix: push the split up to the compiler, not the¶
assembler¶
The pre-fix architecture relied on the assembler to split
immediates silently. The fix in go1.23.12 / go1.24.6 / go1.25.0
changes the compiler (cmd/internal/obj/arm64/obj7.go)
to emit a scratch-register + indivisible register-form
ADD when the immediate exceeds the 12-bit inline form. The
assembler no longer needs to split the SP adjustment; the
single register-form ADD R27, RSP, RSP is emitted directly
and is preemption-safe.
Seen in¶
- sources/2025-10-08-cloudflare-we-found-a-bug-in-gos-arm64-compiler
— canonical wiki instance. The
asm7.goconclass+ split-ADDemission path was the machine-code-level expression of the bug.
Related¶
- systems/go-compiler — the stage upstream of the assembler; the fix moved immediate-awareness up to here.
- systems/arm64-isa — the architectural constraint the assembler navigates.
- concepts/immediate-encoding-limit — the general problem shape.
- concepts/split-instruction-race-window — the failure class that the split decomposition opens up when the target is runtime-observable state.