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
|
||||
|
||||
- **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)
|
||||
documents the Go object file format in full: both containers, the 96
|
||||
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
|
||||
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)
|
||||
specifies the object file format completely. The assembler language
|
||||
reference follows.
|
||||
specifies the object file format completely, and
|
||||
[docs/asm/README.md](docs/asm/README.md) opens the language reference
|
||||
with its common core. The per-architecture pages follow.
|
||||
|
||||
## Direction
|
||||
|
||||
@@ -315,6 +316,7 @@ recipe.
|
||||
them
|
||||
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
|
||||
- [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
|
||||
- [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