docs(asm): open the assembly language reference
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- **The Plan 9 assembly language reference.** [docs/asm/](docs/asm/README.md)
|
||||||
|
opens the complete language reference with its common core: the lexicon,
|
||||||
|
statement structure and constant expressions, the operand grammar with
|
||||||
|
the pseudo-registers and symbol naming, the directives and the function
|
||||||
|
flag vocabulary, preprocessing with `#define` and `#include`, and the
|
||||||
|
Go-embedded layer (ABI0, prototypes, `go_asm.h`, `funcdata.h` and the
|
||||||
|
runtime contract). Every claim is verified against `go tool asm` of
|
||||||
|
Go 1.27.1 and gasm's differential tests; the per-architecture pages and
|
||||||
|
generated instruction appendices follow.
|
||||||
- **GOOBJ format specification.** [docs/GOOBJ.md](docs/GOOBJ.md)
|
- **GOOBJ format specification.** [docs/GOOBJ.md](docs/GOOBJ.md)
|
||||||
documents the Go object file format in full: both containers, the 96
|
documents the Go object file format in full: both containers, the 96
|
||||||
byte header and all 19 blocks, every structure with its byte
|
byte header and all 19 blocks, every structure with its byte
|
||||||
|
|||||||
@@ -186,8 +186,9 @@ verified the way the code is verified: an encoding documented here is
|
|||||||
one that differential tests against `go tool asm` confirm
|
one that differential tests against `go tool asm` confirm
|
||||||
byte-for-byte, and a format field documented here is one the linker
|
byte-for-byte, and a format field documented here is one the linker
|
||||||
demonstrably reads. The work has begun: [docs/GOOBJ.md](docs/GOOBJ.md)
|
demonstrably reads. The work has begun: [docs/GOOBJ.md](docs/GOOBJ.md)
|
||||||
specifies the object file format completely. The assembler language
|
specifies the object file format completely, and
|
||||||
reference follows.
|
[docs/asm/README.md](docs/asm/README.md) opens the language reference
|
||||||
|
with its common core. The per-architecture pages follow.
|
||||||
|
|
||||||
## Direction
|
## Direction
|
||||||
|
|
||||||
@@ -315,6 +316,7 @@ recipe.
|
|||||||
them
|
them
|
||||||
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
|
||||||
- [docs/GOOBJ.md](docs/GOOBJ.md): the GOOBJ object file format specification
|
- [docs/GOOBJ.md](docs/GOOBJ.md): the GOOBJ object file format specification
|
||||||
|
- [docs/asm/](docs/asm/README.md): the Plan 9 assembly language reference
|
||||||
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): development setup and recipes
|
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): development setup and recipes
|
||||||
- [CHANGELOG.md](CHANGELOG.md): release history
|
- [CHANGELOG.md](CHANGELOG.md): release history
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
# Directives: TEXT, DATA, GLOBL and the annotations
|
||||||
|
|
||||||
|
Layer 1, the common language, with the flag vocabulary both layers share.
|
||||||
|
Verified against `go tool asm` of Go 1.27.1, against the shipped headers
|
||||||
|
`textflag.h` and `funcdata.h` in `$GOROOT/pkg/include`, and against gasm's
|
||||||
|
parser. Where gasm extends a directive, the extension says so and is marked.
|
||||||
|
|
||||||
|
Six directives exist. Three define things: TEXT, DATA, GLOBL. Three
|
||||||
|
annotate: FUNCDATA, PCDATA, PCALIGN.
|
||||||
|
|
||||||
|
## TEXT
|
||||||
|
|
||||||
|
```text
|
||||||
|
// func Add(a, b int64) int64
|
||||||
|
TEXT ·Add(SB), NOSPLIT, $0-24
|
||||||
|
...instructions...
|
||||||
|
RET
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
TEXT symbol(SB), [flags,] $framesize[-argsize]
|
||||||
|
```
|
||||||
|
|
||||||
|
- The symbol is an `·Name(SB)` reference into the current package, or a
|
||||||
|
fully qualified name.
|
||||||
|
- The optional flag argument is a constant expression, normally an OR of the
|
||||||
|
names from `textflag.h`, the table below. Without `#include "textflag.h"`
|
||||||
|
the names are not macros and the assembler reports the misleading error
|
||||||
|
`illegal or missing addressing mode for symbol NOSPLIT`: include the
|
||||||
|
header first.
|
||||||
|
- `$framesize-argsize` is two constants, not a subtraction: the local frame
|
||||||
|
size in bytes, and the caller's argument area in bytes. The argument size
|
||||||
|
may be omitted entirely, `$16`, which marks the argument size unknown
|
||||||
|
(0x80000000 in the object, the value of `ArgsSizeUnknown` from
|
||||||
|
`funcdata.h`); a frame size may be negative only in the generated ABI
|
||||||
|
wrappers.
|
||||||
|
- A function whose last instruction is not a branch cannot fall through into
|
||||||
|
the next TEXT: the toolchain appends a jump to itself, so end functions
|
||||||
|
with `RET` deliberately.
|
||||||
|
- One TEXT per symbol; redeclaring is an error. The TEXT line also fixes the
|
||||||
|
function's source line for traceback: it is the line number that pcln
|
||||||
|
reports for the function's start.
|
||||||
|
|
||||||
|
The framesize and argsize fields do real work: the framesize drives the
|
||||||
|
stack-split preamble (RUNTIME.md carries the contract), and both travel into
|
||||||
|
the FuncInfo record of the object (GOOBJ.md carries its layout).
|
||||||
|
|
||||||
|
### The flag table
|
||||||
|
|
||||||
|
Values from `textflag.h`, in agreement with `cmd/internal/obj/textflag.go`:
|
||||||
|
|
||||||
|
| Name | Value | Applies to | Meaning |
|
||||||
|
|---|---|---|---|
|
||||||
|
| NOPROF | 1 | both | do not profile; deprecated |
|
||||||
|
| DUPOK | 2 | both | the linker may keep one of several duplicates |
|
||||||
|
| NOSPLIT | 4 | TEXT | no stack-split preamble |
|
||||||
|
| RODATA | 8 | data | put the data in a read-only section |
|
||||||
|
| NOPTR | 16 | data | the data contains no pointers |
|
||||||
|
| WRAPPER | 32 | TEXT | a wrapper; must not disable `recover` |
|
||||||
|
| NEEDCTXT | 64 | TEXT | a closure consuming the context register |
|
||||||
|
| TLSBSS | 256 | data | a thread local word in BSS |
|
||||||
|
| NOFRAME | 512 | TEXT | no frame setup; only valid with a frame size of 0 |
|
||||||
|
| REFLECTMETHOD | 1024 | TEXT | the function calls `reflect.Type.Method` or `MethodByName` |
|
||||||
|
| TOPFRAME | 2048 | TEXT | the outermost frame; unwinders stop here |
|
||||||
|
| ABIWRAPPER | 4096 | TEXT | an ABI transition wrapper |
|
||||||
|
|
||||||
|
Rules with teeth:
|
||||||
|
|
||||||
|
- `NOSPLIT` removes the split check, so the frame plus everything the
|
||||||
|
function calls must fit in the stack segment that remains. It exists to
|
||||||
|
protect the splitting code itself; reaching for it to save two instructions
|
||||||
|
is how stack overflows corrupt memory. On amd64 the assembler additionally
|
||||||
|
marks small leaf functions NoSplit itself and omits the check, so the
|
||||||
|
absence of the preamble is not proof the flag was written.
|
||||||
|
- A TEXT whose symbol is declared `ABIInternal` must carry NOSPLIT: the
|
||||||
|
assembler rejects it otherwise, because it cannot generate
|
||||||
|
the split path for a register-ABI function.
|
||||||
|
- `RODATA` implies NOPTR for the garbage collector.
|
||||||
|
|
||||||
|
## DATA
|
||||||
|
|
||||||
|
```text
|
||||||
|
DATA ·table+0(SB)/8, $0x0102030405060708
|
||||||
|
DATA ·msg+0(SB)/14, $"hello, world\n"
|
||||||
|
GLOBL ·msg(SB), RODATA, $14
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
DATA symbol+offset(SB)/width, value
|
||||||
|
```
|
||||||
|
|
||||||
|
- `width` is exactly 1, 2, 4 or 8: the initialiser is written into the data
|
||||||
|
image at `symbol+offset` in that many bytes.
|
||||||
|
- The value is an integer or character constant of the width, or a string
|
||||||
|
literal whose byte length equals the width exactly; escapes count. Long
|
||||||
|
data is written as successive DATA lines at increasing offsets; bytes the
|
||||||
|
directives never name are zero.
|
||||||
|
- Every symbol initialised with DATA ends with a GLOBL line declaring its
|
||||||
|
total size, after all of its DATA lines.
|
||||||
|
|
||||||
|
A symbol containing pointers cannot be defined in assembly, because the
|
||||||
|
collector cannot see into it: define it in Go and refer to it by name. As a
|
||||||
|
rule, data that is not read-only belongs in Go.
|
||||||
|
|
||||||
|
Extension, gasm only: a DATA initialiser may name a symbol,
|
||||||
|
`DATA ·fn+0(SB)/8, $·handler(SB)`, which gasm lays down as an absolute
|
||||||
|
relocation on that field. The toolchain offers no ground truth for this
|
||||||
|
form; gasm's behaviour is verified by linking and execution.
|
||||||
|
|
||||||
|
## GLOBL
|
||||||
|
|
||||||
|
```text
|
||||||
|
GLOBL symbol(SB), [flags,] $size
|
||||||
|
```
|
||||||
|
|
||||||
|
Declares the symbol global with its total size in bytes. The useful flags
|
||||||
|
are RODATA, NOPTR, DUPOK and TLSBSS from the table above. Uninitialised
|
||||||
|
bytes are zero, which makes GLOBL with no DATA the language's BSS.
|
||||||
|
|
||||||
|
## FUNCDATA and PCDATA
|
||||||
|
|
||||||
|
```text
|
||||||
|
FUNCDATA $functypeid, symbol(SB)
|
||||||
|
PCDATA $pctypeid, $value
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiler's annotations for the garbage collector and traceback, named by
|
||||||
|
the ids in `funcdata.h`: FUNCDATA 0 to 7 (args pointer maps, locals pointer
|
||||||
|
maps, stack objects, inline tree, open-coded defer info, argument info,
|
||||||
|
argument liveness, wrap info), PCDATA 0 to 4 (unsafe point, stack map index,
|
||||||
|
inline tree index, argument liveness index, panic bounds). Assembly code
|
||||||
|
normally reaches them only through the macro forms in `funcdata.h`, which
|
||||||
|
RUNTIME.md explains. Outside the macros, hand-written PCDATA is meaningless:
|
||||||
|
the values are pc-value tables the compiler builds from its own view of the
|
||||||
|
program.
|
||||||
|
|
||||||
|
## PCALIGN
|
||||||
|
|
||||||
|
```text
|
||||||
|
PCALIGN $32
|
||||||
|
```
|
||||||
|
|
||||||
|
Pads the code so that the next instruction lands on the given boundary,
|
||||||
|
which must be a power of two and at least the target's instruction
|
||||||
|
alignment. Supported on amd64, arm64, ppc64, loong64 and riscv64. The
|
||||||
|
padding instructions are the target's NOP encoding, so the bytes between
|
||||||
|
functions differ from what the instruction stream alone would produce, which
|
||||||
|
matters to anyone comparing encodings byte for byte.
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Language: lexicon, statements and expressions
|
||||||
|
|
||||||
|
Layer 1, the common language, the same on every target. Verified against
|
||||||
|
`go tool asm` of Go 1.27.1 and against gasm's parser, which is differentially
|
||||||
|
tested against the toolchain. The authoritative sources behind this page are
|
||||||
|
the assembler's lexer (`cmd/asm/internal/lex`), its parser
|
||||||
|
(`cmd/asm/internal/asm/parse.go`) and the toolchain's own test data.
|
||||||
|
|
||||||
|
## Source files and targets
|
||||||
|
|
||||||
|
An assembly source is a `.s` file. The Go build convention names a
|
||||||
|
target-specific file with the architecture suffix, `_amd64.s`, `_arm64.s`,
|
||||||
|
`_riscv64.s` or `_loong64.s`; files without a suffix are portable across
|
||||||
|
targets. The same assembler program assembles every target: `go tool asm`
|
||||||
|
picks the target from the `GOOS` and `GOARCH` environment variables, and gasm
|
||||||
|
from the file name suffix or the `--arch` flag.
|
||||||
|
|
||||||
|
## Character set and identifiers
|
||||||
|
|
||||||
|
Sources are ASCII text. An identifier is a sequence of ASCII letters, digits
|
||||||
|
and underscores, digits never first, with exactly two additions:
|
||||||
|
|
||||||
|
- U+00B7, the middle dot `·`, stands for the period in a symbol's
|
||||||
|
package-qualified name;
|
||||||
|
- U+2215, the division slash `∕`, stands for the slash in a package path.
|
||||||
|
|
||||||
|
The two substitutions exist because the parser treats a real period and a
|
||||||
|
real slash as punctuation. The syntax is otherwise uppercase throughout:
|
||||||
|
instructions, registers and directives are written in upper case. The one
|
||||||
|
inherited exception is the `g` register name on 32-bit ARM.
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
Two comment forms, both Go's:
|
||||||
|
|
||||||
|
```text
|
||||||
|
// a line comment
|
||||||
|
/* a block comment */
|
||||||
|
```
|
||||||
|
|
||||||
|
A comment of the form `//go:build` or the legacy `+build` comment is not a
|
||||||
|
plain comment: the lexer reports it to the build system as a build
|
||||||
|
constraint.
|
||||||
|
|
||||||
|
## Statements
|
||||||
|
|
||||||
|
The grammar of one line, from the parser:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{label:} WORD[.qualifier] [ arg {, arg} ] (';' | '\n')
|
||||||
|
```
|
||||||
|
|
||||||
|
- A **label** is an identifier followed by a colon. Labels are
|
||||||
|
function-local: two functions in one file may reuse the same name, and a
|
||||||
|
reference resolves within the function that contains it. A branch
|
||||||
|
instruction names its target with a bare label operand, and the assembler
|
||||||
|
resolves it PC-relative. The explicit forms `offset(PC)`, a constant
|
||||||
|
counting instructions from the branch, and `name(SB)`, a cross-function
|
||||||
|
static reference, appear as branch targets as well.
|
||||||
|
- **WORD** is the instruction or directive name, upper case. On the ARM
|
||||||
|
family the word may carry a dot qualifier selecting a condition or shift
|
||||||
|
mode, such as the condition suffixes on 32-bit ARM; the amd64, arm64,
|
||||||
|
riscv64 and loong64 assemblies carry no instruction qualifiers apart from
|
||||||
|
their own width suffixes, which are part of the mnemonic.
|
||||||
|
- **Arguments** are separated by commas, with no trailing comma.
|
||||||
|
- A statement ends at a newline or at a semicolon, so several statements fit
|
||||||
|
on one line separated by `;`. Blank lines are free.
|
||||||
|
|
||||||
|
The first word of a line is a directive if it is one of the directive names
|
||||||
|
(TEXT, DATA, GLOBL, FUNCDATA, PCDATA, PCALIGN) and an instruction otherwise.
|
||||||
|
Unknown instruction names are errors; the instruction set is the set the
|
||||||
|
toolchain itself defines per target, plus the common pseudo-instructions.
|
||||||
|
|
||||||
|
## Literals
|
||||||
|
|
||||||
|
| Form | Examples | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Integer | `0`, `42`, `0x2a`, `0o52`, `0b101010`, `1_000` | decimal, hexadecimal, octal and binary forms with Go's digit separators |
|
||||||
|
| Character | `'a'`, `'\n'`, `'\x41'` | single quoted, Go escape rules |
|
||||||
|
| String | `"this program can only run\n"` | double quoted, Go escape rules; accepted where an operand takes raw bytes, in practice a DATA initialiser |
|
||||||
|
| Float | `1.5`, `1e9` | accepted by the lexer; only meaningful where the target's encoding takes a float operand |
|
||||||
|
|
||||||
|
## Expressions
|
||||||
|
|
||||||
|
Constant expressions may appear wherever a constant is expected: in
|
||||||
|
immediates after `$`, in memory offsets, in frame and data sizes. The
|
||||||
|
evaluator works on unsigned 64-bit values with Go's operator precedence, and
|
||||||
|
the parser states its grammar in exactly those terms:
|
||||||
|
|
||||||
|
```text
|
||||||
|
expr = term { '+' term | '-' term | '|' term | '^' term }
|
||||||
|
term = factor { '*' factor | '/' factor | '%' factor | '<<' factor | '>>' factor | '&' factor }
|
||||||
|
factor = const | '+' factor | '-' factor | '~' factor | '(' expr ')'
|
||||||
|
```
|
||||||
|
|
||||||
|
Two consequences are worth naming, because the arithmetic surprises people
|
||||||
|
who read it as C:
|
||||||
|
|
||||||
|
- Shifts bind at the multiplicative level, next to `*` and `&`, while `|`
|
||||||
|
and `^` bind at the additive level. `$x<<1|3` computes `(x<<1)|3`, which
|
||||||
|
differs from `x*2+3` whenever `x` is odd. Plan 9 arithmetic is Go
|
||||||
|
precedence applied to a byte-oriented language, not the C expression it
|
||||||
|
resembles.
|
||||||
|
- The evaluator is unsigned and guarded: division or modulo by zero is an
|
||||||
|
error, and so is dividing a value with the high bit set; shift counts must
|
||||||
|
be non-negative; and a right shift of a value with the high bit set is
|
||||||
|
rejected rather than sign-extended.
|
||||||
|
|
||||||
|
An address expression such as `(index*4)(base)` is evaluated at assembly
|
||||||
|
time only if every name in it is a constant; a name that resolves to a
|
||||||
|
symbol turns the expression into a relocation request, never into a folded
|
||||||
|
constant.
|
||||||
|
|
||||||
|
Named constants enter expressions through the preprocessor (`#define`,
|
||||||
|
`-D`) and, in Go-embedded packages, through the generated `go_asm.h`; see
|
||||||
|
PREPROCESSOR.md and RUNTIME.md.
|
||||||
|
|
||||||
|
## The common pseudo-instructions
|
||||||
|
|
||||||
|
A handful of instructions exist on every target, assembled by the assembler
|
||||||
|
itself rather than the encoder: `NOP`, which emits the target's no-operation
|
||||||
|
encoding, and the frame-management pseudo-instructions the compiler emits
|
||||||
|
(`FUNCDATA`, `PCDATA`) which DIRECTIVES.md specifies. Everything else is the
|
||||||
|
target's own instruction set, and the assembler knows only the instructions
|
||||||
|
the toolchain's compiler emits; a hand-written kernel wanting more lays the
|
||||||
|
encoding down with `BYTE` on amd64 or waits for the extended layer.
|
||||||
|
|
||||||
|
## Case study: three lines, decomposed
|
||||||
|
|
||||||
|
```text
|
||||||
|
B.EQ 1(PC) // arm64: condition qualifier on the mnemonic,
|
||||||
|
// target one instruction past the branch
|
||||||
|
JMP done // every target: bare label, function-local,
|
||||||
|
// resolved PC-relative
|
||||||
|
MOVQ $reader__size>>3, CX // amd64: expression over a go_asm.h constant
|
||||||
|
```
|
||||||
|
|
||||||
|
The first shows a qualifier and the explicit relative target form; the second
|
||||||
|
the ordinary label reference; the third an expression over a generated
|
||||||
|
constant. Labels are reusable between functions without conflict.
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Preprocessing: include, define and selection
|
||||||
|
|
||||||
|
Layer 1, the common language. Verified against the preprocessor inside
|
||||||
|
`go tool asm` of Go 1.27.1 (`cmd/asm/internal/lex`), whose directives are
|
||||||
|
`#define`, `#undef`, `#include`, `#ifdef`, `#ifndef`, `#else`, `#endif` and
|
||||||
|
`#line`, and against gasm's implementation, which is differentially tested
|
||||||
|
against the toolchain's.
|
||||||
|
|
||||||
|
Input runs through a simplified C preprocessor before the parser sees it.
|
||||||
|
The set is deliberately small: there is no `#if` with constant expressions
|
||||||
|
and no token pasting with `##`. `#line` is honoured, so it changes the
|
||||||
|
positions the assembler reports and records.
|
||||||
|
|
||||||
|
## #include
|
||||||
|
|
||||||
|
```text
|
||||||
|
#include "textflag.h"
|
||||||
|
#include "go_asm.h"
|
||||||
|
#include "defs_linux_amd64.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
The search path, in order: the directory of the including file, then the
|
||||||
|
directories given by repeatable `-I` flags. The assembler seeds no default
|
||||||
|
of its own: a bare `go tool asm` invocation finds none of the standard
|
||||||
|
headers, and it is the `go` build system that passes `$GOROOT/pkg/include`
|
||||||
|
among the `-I` directories when it drives the build. That directory ships
|
||||||
|
`textflag.h`, `funcdata.h` and the per architecture register headers.
|
||||||
|
Includes nest; a file included twice through different paths is processed
|
||||||
|
twice, which is why headers guard their defines.
|
||||||
|
|
||||||
|
## #define and #undef
|
||||||
|
|
||||||
|
```text
|
||||||
|
#define bufSize 1024
|
||||||
|
#define MOVD(d, s) MOVQ s, d
|
||||||
|
#undef bufSize
|
||||||
|
```
|
||||||
|
|
||||||
|
- An object macro replaces its name with its token sequence at the point of
|
||||||
|
use.
|
||||||
|
- A parameterised macro takes its arguments in parentheses and substitutes
|
||||||
|
them into the body. Macro parameters compose with the rest of the
|
||||||
|
language: an argument used with an element suffix, as in `A.S4` on the
|
||||||
|
vector forms, substitutes correctly.
|
||||||
|
- Redefinition is an error; `#undef` first, or pick a new name.
|
||||||
|
- The `-D name[=value]` flag predefines an object macro from the command
|
||||||
|
line, repeatable, exactly as `#define` would; a `-D` without a value
|
||||||
|
defines the name as `1`.
|
||||||
|
- Expansion happens when the name is used, so a macro may expand to
|
||||||
|
instructions, operands or fragments of either, and a macro body may use
|
||||||
|
macros defined before it.
|
||||||
|
|
||||||
|
`textflag.h` and `funcdata.h` are themselves ordinary `#define` files: the
|
||||||
|
flag names and the runtime macros are preprocessor definitions, not language
|
||||||
|
keywords. That is why a missing include produces a parser error at the first
|
||||||
|
use of `NOSPLIT` rather than a complaint about the name.
|
||||||
|
|
||||||
|
## #ifdef, #ifndef, #else, #endif
|
||||||
|
|
||||||
|
```text
|
||||||
|
#ifdef GOOS_windows
|
||||||
|
#define SYSCALL_INT 0x2b
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
Selection is by defined-name only: `#ifdef`, `#ifndef`, `#else`, `#endif`,
|
||||||
|
nesting freely. There is no `#if defined(x) && y`, because the preprocessor
|
||||||
|
evaluates no expressions; reach that with a build-tag Go file generating a
|
||||||
|
header, which is exactly how the runtime's own `go_asm.h` and defs headers
|
||||||
|
are produced.
|
||||||
|
|
||||||
|
## What preprocessing does not cover
|
||||||
|
|
||||||
|
The preprocessor is textual and runs first, so it knows nothing of assembly
|
||||||
|
semantics: it does not check that a macro expansion is a legal instruction,
|
||||||
|
and it does not participate in the constant expression evaluator, which runs
|
||||||
|
later, in the parser. A constant folded with `#define` and a constant folded
|
||||||
|
in an operand expression end at the same value through different doors;
|
||||||
|
GOOBJ.md records both in the object identically.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# The Plan 9 assembly language
|
||||||
|
|
||||||
|
This directory is the reference for the Plan 9 assembly language as the Go
|
||||||
|
toolchain and gasm accept it, written to be complete enough to implement
|
||||||
|
against. It exists because no such reference exists upstream: Go documents
|
||||||
|
the language on a single page, and the rest of the knowledge lives in the
|
||||||
|
toolchain's source and in the practice of reading it.
|
||||||
|
|
||||||
|
Every page carries the same conformance statement: which layer of the system
|
||||||
|
it describes, which toolchain release it was verified against, and how the
|
||||||
|
claims were checked. Pages in this directory are verified against Go 1.27.1
|
||||||
|
and against gasm's own differential test suite, which compares gasm's
|
||||||
|
behaviour with `go tool asm` byte for byte and output for output.
|
||||||
|
|
||||||
|
## The three layers
|
||||||
|
|
||||||
|
The reference deliberately separates three layers, because their rules have
|
||||||
|
different owners and different lifetimes:
|
||||||
|
|
||||||
|
1. **The common language** (LANGUAGE, OPERANDS, DIRECTIVES,
|
||||||
|
PREPROCESSOR): the syntax, operands, directives and preprocessing, the
|
||||||
|
same on every target and meaningful without a Go runtime.
|
||||||
|
2. **The Go-embedded layer** (RUNTIME): everything that exists only because
|
||||||
|
the code runs inside a Go program: the ABI0 contract, generated wrappers,
|
||||||
|
`go_asm.h`, the garbage collector annotations and `go vet` checks.
|
||||||
|
3. **The standalone layer** (STANDALONE, planned with the standalone
|
||||||
|
compilation phase): using the language outside Go, through gasm's ELF
|
||||||
|
output and the extended instruction set, where the toolchain offers no
|
||||||
|
ground truth and execution testing is the only verification.
|
||||||
|
|
||||||
|
A rule stated in layer 1 holds on every target. A rule stated in layer 2
|
||||||
|
says which part of the Go machinery imposes it. Nothing in layer 3 changes
|
||||||
|
layers 1 or 2; it extends them.
|
||||||
|
|
||||||
|
## Pages
|
||||||
|
|
||||||
|
| Page | Layer | Contents |
|
||||||
|
|---|---|---|
|
||||||
|
| [LANGUAGE.md](LANGUAGE.md) | 1 | lexicon, statement structure, labels, literals, expressions |
|
||||||
|
| [OPERANDS.md](OPERANDS.md) | 1 | operand grammar, pseudo-registers, addressing modes, symbol naming |
|
||||||
|
| [DIRECTIVES.md](DIRECTIVES.md) | 1 | TEXT, DATA, GLOBL, FUNCDATA, PCDATA, PCALIGN and the function flags |
|
||||||
|
| [PREPROCESSOR.md](PREPROCESSOR.md) | 1 | `#include`, `#define`, `#ifdef` and friends, `-D`, `-I` |
|
||||||
|
| [RUNTIME.md](RUNTIME.md) | 2 | ABI0, prototypes, `go_asm.h`, `funcdata.h`, `go vet` |
|
||||||
|
| AMD64, ARM64, RISCV64, LOONG64 | 1, 3 | per architecture: registers, conventions, addressing, instruction families and the generated instruction appendices |
|
||||||
|
| STANDALONE.md | 3 | the language outside Go |
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
The common-language core and the Go-embedded layer are written and verified.
|
||||||
|
The four per-architecture pages and their generated instruction appendices
|
||||||
|
follow, architecture by architecture; STANDALONE.md lands with the standalone
|
||||||
|
compilation phase. The object format these pages feed is specified in
|
||||||
|
[GOOBJ.md](../GOOBJ.md).
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# The Go-embedded layer: ABI0, prototypes and the runtime contract
|
||||||
|
|
||||||
|
Layer 2: everything that exists only because the assembly runs inside a Go
|
||||||
|
program. Without a Go runtime this page does not apply; the language of
|
||||||
|
OPERANDS.md and DIRECTIVES.md still does. Verified against Go 1.27.1, against
|
||||||
|
the shipped `funcdata.h` header, and against the object files the toolchain
|
||||||
|
produces, which were parsed and checked field by field while writing
|
||||||
|
[GOOBJ.md](../GOOBJ.md).
|
||||||
|
|
||||||
|
## Hand-written assembly is ABI0
|
||||||
|
|
||||||
|
Go functions compiled from source use ABIInternal, the register-based
|
||||||
|
calling convention, which the toolchain documents as unstable and free to
|
||||||
|
change between releases. A `.s` function is written against ABI0, the stack
|
||||||
|
based convention: arguments and results live in the caller's frame at
|
||||||
|
positive FP offsets, byte-addressed, in declaration order, with no registers
|
||||||
|
assigned at all. The toolchain generates the wrapper that translates between
|
||||||
|
the two; a caller in Go calling an assembly function goes through it, and it
|
||||||
|
is marked `ABIWRAPPER` in the object. Hand-writing a bridge is never needed
|
||||||
|
and never correct.
|
||||||
|
|
||||||
|
## Every assembly function carries a Go prototype
|
||||||
|
|
||||||
|
```go
|
||||||
|
package add
|
||||||
|
|
||||||
|
func Add(x, y int64) int64
|
||||||
|
```
|
||||||
|
|
||||||
|
The body-less declaration is not optional, and not only for the linker: it
|
||||||
|
is what tells the garbage collector which arguments and results hold
|
||||||
|
pointers, and what `go vet` checks the assembly against. Even a function
|
||||||
|
nothing in Go calls gets one. Consequences:
|
||||||
|
|
||||||
|
- The FP operand names and offsets are checked by vet's asmdecl analyzer
|
||||||
|
against the prototype: `x+0(FP)` must name an argument that exists, at the
|
||||||
|
offset the prototype says. A file that assembles and links can still fail
|
||||||
|
vet.
|
||||||
|
- The declared argument area in `$framesize-argsize` is checked against the
|
||||||
|
prototype's size. An omitted argsize marks the argument size unknown
|
||||||
|
(0x80000000 in the object, the value of `ArgsSizeUnknown` from
|
||||||
|
`funcdata.h`), which is the normal spelling for functions with no Go
|
||||||
|
callers.
|
||||||
|
- `//go:noescape` on the declaration tells the compiler that a pointer
|
||||||
|
argument does not escape, for assembly that keeps the pointer beyond the
|
||||||
|
call.
|
||||||
|
|
||||||
|
## The frame, the stack and the collector
|
||||||
|
|
||||||
|
The runtime owns the stack and the pointer map, and assembly must hold up
|
||||||
|
its end of four rules:
|
||||||
|
|
||||||
|
1. **Arguments are initialised on entry; results are not.** A function whose
|
||||||
|
results hold live pointers across a call must zero them and then execute
|
||||||
|
`GO_RESULTS_INITIALIZED`. Designing functions that return no pointers
|
||||||
|
avoids the problem.
|
||||||
|
2. **A frame with calls and no local pointers says so** with
|
||||||
|
`NO_LOCAL_POINTERS`. A frame with local pointers that the runtime cannot
|
||||||
|
see is not allowed at all: assembly cannot describe a pointer-containing
|
||||||
|
local, so it must not have one. Data symbols containing pointers are the
|
||||||
|
same: define them in Go.
|
||||||
|
3. **The stack may move.** Stack growth copies the frame, so no pointer into
|
||||||
|
the frame may be held across a call, and the raw hardware SP register may
|
||||||
|
not be cached across a call either.
|
||||||
|
4. **The split check is not optional by default.** Without NOSPLIT, the
|
||||||
|
assembler inserts the stack-growth preamble, including the morestack
|
||||||
|
block for framed functions; NOSPLIT is a contract that the frame and
|
||||||
|
everything below it fit in the remaining stack segment. On amd64 the
|
||||||
|
assembler also marks small leaf functions NoSplit itself and skips the
|
||||||
|
preamble, so silence is not a promise.
|
||||||
|
|
||||||
|
The simplest safe shape is a leaf function with no local frame and no calls:
|
||||||
|
it needs no annotation beyond the prototype.
|
||||||
|
|
||||||
|
## go_asm.h: Go constants and layout in assembly
|
||||||
|
|
||||||
|
A package with `.s` files gets a generated header. Include it and use the
|
||||||
|
generated names instead of hard-coding layouts, which lie silently when the
|
||||||
|
Go side changes:
|
||||||
|
|
||||||
|
| Go declaration | Assembly name |
|
||||||
|
|---|---|
|
||||||
|
| `const bufSize = 1024` | `const_bufSize` |
|
||||||
|
| field `r` of `type reader struct` | `reader_r` |
|
||||||
|
| size of `type reader struct` | `reader__size` |
|
||||||
|
|
||||||
|
The constants arrive as macros, usable as immediates and offsets, computed
|
||||||
|
from the Go declarations. An ambiguous name, such as a struct that really
|
||||||
|
has a `_size` field, fails the generation with a redefinition error.
|
||||||
|
|
||||||
|
## funcdata.h: the runtime macros
|
||||||
|
|
||||||
|
`$GOROOT/pkg/include/funcdata.h` defines the PCDATA and FUNCDATA ids and the
|
||||||
|
three macros assembly normally uses instead:
|
||||||
|
|
||||||
|
| Macro | Expands to | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `GO_ARGS` | `FUNCDATA $FUNCDATA_ArgsPointerMaps, go_args_stackmap(SB)` | the Go prototype defines the argument pointer map |
|
||||||
|
| `GO_RESULTS_INITIALIZED` | `PCDATA $PCDATA_StackMapIndex, $1` | results are initialised; treat them as live from here |
|
||||||
|
| `NO_LOCAL_POINTERS` | `FUNCDATA $FUNCDATA_LocalsPointerMaps, no_pointers_stackmap(SB)` | the frame holds no pointers |
|
||||||
|
|
||||||
|
`GO_ARGS` is inserted implicitly by the assembler for any function whose
|
||||||
|
package-qualified name belongs to the current package, which is why most
|
||||||
|
assembly never writes it. `NOSPLIT` leaf functions that call nothing need
|
||||||
|
none of the three.
|
||||||
|
|
||||||
|
The underlying ids, for reading toolchain output rather than for writing
|
||||||
|
source: FUNCDATA 0 to 7 are args pointer maps, locals pointer maps, stack
|
||||||
|
objects, inline tree, open-coded defer info, argument info, argument
|
||||||
|
liveness and wrap info; PCDATA 0 to 4 are unsafe point, stack map index,
|
||||||
|
inline tree index, argument liveness index and panic bounds.
|
||||||
|
|
||||||
|
## What the runtime does with all of this
|
||||||
|
|
||||||
|
The object file records the annotations as aux symbols and FuncInfo records;
|
||||||
|
GOOBJ.md specifies the encoding. The linker assembles them into the runtime's
|
||||||
|
pclntable, which traceback and the collector consume. An assembly function
|
||||||
|
that misdeclares its frame is not a compile error and usually not a link
|
||||||
|
error: it is a wrong collector decision or a wrong traceback at runtime,
|
||||||
|
which is why the annotations are a contract and not documentation.
|
||||||
Reference in New Issue
Block a user