2026-08-01 05:22:00 +02:00
|
|
|
# gasm-devkit
|
|
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
Developer tooling for **GAsm**, Go's built-in Plan 9 assembler.
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
Go ships an assembler but no tooling for it: there is no syntax highlighting,
|
2026-08-01 05:22:00 +02:00
|
|
|
no autocomplete, no linter, no static analyser, no formatter, no standalone
|
|
|
|
|
assembler and no debugger for `.s` files. Developers write assembly blind,
|
2026-08-30 10:40:25 +02:00
|
|
|
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.
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
## Features
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
- **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,
|
2026-09-14 23:36:19 +02:00
|
|
|
operating recursively on directories the way `go fmt` does. `-l` lists
|
|
|
|
|
files whose formatting differs and `-d` prints a unified diff.
|
2026-08-30 21:31:05 +02:00
|
|
|
- **Linter.** `gasm lint` runs 18 conservative static checks, among them
|
2026-08-30 10:40:25 +02:00
|
|
|
`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`
|
2026-09-14 23:36:19 +02:00
|
|
|
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.
|
2026-08-30 10:40:25 +02:00
|
|
|
- **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,
|
2026-08-30 21:31:05 +02:00
|
|
|
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
|
2026-09-14 23:36:19 +02:00
|
|
|
links and folding ranges over stdio; definition, references and rename
|
|
|
|
|
work across every open document.
|
2026-08-30 10:40:25 +02:00
|
|
|
- **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.
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
### Architecture support
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
| 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 |
|
2026-08-01 05:22:00 +02:00
|
|
|
|
|
|
|
|
"Common opcodes" are the instructions shared by every architecture (`RET`,
|
2026-08-30 10:40:25 +02:00
|
|
|
`JMP`, `NOP`, `CALL`, `TEXT`, `FUNCDATA`, `PCDATA`, ...). AMD64 additionally
|
2026-08-01 05:22:00 +02:00
|
|
|
carries the traditional conditional-jump spellings (`JZ`, `JNZ`, `JA`, `JC`,
|
2026-08-30 10:40:25 +02:00
|
|
|
...) 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.
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
## Install
|
2026-08-07 22:20:26 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64 and
|
|
|
|
|
linux/loong64 are on the
|
|
|
|
|
[releases page](https://sourcedock.dev/petrbalvin/gasm-devkit/releases).
|
2026-09-17 20:33:18 +02:00
|
|
|
From source (Go 1.27.1):
|
2026-08-07 22:20:26 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
```sh
|
|
|
|
|
go install sourcedock.dev/petrbalvin/gasm-devkit/cmd/gasm@latest
|
|
|
|
|
```
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-09-16 22:53:01 +02:00
|
|
|
Or from a repository checkout:
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
```sh
|
2026-09-16 22:53:01 +02:00
|
|
|
just install
|
2026-08-30 10:40:25 +02:00
|
|
|
```
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-09-16 22:53:01 +02:00
|
|
|
The installed binary reports the version the toolchain recorded: the tag
|
|
|
|
|
on a tagged checkout, a pseudo-version naming the commit below one.
|
|
|
|
|
|
2026-08-01 05:22:00 +02:00
|
|
|
## Quick start
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-08-30 10:40:25 +02:00
|
|
|
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
|
2026-08-01 05:22:00 +02:00
|
|
|
```
|
|
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
## Usage
|
2026-08-01 05:22:00 +02:00
|
|
|
|
|
|
|
|
```sh
|
2026-08-30 10:40:25 +02:00
|
|
|
gasm fmt # reformat every .s below here, like go fmt
|
|
|
|
|
gasm fmt -w kernel_amd64.s # canonicalise one file in place
|
2026-09-14 23:36:19 +02:00
|
|
|
gasm fmt -l *.s # list files whose formatting differs
|
|
|
|
|
gasm fmt -d kernel_amd64.s # print a unified diff instead
|
2026-08-30 10:40:25 +02:00
|
|
|
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
|
2026-09-14 23:36:19 +02:00
|
|
|
gasm dis k.s # assemble, then list each function
|
|
|
|
|
gasm dis -a amd64 - < dump.bin # disassemble raw bytes from stdin
|
2026-08-30 10:40:25 +02:00
|
|
|
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
|
2026-08-29 14:24:52 +02:00
|
|
|
gasm debug --func name --script cmds.txt --timeout 30s k.s # headless run
|
2026-08-30 10:40:25 +02:00
|
|
|
gasm debug --func name --cover k.s # which labels did execution reach?
|
|
|
|
|
gasm diff a.s b.s # compare machine code byte-for-byte
|
2026-08-05 21:15:05 +02:00
|
|
|
gasm diff --map wideCopyAVX2=wideCopyAVX512 avx2.s avx512.s
|
2026-08-30 10:40:25 +02:00
|
|
|
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
|
2026-08-01 05:22:00 +02:00
|
|
|
```
|
|
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
Run `gasm --help` for the command overview and `gasm <command> -h` for a
|
|
|
|
|
command's flags. [docs/CLI.md](docs/CLI.md) is the full reference.
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
### Editor integration
|
2026-08-01 05:22:00 +02:00
|
|
|
|
|
|
|
|
`gasm lsp` speaks the Language Server Protocol over standard input/output, so
|
2026-08-30 10:40:25 +02:00
|
|
|
any LSP-capable editor can use it: point your editor's LSP client at the
|
2026-08-01 05:22:00 +02:00
|
|
|
binary and associate it with `.s` files. Syntax highlighting is delivered as
|
2026-08-30 10:40:25 +02:00
|
|
|
LSP semantic tokens, so no editor-specific grammar is required. The server
|
2026-08-01 05:22:00 +02:00
|
|
|
infers the target architecture from the file-name suffix
|
|
|
|
|
(`_amd64.s` / `_arm64.s` / `_riscv64.s` / `_loong64.s`).
|
|
|
|
|
|
2026-08-30 10:40:25 +02:00
|
|
|
## Development
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-09-16 22:53:01 +02:00
|
|
|
just build # compile, zero errors and zero warnings
|
|
|
|
|
just test # the suite, no cache, the 80 % coverage floor
|
|
|
|
|
just gates # build, fmt-check, vet, test, race: the definition of done
|
2026-08-30 10:40:25 +02:00
|
|
|
just fmt # gofmt the tree
|
|
|
|
|
just gen # regenerate the instruction tables from the Go toolchain
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow and
|
|
|
|
|
[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for setup details and every
|
|
|
|
|
recipe.
|
|
|
|
|
|
|
|
|
|
## Documentation
|
|
|
|
|
|
|
|
|
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow
|
|
|
|
|
- [docs/CLI.md](docs/CLI.md): full command reference
|
|
|
|
|
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md): development setup and recipes
|
|
|
|
|
- [CHANGELOG.md](CHANGELOG.md): release history
|
|
|
|
|
|
|
|
|
|
## Licence
|
|
|
|
|
|
2026-09-16 23:12:31 +02:00
|
|
|
BSD-3-Clause; see [LICENSE](LICENSE).
|
2026-08-01 05:22:00 +02:00
|
|
|
|
2026-08-20 15:01:57 +02:00
|
|
|
Copyright © 2026 [Petr Balvín](https://petrbalvin.org)
|