Skip to content

asmgen

asmgen — Overview

go-asmgen generates Go-compatible Plan 9 assembly for every 64-bit Go target — amd64, arm64, riscv64, loong64, ppc64le, and s390x. It splits the problem into three layers:

  • abi — the architecture-independent ABI0 frame layout (offsets, alignment, word size). All six targets share it.
  • amd64 / arm64 / riscv64 / loong64 / ppc64le / s390x — thin per-architecture builders: a move/register table over the shared layout, plus a Raw escape hatch for everything else.
  • emit — writes well-formed Plan 9 .s lines (the TEXT block, instruction lines, the file header with //go:build and #include "textflag.h"). It knows nothing about any specific ISA.

The key idea: emit text, don’t encode bytes

avo contains a full amd64 instruction encoder. That is powerful, but porting it to a new ISA means re-implementing encoding for that ISA. go-asmgen sidesteps this: it produces the same Plan 9 assembly text a human would hand-write, and cmd/asm does the encoding — the very same assembler the Go toolchain already uses. Adding an architecture therefore costs only a move table over the shared ABI0 model, not an encoder. The one exception is an instruction cmd/asm cannot assemble yet. Those are the ppc64le VSX float64 arithmetic (XVADDDP…) and the loong64 vector fused multiply-add (VFMADDD…). go-asmgen emits each one as a WORD whose comment gives the spelling proposed for cmd/asm, and keeps its encoding, references and proposed spelling in a registry (internal/gap). From that registry come the Go patches in goasm-patches/, and a weekly check that notices when Go catches up. Since v0.15.0 the arm64 vector float64 arithmetic (VFADD2D…) and the loong64 broadcast load are encoded by cmd/asm.

What you write

You describe a function’s signature, then drive a small builder:

sig := arm64.Layout(
    []string{"a", "b"}, []arm64.Type{arm64.Int64, arm64.Int64}, // args
    []string{"ret"}, []arm64.Type{arm64.Int64},                 // result
)

b := arm64.NewFunc("add", sig, 0)    // frameSize 0, NOSPLIT by default
b.LoadArg("a", "R0").
    LoadArg("b", "R1").
    Raw("ADD R1, R0, R2").
    StoreRet("R2", "ret").
    Ret()

What it emits

// 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

go vet’s asmdecl pass then cross-checks every name+offset(FP) against the Go declaration, and the function can be called like any other.

See the Quick start for the full, runnable example.