Files
2026-09-21 19:49:04 +02:00

5.0 KiB
Raw Permalink Blame History

Operands: grammar, pseudo-registers, addressing and symbols

Layer 1, the common language. Verified against go tool asm of Go 1.27.1 and against gasm's parser. The operand grammar is the part of the language that varies most between targets, so this page fixes the common grammar and the pseudo-registers; the per architecture pages carry the register names and the addressing quirks each target adds.

The four operand kinds

Every operand is one of four kinds:

R1                  register
$4                  immediate
label               branch target or symbol
-8(BX)(DI*4)        memory

Operands go source first, destination last: MOVQ x+0(FP), AX loads the argument into AX. This is the opposite of Intel order and the same order as AT&T, with the sigils removed: registers are bare names, immediates take $, memory is offset(base).

Registers

A register operand is its bare name, with no prefix: AX, X15, R14 on amd64; R0 to R30, ZR, V0 to V31 on arm64; X0 to X31, F0 to F31, V0 on riscv64; R0 to R31, F0 to F31, V0 on loong64. Sub-register and width selection rides the mnemonic, not the operand: the amd64 family spells MOVB, MOVW, MOVL, MOVQ, and the arm64 family suffices B, H, S, D, Q on the shared forms. Each architecture page lists its registers and the reserved ones.

Immediates

$ introduces a constant: $42, $-1, $0x2a, $'A', $bufSize. The $ applies to the whole constant expression that follows, so $(4*8+reader__size) is one immediate. Without the $, a number in operand position is an address, not a value; the classic error ADDQ 1, AX asks the assembler for the byte at address 1.

The one place a $ number is not an immediate is the frame and argument size field of TEXT, $16-24, which is two separate constants and not a subtraction; DIRECTIVES.md specifies it.

Memory

offset(base)
offset(base)(index*scale)

Both parts are optional where the target allows them: (BX) is the memory at BX, foo+16(SB) is a global, and on amd64 foo+32(SP)(R9*8) adds a scaled index. offset is a constant expression, optionally carrying a symbol name. The extensions beyond offset(base) are where the targets diverge, and each belongs to its architecture page: amd64 carries the index*scale form with scale 1, 2, 4 or 8 and its own rules on which registers may index; loong64 writes base plus index as (R4)(R5); the ARM family attaches shift amounts to the index register in its own spelling.

The address arithmetic is on byte addresses: the offset is added to the base as it stands, whatever the operand width of the instruction. Loading the third 8-byte word of an array at BX is 16(BX), not 2(BX).

The four pseudo-registers

Four names denote locations no target register holds, and they mean the same on every architecture:

  • FP, the frame pointer: the arguments and results of the current function, at positive offsets, in the order the Go prototype declares them. Every FP reference must carry a name: x+0(FP), and an unnamed 0(FP) is rejected. Results follow arguments; an unnamed result is called ret.
  • SP, the virtual stack pointer: the high end of the function's local frame, so locals live at negative offsets, x-8(SP). A reference without a name and without a plus, -8(SP), addresses the hardware stack pointer instead: the two spellings are one character apart and mean different registers. That is the sharpest edge in the language and the source of the deepest bugs.
  • SB, the static base: the origin of memory, used for globals and cross-package symbols, always with a name: foo(SB), foo+4(SB).
  • PC, the program counter: branch targets, and the explicit relative form 1(PC).

Symbol names

A symbol's full name is the package path, a period, and the base name. In source, the period is written U+00B7 (·) and a slash in the path U+2215 (∕), because the parser treats the ASCII forms as punctuation. Inside the package's own file, ·Name is enough and is the preferred spelling, since it survives a rename of the import path.

Spelling Meaning
·Name(SB) this package's Name
runtime·morestack(SB) another package's morestack
sourcedock.dev∕petrbalvin∕pkg·Name(SB) fully qualified
msg<>(SB) file-local, the static of this language; <> also makes the ABI field static in the object
Name<ABIInternal>(SB) ABI-qualified reference, the ABI in angle brackets after the name

The object file these symbols produce, with the index rules that decide what is referenced by name and what by index, is specified in GOOBJ.md.

What vet adds in Go

Inside a Go package, go vet's asmdecl analyzer checks every FP offset and name against the Go prototype, and checks the declared argument area against the frame. That layer, the prototype requirement and go_asm.h, belongs to RUNTIME.md; the grammar above is the whole of what the assembler itself requires.