121 lines
5.7 KiB
Markdown
121 lines
5.7 KiB
Markdown
# 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.
|