2026-08-01 05:22:00 +02:00
# Development Guide
2026-09-26 11:08:43 +02:00
Repository: [sourcedock.dev/petrbalvin/gasm-sdk ](https://sourcedock.dev/petrbalvin/gasm-sdk )
2026-08-01 05:22:00 +02:00
## Prerequisites
2026-09-17 20:33:18 +02:00
- **Go** 1.27.1, the exact version the `go` directive in `go.mod` declares
2026-08-30 10:40:25 +02:00
- **just**, the command runner; every task below is a just recipe
2026-09-20 01:40:51 +02:00
- **A C compiler** (`gcc` ): `just race` runs the suite under the race detector,
which needs cgo
- **Perl**: the `test` , `fmt-check` , `install-man` and `uninstall-man` recipes
are Perl programs
- **`gzip` **: `install-man` compresses the man pages with it
2026-09-17 20:33:18 +02:00
- A Linux host on amd64, arm64, riscv64 or loong64: `gasm debug` needs ptrace
and the JIT checks of `gasm verify` need executable memory
2026-09-20 01:40:51 +02:00
- **`golang.org/x/arch` **, the one module dependency, which the Go toolchain
fetches; nothing else sits outside the standard library
2026-08-01 05:22:00 +02:00
2026-09-17 20:33:18 +02:00
## Setup
2026-08-01 05:22:00 +02:00
```sh
2026-09-26 11:08:43 +02:00
git clone https://sourcedock.dev/petrbalvin/gasm-sdk.git
cd gasm-sdk
2026-09-16 22:53:01 +02:00
just build # compile bin/gasm, zero errors and zero warnings
just gates # build, fmt-check, vet, test, race: the definition of done
2026-08-01 05:22:00 +02:00
```
2026-09-17 20:33:18 +02:00
## Recipes
2026-08-01 05:22:00 +02:00
2026-09-17 20:33:18 +02:00
Every recipe in the `justfile` , and what it does.
2026-08-01 05:22:00 +02:00
2026-09-17 20:33:18 +02:00
| Recipe | What it does |
|---|---|
2026-09-20 01:40:51 +02:00
| `default` (bare `just` ) | prints the recipe list (`@just --list` ) |
2026-09-17 20:33:18 +02:00
| `just build` | compiles `bin/gasm` with `CGO_ENABLED=0` and stripped symbols; zero errors and zero warnings |
2026-09-20 01:40:51 +02:00
| `just test` | the test gate: the suite with `-count=1` , the coverage profile and the 80 % floor, then the CLI and debugger tests outside the profile |
2026-09-17 20:33:18 +02:00
| `just race` | the same suite under the race detector; the expensive one, so it runs once, inside `gates` |
| `just unit [packages] [run]` | fast, cached, scoped run for iterating: no race and no coverage, so an unchanged package reports instantly |
| `just fuzz <target> <pkg> [fuzztime]` | time-boxed fuzz of one target; the package is required, because `go test -fuzz` refuses more than one |
| `just bench [packages]` | benchmarks (`-benchmem -count=5` ); on an idle machine only |
| `just fmt` | formats the tree in place with `gofmt` |
| `just fmt-check` | zero diff; prints nothing when everything is formatted, which is the shape the CI step wants |
| `just vet` | both static gates: `go vet` and `go fix -diff` |
| `just gates` | `build` , `fmt-check` , `vet` , `test` and `race` , in that order: the definition of done |
| `just clean` | removes the build artefacts, `bin/` and `coverage.out` |
2026-09-20 01:40:51 +02:00
| `just install` | builds, then copies the binary into `bindir` (`~/.local/bin` ) |
2026-09-17 20:33:18 +02:00
| `just uninstall` | removes the installed binary from `bindir` |
2026-09-19 23:49:27 +02:00
| `just install-man` | installs the man pages under `docs/man` into `~/.local/share/man/man1` (`MANDIR` overrides), gzip-compressed; not a gate |
| `just uninstall-man` | removes the installed man pages |
2026-09-17 20:33:18 +02:00
| `just run` | runs the CLI with `go run -buildvcs=true` ; the recipe takes no arguments, so flags go through the package instead |
| `just dev` | the same as `run` ; the project has no watcher to add |
| `just gen` | regenerates the `arch` instruction tables from the Go toolchain source; not a gate |
2026-08-01 05:22:00 +02:00
### `just test`
```sh
2026-09-17 20:33:18 +02:00
go test -count= 1 -timeout 10m -coverprofile= coverage.out \
./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... \
./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
2026-08-01 05:22:00 +02:00
```
2026-09-17 20:33:18 +02:00
The suite runs over the logic packages (`-count=1` , so no cached pass
counts): arch, asm, ast, disasm, format, lexer, lint, lsp, parser,
token, verify. `debug` traces a live process and `cmd/gasm` is thin CLI
2026-09-20 01:40:51 +02:00
glue, so both sit outside the profile sweep, and a thin `cmd/` in it
would drag the coverage total under the floor. Their tests still run, in
a second invocation without a profile:
```sh
go test -count= 1 -timeout 10m ./cmd/... ./debug/...
```
That covers the CLI's exit codes and the guard that compares the manual
pages with the binary's own help, and the debugger's architecture-neutral
units. The floor fails if the total is below 80 %. CI runs the same two
commands with the same ten-minute bound, so
2026-09-17 20:33:18 +02:00
the number is the same everywhere.
2026-08-01 05:22:00 +02:00
2026-09-17 20:33:18 +02:00
### `just run`
2026-08-01 05:22:00 +02:00
```sh
2026-09-17 20:33:18 +02:00
just run
go run -buildvcs= true ./cmd/gasm lint kernel_amd64.s
go run -buildvcs= true ./cmd/gasm verify --ground-truth kernel_amd64.s
2026-08-01 05:22:00 +02:00
```
2026-09-17 20:33:18 +02:00
The flag on `go run` is there because it does not stamp the build otherwise,
which `--version` would then report as `(devel)` .
2026-08-01 05:22:00 +02:00
### `just gen`
2026-09-17 20:33:18 +02:00
Regenerates the architecture instruction tables in `arch/` by parsing the Go
toolchain's own assembler source
2026-08-01 05:22:00 +02:00
(`$GOROOT/src/cmd/internal/obj/<arch>/anames.go` ). Requires a Go
2026-08-30 10:40:25 +02:00
installation. Output is committed, with no runtime dependency on the
2026-08-01 05:22:00 +02:00
toolchain.
2026-09-17 20:33:18 +02:00
## Running a single test
2026-08-01 05:22:00 +02:00
```sh
go test -run TestVexGroundTruth ./asm/
2026-08-30 10:40:25 +02:00
go test -run TestGroundTruthBasic ./verify/
2026-08-01 05:22:00 +02:00
go test -run TestGOObjectLinkAndRun ./asm/
2026-08-30 10:40:25 +02:00
go test -run TestFuzzWideCopy ./verify/
2026-08-01 05:22:00 +02:00
```
2026-09-17 20:33:18 +02:00
Add `-v` for the sub-test names, and `-race` when the change touches
concurrency. `-count=1` defeats the test cache when a result looks stale.
2026-08-01 05:22:00 +02:00
2026-09-17 20:33:18 +02:00
## Coverage
2026-08-01 05:22:00 +02:00
```sh
2026-09-17 20:33:18 +02:00
just test
go tool cover -func= coverage.out
2026-08-01 05:22:00 +02:00
```
2026-09-17 20:33:18 +02:00
The `total:` line is the number that matters, and it stays at 80 percent or
more.
2026-08-01 05:22:00 +02:00
2026-09-17 20:33:18 +02:00
## Debugging the build
```sh
go build -gcflags= '-m' ./... # inlining decisions
go build -gcflags= '-S' ./... # what the compiler generated
go tool asm -S kernel_amd64.s # how the toolchain's assembler encodes a kernel
gasm dis kernel_amd64.s # what gasm makes of the same kernel
gasm tokens kernel_amd64.s # the token stream
gasm profile kernel_amd64.s # the basic blocks of each function
2026-08-01 05:22:00 +02:00
```
2026-09-17 20:33:18 +02:00
`gasm verify --ground-truth` is the differential check that ties the two
together: it compares gasm's bytes with `go tool asm` 's, with the relocation
sites masked, so an encoding drift shows up as a byte difference rather than a
crash later.
## Continuous integration
Workflows live in `.gitea/workflows/` and run on the project's own runners:
Test on a push or pull request to `development` , race dispatched by hand, and
the release on a `v*` tag. They are written by hand rather than through
`just` , but they enforce the same set of gates minus the race detector, which
the shared runner cannot afford on a push; a green `just gates` locally is
therefore the fastest way to a green pipeline.
## Releases
Releases are cut by merging `development` into `main` and tagging `vX.Y.Z` ,
which triggers the release workflow: it builds the portable Linux targets,
takes the notes from the matching `CHANGELOG.md` section and uploads the
2026-09-20 01:40:51 +02:00
assets. `SECURITY.md` carries the supported-versions table, so that table
moves with the release; the pipeline refuses a tag the policy does not name.
2026-09-17 20:33:18 +02:00
The version is never injected. `gasm --version` prints what the
toolchain recorded in the build information: the tag on a tagged
checkout, a pseudo-version naming the commit below one, `+dirty` on a
dirty tree, and `(devel)` outside version control. There is no
`-ldflags "-X"` anywhere and no version constant in the source.