174 lines
8.1 KiB
Markdown
174 lines
8.1 KiB
Markdown
# Development Guide
|
|
|
|
Repository: [sourcedock.dev/petrbalvin/gasm-sdk](https://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
|
|
|
|
```sh
|
|
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`
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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`
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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
|
|
|
|
```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
|
|
```
|
|
|
|
`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.
|