Files
gasm-sdk/docs/DEVELOPMENT.md
T
petrbalvin ae1baa0e61
Test / test (push) Successful in 1m45s
ci: run the affordable gate set on push and publish at the tag
Assisted-by: DeepSeek V4.1 Flash
2026-10-04 21:17:40 +02:00

8.1 KiB

Development Guide

Repository: sourcedock.dev/petrbalvin/gasm-sdk

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-sdk.git
cd gasm-sdk
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, -trimpath and -buildvcs=true, symbols stripped; zero errors and zero warnings
just test the test gate: the full suite with -count=1 and -timeout 0 under the 4 GiB memory fence, 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, under its own 16 GiB fence; the expensive one, so it runs once, inside gates
just unit [packages] [run] fast, cached, scoped run for iterating, under the fence: no race and no coverage, so an unchanged package reports instantly
just fuzz <target> <pkg> [fuzztime] time-boxed fuzz of one target, under the fence; the package is required, because go test -fuzz refuses more than one
just link-parity the opt-in cmd/link GOOBJ parity gate; it costs minutes no pipeline can afford
just bench [packages] benchmarks (-benchmem -count=5); deliberately unfenced, 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 0 -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 0 ./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 units. The floor fails if the total is below 80 %. Both invocations run under a 4 GiB cgroup fence with swap off, so a runaway test dies as a failed run instead of eating the machine, and no timeout is set: -timeout 0 disables the ten-minute default go test would otherwise impose. The push pipeline runs the same two commands with -short, the suite workflow and the local gate run them in full, 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:

Workflow Trigger What it does
Test push or pull request to development the format check, go vet, the short layer of the suite with the coverage profile and the 80 % floor, inside the two-minute budget
Suite dispatched by hand the complete gate set minus race: the build, the format check, go vet, go fix -diff, the full suite and the coverage floor
FreeBSD build dispatched by hand the three FreeBSD compile gates: GOOS=freebsd for amd64, arm64 and riscv64
Release a v* tag the matrix build, the version smoke test, and the release with its assets; no gate runs at the tag

The pipelines are written by hand rather than through just, but they enforce the same gates: the push path carries the affordable subset and the heavy tests skip under testing.Short, so the dispatched suite is the moment to prove the tree end to end. Race never runs in CI, on a push or a tag: it roughly doubles the time and the memory on a shared runner, and the local just gates races the tree on the machine at the keyboard before the tag is cut.

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 (amd64, arm64, loong64, riscv64), takes the notes from the matching CHANGELOG.md section and uploads the assets. No gate runs at the tag, so just gates must be green on the tree before the tag is cut. SECURITY.md carries the supported-versions table, naming the version being released; the release commit updates it by hand.

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.