Open-source project
mmcloughlin/avo avatar
mmcloughlin/avo

avo: writing x86-64 assembly as a Go program

Generate x86 Assembly with Go

2,992 stars97 forksGoBSD-3-Clause

At a glance

What is it?
avo generates Go assembly from ordinary Go code, with virtual registers, automatic argument loading and stub generation. It suits projects that already decided hand-written .s files are too hard to maintain, and it is still labelled experimental in its own README.
Who is it for?
Adopt avo if you maintain Go assembly and want the generator, the register allocation and the stub file to live in a Go program you can review in the same pull request as the call site. Do not adopt it if you only need a faster inner loop that the Go compiler already vectorises, or if your target is not x86-64, since the README describes it as generating x86 assembly.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The maintenance problem avo targets

Go assembly is a text format with its own pseudo-instructions, and a function that indexes a slice has to spell out the base pointer offset, the length offset and the return slot offset by hand. Change the signature from one uint64 argument to two and every offset in the body is wrong. avo's answer is to make the generator a Go program: the README states that avo programs are Go programs, so loop structure, constants and helper functions come from the language rather than from macros. The audience is narrow and specific. It is the maintainer of a hashing, encoding or arithmetic package who has already measured that the compiler's output is not enough and who now has to keep that assembly correct across Go releases. The README's adopters list, drawn from avo's third-party test suite, includes the Go standard library itself, which tells you the intended scale of the user. If you have never written a .s file, avo does not remove that requirement; it changes which file you edit.

Virtual registers and the build package

The mechanism is a small builder API in the build package. A generator calls TEXT to declare the function and its Go signature, Load to pull an argument into a register, instruction functions such as ADDQ and XORQ, Label and LabelRef for control flow, Store to write a return value, and Generate to emit the files. Registers are virtual: in the README's slice-sum example, GP64() returns a register value that avo later maps to a physical register, and the emitted code uses DX. That mapping is the part worth understanding, because it is where avo's output differs from what you would write by hand. Argument and return offsets are computed from the declared signature, so xs_base+0(FP), xs_len+8(FP) and ret+24(FP) appear without the generator naming them. The output is a .s file with a generated-code header and, when the -stubs flag is passed, a Go file containing the function declaration and its doc comment. Data sections, external types, complex numbers and compiler pragmas each have a dedicated example under examples/, so the API surface is broader than the add example suggests.

Installing avo and generating your first function

Installation is a single go get, and the README notes the module can be pinned with your package manager of choice, which is worth doing given the experimental-phase warning. The generator is a normal Go file, usually kept out of the build with a build constraint so it does not compile into the package it generates for.

bash
go get -u github.com/mmcloughlin/avo

The README's add example is the smallest complete program. It declares the signature, loads both parameters, adds them and stores the result. Run it with go run to see the assembly on stdout before wiring it into a package.

go
//go:build ignore

package main

import . "github.com/mmcloughlin/avo/build"

func main() {
	TEXT("Add", NOSPLIT, "func(x, y uint64) uint64")
	Doc("Add adds x and y.")
	x := Load(Param("x"), GP64())
	y := Load(Param("y"), GP64())
	ADDQ(x, y)
	Store(y, ReturnIndex(0))
	RET()
	Generate()
}

For a real package the README recommends a go:generate line so the .s file and the stub are regenerated together. The two flags used here are -out for the assembly file and -stubs for the Go declaration file.

go
//go:generate go run asm.go -out add.s -stubs stub.go

After go generate, add.s contains the TEXT block with MOVQ, ADDQ and RET, and stub.go contains the exported func Add declaration with the doc comment. The complete working version lives in examples/add.

What avo does not do for you

The README's own note is the first limitation: APIs are subject to change while avo is in an experimental phase, and the recommendation is to pin a version rather than track master. That is a real cost for a dependency that sits in your build, because a generator change can alter emitted assembly without any change to your source. Second, the generated file is Go assembly, not a portable representation. avo's description and its topics are x86-64, and the examples are x86 instruction mnemonics, so an ARM64 build of the same package still needs a separate path; avo will not produce it. Third, register allocation is a convenience, not an optimiser. The README describes assigning physical registers to virtual ones, and nothing in it claims avo schedules instructions or improves on hand-written allocation. If your hand-written function depends on a particular register assignment for a reason avo does not model, you are working against the tool. Finally, avo generates code; it does not verify it. The doc directory and the tests directory exist in the repository, but the README does not describe a correctness checker for the assembly you produce, so your own tests remain the only evidence that the generated function computes what you meant.

avo against hand-written .s files and Go intrinsics

The honest alternative for most people is to keep writing .s files by hand. The difference is where the knowledge lives. A hand-written file is a snapshot that a reader can compare against the Go declaration by counting offsets; an avo generator is a program whose output you have to regenerate and read to know what shipped. For a single short function that rarely changes, the hand-written file is less machinery. avo pays off when the function has loops, labels and several arguments, which is exactly the slice-sum shape in the README, because the offsets and labels are derived rather than typed. The other alternative is to stay in pure Go and let the compiler handle it, which is the right answer whenever the code is not a measured bottleneck, and it avoids the build step entirely. There is also the question of what avo is not: it is not an assembler and not a disassembler, and the README does not present it as a way to consume existing assembly. It only goes one direction, from Go source to .s and a stub.

Maintenance, versioning and the licence

The repository is not archived, and the last push was on 2026-09-15, so it is being touched. That is not the same as a stable API. The release history shows v0.6.0 in January 2024, v0.5.0 in November 2022 and v0.4.0 in November 2021, which is a slow cadence with long gaps, and the README's experimental warning sits above all of it. The go.mod declares go 1.25.0 and depends on golang.org/x/arch, golang.org/x/sys and golang.org/x/tools, so a Go toolchain upgrade can pull those forward and change what the generator emits. Budget for regeneration as part of your Go version bumps, not as a one-time setup. The licence is BSD-3-Clause, which is permissive and generally compatible with shipping generated files inside a larger program, but the generated .s and stub.go carry a generated-code header naming the command that produced them, so keep the generation command in the repository rather than editing the output. This is a description of the licence text, not legal advice; check BSD-3-Clause against your own distribution terms.

Editorial conclusion

Adopt avo if you maintain Go assembly and want the generator, the register allocation and the stub file to live in a Go program you can review in the same pull request as the call site. Do not adopt it if you only need a faster inner loop that the Go compiler already vectorises, or if your target is not x86-64, since the README describes it as generating x86 assembly. Before committing, run the add example end to end, check the generated .s against the exact output the README shows, and read doc/adopters.md to see whether a project with your build constraints has already taken this path.

Frequently asked questions

What is avo used for?

avo generates x86 assembly for Go packages from a Go program, with virtual registers, automatic argument and return-value handling, and an optional Go stub file. The README describes it as making high-performance Go assembly easier to write, review and maintain.

How do I install avo?

The README's quick start is go get -u github.com/mmcloughlin/avo. Because avo is described as experimental, the same section suggests pinning a version with your package manager of choice.

Does avo replace writing Go assembly by hand?

No. It generates Go assembly, so you still need to read the .s output and understand its offsets and instructions. The benefit is that the offsets and labels are derived from the declared signature instead of being typed by hand.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. mmcloughlin/avo on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mmcloughlin-avo.svg)](https://hysenlabs.com/projects/mmcloughlin-avo)