Skip to content

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.

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.