115 lines
5.0 KiB
Markdown
115 lines
5.0 KiB
Markdown
# 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:
|
||
|
||
```text
|
||
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
|
||
|
||
```text
|
||
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](../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.
|