asmgen — Quick start¶
This walks through the examples/add program shipped in the
asmgen repo: a
func add(a, b int64) int64 whose body is generated arm64 assembly.
arm64 required to run
The generated assembly is built under //go:build arm64 and exercised by a
runtime test. Generation runs anywhere, but building and testing the
example requires an arm64 host (Apple Silicon, an arm64 Linux box, or a
GitHub ubuntu-24.04-arm runner). The library packages themselves are
architecture-independent.
Install¶
1. Declare the function with no body¶
// add.go
package add
//go:generate go run gen.go
// add returns a + b. Implemented in add_arm64.s, generated by go-asmgen.
func add(a, b int64) int64
2. Write the generator¶
//go:build ignore
// gen.go — run with: go run gen.go
package main
import (
"fmt"
"os"
"github.com/go-asmgen/asmgen/arm64"
"github.com/go-asmgen/asmgen/emit"
)
func main() {
// func add(a, b int64) int64
sig := arm64.Layout(
[]string{"a", "b"}, []arm64.Type{arm64.Int64, arm64.Int64},
[]string{"ret"}, []arm64.Type{arm64.Int64},
)
b := arm64.NewFunc("add", sig, 0)
b.LoadArg("a", "R0").
LoadArg("b", "R1").
Raw("ADD R1, R0, R2").
StoreRet("R2", "ret").
Ret()
f := emit.NewFile("arm64")
f.Add(b.Func())
if err := os.WriteFile("add_arm64.s", []byte(f.String()), 0o644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("wrote add_arm64.s")
}
3. Generate, vet, build, test¶
go generate ./... # runs gen.go -> writes add_arm64.s
GOARCH=arm64 go vet ./... # asmdecl cross-checks .s offsets vs add.go decl
GOARCH=arm64 go build ./...
go test ./... # on an arm64 host (or under qemu/Rosetta)
The generated add_arm64.s:
// Code generated by go-asmgen. DO NOT EDIT.
//go:build arm64
#include "textflag.h"
TEXT ·add(SB), NOSPLIT, $0-24
MOVD a+0(FP), R0
MOVD b+8(FP), R1
ADD R1, R0, R2
MOVD R2, ret+16(FP)
RET
How correctness is checked¶
In priority order — the same checks the asmgen CI runs:
go vetasmdecl passes. The cheapest, strongest check: it verifies everyname+offset(FP)in the.smatches the Go declaration. Wrong offsets are caught before runtime.- Generated assembly is committed and up to date. CI regenerates and fails
on any diff, so the checked-in
.salways matches the generator. - Runtime test on real arm64 — the function is called and its result checked, on a native arm64 runner.