docs(asm): open the assembly language reference
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -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