Files
gasm-sdk/docs/DEVELOPMENT.md
T
2026-09-20 01:40:51 +02:00

6.7 KiB

Development Guide

Repository: sourcedock.dev/petrbalvin/gasm-devkit

Prerequisites

  • Go 1.27.1, the exact version the go directive in go.mod declares
  • just, the command runner; every task below is a just recipe
  • 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
  • A Linux host on amd64, arm64, riscv64 or loong64: gasm debug needs ptrace and the JIT checks of gasm verify need executable memory
  • golang.org/x/arch, the one module dependency, which the Go toolchain fetches; nothing else sits outside the standard library

Setup

git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
cd gasm-devkit
just build     # compile bin/gasm, zero errors and zero warnings
just gates     # build, fmt-check, vet, test, race: the definition of done

Recipes

Every recipe in the justfile, and what it does.

Recipe What it does
default (bare just) prints the recipe list (@just --list)
just build compiles bin/gasm with CGO_ENABLED=0 and stripped symbols; zero errors and zero warnings
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
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
just install builds, then copies the binary into bindir (~/.local/bin)
just uninstall removes the installed binary from bindir
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
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

just test

go test -count=1 -timeout 10m -coverprofile=coverage.out \
    ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... \
    ./lint/... ./lsp/... ./parser/... ./token/... ./verify/...

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 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:

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 the number is the same everywhere.

just run

just run
go run -buildvcs=true ./cmd/gasm lint kernel_amd64.s
go run -buildvcs=true ./cmd/gasm verify --ground-truth kernel_amd64.s

The flag on go run is there because it does not stamp the build otherwise, which --version would then report as (devel).

just gen

Regenerates the architecture instruction tables in arch/ by parsing the Go toolchain's own assembler source ($GOROOT/src/cmd/internal/obj/<arch>/anames.go). Requires a Go installation. Output is committed, with no runtime dependency on the toolchain.

Running a single test

go test -run TestVexGroundTruth ./asm/
go test -run TestGroundTruthBasic ./verify/
go test -run TestGOObjectLinkAndRun ./asm/
go test -run TestFuzzWideCopy ./verify/

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.

Coverage

just test
go tool cover -func=coverage.out

The total: line is the number that matters, and it stays at 80 percent or more.

Debugging the build

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

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 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.

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.