Skip to content

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-form ADD.

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

Last updated · 200 distilled / 1,178 read