docs(asm): open the assembly language reference
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user