CONCEPT Cited by 1 source
Immediate encoding limit¶
Definition¶
Immediate encoding limit is the architectural constraint on how wide a value can be encoded directly inside a single instruction. On fixed-length ISAs (ARM64, RISC-V, etc.) the limit is strict — every instruction is exactly 4 bytes, and the opcode must share those 32 bits with the target register, source register, and any flags, leaving only a handful of bits for immediate operands.
Representative examples on ARM64:
| Instruction | Immediate bits | Range |
|---|---|---|
ADD (imm) |
12 | 0..4095 (with optional LSL #12 → 24 bits effective) |
MOV (wide imm) |
16 | 0..65535 (with MOVK to combine 16-bit chunks) |
LDR (imm offset) |
12 | typically 0..4095 × access size |
Wider values must be built up across multiple instructions.
Consequences for compiler backends¶
A compiler targeting a fixed-length ISA must decide how to emit operations whose immediate exceeds the inline limit:
- Split into multiple opcodes. For
ADDspecifically, ARM64 reserves an LSL-#12 bit so any 24-bit addition can be expressed as two opcodes:ADD $low, dst, dstthenADD $(high << 12), dst, dst. - Build in a scratch register, then apply. Load the
immediate into a scratch register via
MOV+MOVKpairs, then use the register form of the target operation. This is one logical operation expressed as multiple preparatory opcodes followed by one indivisible application opcode.
Option (1) is cheaper in instruction count when the register pressure is high; option (2) is necessary when the intermediate state after the first opcode must not be observable — i.e. when the target of the operation is runtime-observable state.
The stack pointer is a special case¶
When the target of a split-immediate operation is the stack pointer — or any other register the runtime watches during stack unwinding — option (1) is unsafe under async preemption because the intermediate state is a valid-looking but wrong stack pointer. The split-instruction race window becomes a reachable bug.
The Go toolchain pre-go1.23.12 used option (1) for epilogue
SP adjustments on arm64. The fix (go1.23.12 / go1.24.6 /
go1.25.0) switches to option (2): SP is now adjusted via a
scratch-register + indivisible register-form ADD.
On variable-length ISAs (x86-64)¶
amd64 has variable-length instructions. ADD imm32, reg
encodes a full 32-bit immediate directly in a single
variable-length opcode. The immediate-encoding limit that
forces split operations on arm64 does not arise — amd64 is not
exposed to the split-instruction race window (for this
specific class of operation). The Cloudflare 2025-10 bug was
arm64-only for this reason.
Seen in¶
- sources/2025-10-08-cloudflare-we-found-a-bug-in-gos-arm64-compiler
— canonical wiki instance; 12-bit
ADDimmediate forced the split-opcode decomposition that created the race window on SP adjustment.
Related¶
- systems/arm64-isa — the architecture.
- systems/go-assembler — the Go toolchain component
that classifies immediates via
conclass. - concepts/split-instruction-race-window — the general failure class this limit enables.