petrbalvin 3669f64ff6
Test / vet (push) Successful in 46s
Test / test (push) Successful in 2m44s
Test / build (push) Successful in 42s
build: install the gasm binary into the user-local bin directory
2026-09-14 23:41:21 +02:00
2026-09-14 23:36:19 +02:00
2026-09-14 23:36:19 +02:00
2026-09-14 23:36:19 +02:00
2026-09-14 23:36:19 +02:00

gasm-devkit

Developer tooling for GAsm, Go's built-in Plan 9 assembler.

Go ships an assembler but no tooling for it: there is no syntax highlighting, no autocomplete, no linter, no static analyser, no formatter, no standalone assembler and no debugger for .s files. Developers write assembly blind, validate it by benchmark, and debug it by print statement. gasm-devkit is the missing toolkit: a single, self-contained binary, gasm, that brings proper developer tooling to Plan 9 assembly on amd64, arm64, riscv64 and loong64.

Features

  • Front end. A hand-written lexer and an error-tolerant parser produce a typed AST with source positions; gasm tokens and gasm parse expose them directly.
  • Formatter. gasm fmt canonicalises indentation, operand spacing, per-function mnemonic alignment and blank-line layout: gofmt for assembly, operating recursively on directories the way go fmt does. -l lists files whose formatting differs and -d prints a unified diff.
  • Linter. gasm lint runs 18 conservative static checks, among them undefined-label, abi-argsize (declared frame vs the // func signature), register-clobber (Go ABI register liveness over the control-flow graph), stack-imbalance, abi0-register-args and unencodable-instruction.
  • Standalone assembler. gasm asm encodes all four architectures without the Go toolchain and writes raw images, linkable ELF objects (with DWARF5 debug sections) or the Go toolchain's own GOOBJ format, which go build consumes in place of the toolchain's output. Framed functions get the stack-split guard and the morestack block, byte-identical to the toolchain's, so split functions link too.
  • Disassembler. gasm dis lists a .s file's functions at their real offsets after assembling, or disassembles raw bytes from a file or stdin.
  • Dynamic verification. gasm verify JIT-loads assembled functions into executable memory: smoke calls, ABI checks (sentinel registers, red-zone canary), differential fuzzing against the go tool asm build, and byte-for-byte ground-truth comparison of the machine code.
  • Debugger. gasm debug is a source-level ptrace debugger with breakpoints (optionally conditional), hardware watchpoints, register and memory inspection, and headless script runs with label-level coverage.
  • Language server. gasm lsp serves completion, hover, document symbols, push and pull diagnostics, semantic-token highlighting, go-to-definition, find references, rename, formatting, inlay hints, code actions, signature help, document highlights, workspace symbol search, #include document links and folding ranges over stdio; definition, references and rename work across every open document.
  • Comparators and audits. gasm diff compares the machine code of two assembly files byte-for-byte, gasm profile shows basic-block structure, gasm audit-instructions diffs the encoder against the installed toolchain, and gasm scaffold generates a differential test skeleton for a kernel.
  • Complete instruction coverage. The instruction tables are generated from the Go toolchain's own assembler source, so the toolkit recognises every mnemonic the real assembler accepts; just gen refreshes them.

Architecture support

Architecture GOARCH File suffix Instructions recognised
AMD64 amd64 _amd64.s 1600 + common opcodes + traditional aliases
ARM64 arm64 _arm64.s 538 + common opcodes
RISC-V riscv64 _riscv64.s 961 + common opcodes
LoongArch loong64 _loong64.s 799 + common opcodes

"Common opcodes" are the instructions shared by every architecture (RET, JMP, NOP, CALL, TEXT, FUNCDATA, PCDATA, ...). AMD64 additionally carries the traditional conditional-jump spellings (JZ, JNZ, JA, JC, ...) that the assembler accepts as aliases. Regenerating the tables is one command (just gen) and requires only a Go installation; the committed output has no runtime dependency on the toolchain.

Install

Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64 and linux/loong64 are on the releases page. From source (Go 1.27 or later):

go install sourcedock.dev/petrbalvin/gasm-devkit/cmd/gasm@latest

Or from a repository checkout, with the development version stamped:

just install-bin

Quick start

cat > hello_amd64.s <<'EOF'
#include "textflag.h"

// func add(a, b int) int
TEXT ·add(SB), NOSPLIT, $0-24
	MOVQ a+0(FP), AX
	ADDQ b+8(FP), AX
	MOVQ AX, ret+16(FP)
	RET
EOF

gasm lint hello_amd64.s                         # static checks
gasm asm -o hello.bin hello_amd64.s             # assemble to a raw image
gasm verify --call add --args a=2,b=3 hello_amd64.s   # JIT-call it with arguments

Usage

gasm fmt                                # reformat every .s below here, like go fmt
gasm fmt    -w kernel_amd64.s           # canonicalise one file in place
gasm fmt    -l *.s                      # list files whose formatting differs
gasm fmt    -d kernel_amd64.s           # print a unified diff instead
gasm lint   *.s                         # static checks
gasm asm --format elf -o k.o k.s        # assemble to a linkable ELF object
gasm asm --format goobj -p pkg/path -o k.o k.s   # Go object, consumed by go build
gasm dis k.s                            # assemble, then list each function
gasm dis -a amd64 - < dump.bin          # disassemble raw bytes from stdin
gasm verify --ground-truth k.s          # byte-for-byte vs go tool asm
gasm verify --fuzz k.s                  # differential fuzz vs the go tool asm build
gasm debug --func name k.s              # interactive debugger
gasm debug --func name --script cmds.txt --timeout 30s k.s   # headless run
gasm debug --func name --cover k.s      # which labels did execution reach?
gasm diff a.s b.s                       # compare machine code byte-for-byte
gasm diff --map wideCopyAVX2=wideCopyAVX512 avx2.s avx512.s
gasm profile k.s                        # show basic-block structure
gasm audit-instructions                 # encoder vs go tool asm name diff
gasm scaffold differential k.s          # generate a differential test skeleton

Run gasm --help for the command overview and gasm <command> -h for a command's flags. docs/CLI.md is the full reference.

Editor integration

gasm lsp speaks the Language Server Protocol over standard input/output, so any LSP-capable editor can use it: point your editor's LSP client at the binary and associate it with .s files. Syntax highlighting is delivered as LSP semantic tokens, so no editor-specific grammar is required. The server infers the target architecture from the file-name suffix (_amd64.s / _arm64.s / _riscv64.s / _loong64.s).

Development

just install      # download module dependencies
just build        # go vet + gofmt check, zero errors and zero warnings
just test         # full suite, race detector, 80 % coverage gate
just fmt          # gofmt the tree
just gen          # regenerate the instruction tables from the Go toolchain

See CONTRIBUTING.md for the development workflow and docs/DEVELOPMENT.md for setup details and every recipe.

Documentation

Licence

BSD-3-Clause — see LICENSE.

Copyright © 2026 Petr Balvín

S
Description
A complete software development kit for the Go Plan 9 assembler: lexer, parser, linter, formatter, an assembler with AVX-512 and RISC-V support, a debugger, and an LSP server. Pure Go, no external toolchains.
Readme BSD-3-Clause
5.5 MiB
v0.35.0
Latest
2026-09-21 23:33:11 +00:00
Languages
Go 99%
Assembly 0.8%
Just 0.2%