docs: sync README, CHANGELOG and docs with the current state
This commit is contained in:
+68
-2
@@ -15,6 +15,14 @@ Unreleased changes on the `development` branch.
|
|||||||
and loong64 in addition to amd64. Each architecture has its own ptrace
|
and loong64 in addition to amd64. Each architecture has its own ptrace
|
||||||
register access, disassembler (`golang.org/x/arch`), register display,
|
register access, disassembler (`golang.org/x/arch`), register display,
|
||||||
and stop-info handler. The REPL is fully arch-neutral.
|
and stop-info handler. The REPL is fully arch-neutral.
|
||||||
|
- **Headless debugging.** `gasm debug --script` runs REPL commands from a
|
||||||
|
file (or stdin) and exits; `--timeout` kills the debuggee when a run
|
||||||
|
hangs, with the watchdog armed before the ptrace attach. `--cover` runs
|
||||||
|
to completion with a breakpoint on every label and reports which blocks
|
||||||
|
executed: label-level coverage for headless test runs.
|
||||||
|
- **Conditional breakpoints.** `break <label> if <reg> <op> <val>` now also
|
||||||
|
compares two registers (`break loop if RAX > RBX`), not only a register
|
||||||
|
against an immediate.
|
||||||
- **JIT execution trampolines.** `verify.Call` now works on all four
|
- **JIT execution trampolines.** `verify.Call` now works on all four
|
||||||
architectures via hand-written assembly trampolines
|
architectures via hand-written assembly trampolines
|
||||||
(`trampoline_{arm64,riscv64,loong64}.s`) that save the Go stack, switch
|
(`trampoline_{arm64,riscv64,loong64}.s`) that save the Go stack, switch
|
||||||
@@ -22,21 +30,67 @@ Unreleased changes on the `development` branch.
|
|||||||
- **Hardware watchpoints on all architectures.** arm64 uses DBGWVR/DBGWCR
|
- **Hardware watchpoints on all architectures.** arm64 uses DBGWVR/DBGWCR
|
||||||
via `PTRACE_SETREGSET` with `NT_ARM_HW_BREAK`; riscv64 and loong64 use
|
via `PTRACE_SETREGSET` with `NT_ARM_HW_BREAK`; riscv64 and loong64 use
|
||||||
`PTRACE_POKEUSER` to access trigger/debug registers.
|
`PTRACE_POKEUSER` to access trigger/debug registers.
|
||||||
|
- **`gasm verify --args`.** Scalar arguments (`name=value`, decimal or
|
||||||
|
`0x` hex) can now be supplied to a `--call` invocation alongside `--buf`
|
||||||
|
buffers, closing the gap where only buffers could be supplied.
|
||||||
|
- **`gasm audit-instructions`.** Black-box diff of the amd64 encoder
|
||||||
|
against the installed `go tool asm`: superset encodings (gasm-only,
|
||||||
|
shippable via `gasm asm --format goobj`), known-but-unencodable names
|
||||||
|
(the backlog) and go-only names (feature gaps).
|
||||||
|
- **`gasm scaffold differential`.** Prints a differential test skeleton
|
||||||
|
for every `// func` signature in a kernel file: random seed states, the
|
||||||
|
kernel call and a portable reference (`<name>Portable`), compared
|
||||||
|
byte-for-byte.
|
||||||
- **LSP: find references** (`textDocument/references`).
|
- **LSP: find references** (`textDocument/references`).
|
||||||
- **LSP: rename symbol** (`textDocument/rename`).
|
- **LSP: rename symbol** (`textDocument/rename`).
|
||||||
- **LSP: document formatting** (`textDocument/formatting`) using the
|
- **LSP: document formatting** (`textDocument/formatting`) using the
|
||||||
`format` package for canonical gofmt-style output.
|
`format` package for canonical gofmt-style output.
|
||||||
- **LSP: inlay hints** (`textDocument/inlayHint`) — frame size hints after
|
- **LSP: inlay hints** (`textDocument/inlayHint`): frame size hints after
|
||||||
the TEXT directive's argument area.
|
the TEXT directive's argument area.
|
||||||
|
- **LSP: workspace symbol search** (`workspace/symbol`): substring search
|
||||||
|
over the TEXT functions and GLOBL/DATA symbols of every open document.
|
||||||
|
- **LSP: code actions.** Quick fixes for `missing-ret` (insert the RET)
|
||||||
|
and `unused-label` (remove the label) diagnostics.
|
||||||
|
- **LSP: signature help** (`textDocument/signatureHelp`): the callee's
|
||||||
|
`// func` signature while the cursor is on a `CALL`.
|
||||||
|
- **LSP: document highlights**: every reference to the function or label
|
||||||
|
under the cursor is highlighted.
|
||||||
- **Lint: `unused-label` rule.** Flags labels that are defined but never
|
- **Lint: `unused-label` rule.** Flags labels that are defined but never
|
||||||
referenced by any jump (Hint severity).
|
referenced by any jump (Hint severity).
|
||||||
- **Lint: `invalid-textflag` rule.** Flags TEXT/GLOBL flags not in the
|
- **Lint: `invalid-textflag` rule.** Flags TEXT/GLOBL flags not in the
|
||||||
known set from `textflag.h` (Warning severity).
|
known set from `textflag.h` (Warning severity).
|
||||||
- **Lint: `stack-imbalance` rule.** Tracks SP changes and flags if the
|
- **Lint: `stack-imbalance` rule.** Tracks SP changes and flags if the
|
||||||
net delta at RET does not match the declared frame size.
|
net delta at RET does not match the declared frame size.
|
||||||
|
- **Lint: `register-width-mismatch` rule.** Flags amd64 operands whose
|
||||||
|
register width does not match the width the mnemonic suffix prescribes
|
||||||
|
(for example a 32-bit register in a `MOVQ`).
|
||||||
|
- **Lint: `abi0-register-args` rule.** Flags kernels whose `// func`
|
||||||
|
parameters are never read from the FP frame (for example arguments read
|
||||||
|
from registers instead), which pass every test today and break on a
|
||||||
|
toolchain upgrade. Shipped with the Go assembler's operand-width model.
|
||||||
|
- **Lint: `nonportable-register-name` rule.** Flags the `RAX`/`EAX`-style
|
||||||
|
register aliases gasm accepts but `go tool asm` rejects, so files using
|
||||||
|
them only link through the gasm GOOBJ path.
|
||||||
|
- **Lint: `unencodable-instruction` rule.** Flags mnemonics the
|
||||||
|
architecture table knows but the encoder cannot yet emit, at edit time
|
||||||
|
instead of at assembly time.
|
||||||
- **DWARF5 debug sections in ELF output.** All four ELF emitters now emit
|
- **DWARF5 debug sections in ELF output.** All four ELF emitters now emit
|
||||||
`.debug_abbrev`, `.debug_info`, `.debug_line`, and `.debug_line_str`
|
`.debug_abbrev`, `.debug_info`, `.debug_line`, and `.debug_line_str`
|
||||||
sections, enabling `addr2line` and GDB/LLDB source-level debugging.
|
sections, enabling `addr2line` and GDB/LLDB source-level debugging, and
|
||||||
|
the amd64 emitter adds a `.debug_frame` CFI section for stack unwinding.
|
||||||
|
- **amd64: legacy SSE and conversion coverage.** The encoder now handles
|
||||||
|
the legacy (non-VEX) SSE packed binaries and immediate shuffles, the
|
||||||
|
legacy SSE integer instructions and `BSWAP`, the scalar and packed
|
||||||
|
double conversions (`CVTSS2SD`, `CVTSD2SS`, `CVTPS2PD`, `CVTPD2PS`) and
|
||||||
|
the prefetch hints.
|
||||||
|
- **amd64: `VPCMP` and the full opmask set.** The EVEX compare with an
|
||||||
|
opmask destination and the remaining opmask-register instructions are
|
||||||
|
encoded, byte for byte against the Go assembler.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Parallel verify sweeps.** The `-smoke` and `-abi` per-function checks
|
||||||
|
now run in parallel instead of sequentially.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -48,6 +102,18 @@ Unreleased changes on the `development` branch.
|
|||||||
`AssembleFileLOONG64`, and `AssembleFileARM64` now mark external
|
`AssembleFileLOONG64`, and `AssembleFileARM64` now mark external
|
||||||
relocations and populate `img.Externals`, enabling cross-package symbol
|
relocations and populate `img.Externals`, enabling cross-package symbol
|
||||||
resolution in GOOBJ output.
|
resolution in GOOBJ output.
|
||||||
|
- **Subprocess-isolated smoke and ABI sweeps.** A function that faults
|
||||||
|
during the `-smoke` or `-abi` sweep is reported without killing the
|
||||||
|
parent; each sweep runs in a child process.
|
||||||
|
- **`BSF`, `BSR` and `POPCNT` encodings.** Corrected to the bytes
|
||||||
|
`go tool asm` emits.
|
||||||
|
- **`MOVQ` immediates.** Compressed to the toolchain's imm32 forms.
|
||||||
|
- **Frame adjustments of 128 to 255 bytes.** Now use the imm8 `ADDQ`
|
||||||
|
stack adjustment.
|
||||||
|
- **amd64 encoding parity.** A broad pass aligned the remaining encoder
|
||||||
|
outputs and operand strictness with `go tool asm`.
|
||||||
|
- **`gasm scaffold` parameter names.** Shared parameter names and
|
||||||
|
two-sided seed sets in the generated skeleton are now correct.
|
||||||
- **asm help text.** Updated to list arm64 as a supported architecture.
|
- **asm help text.** Updated to list arm64 as a supported architecture.
|
||||||
|
|
||||||
## [0.31.1] — 2026-08-20
|
## [0.31.1] — 2026-08-20
|
||||||
|
|||||||
+77
-71
@@ -1,101 +1,107 @@
|
|||||||
# Contributing to gasm-devkit
|
# Contributing to gasm-devkit
|
||||||
|
|
||||||
## Prerequisites
|
Thanks for contributing to gasm-devkit.
|
||||||
|
|
||||||
- Go 1.27 or later (`toolchain go1.27.0`)
|
## Development setup
|
||||||
- `just` command runner
|
|
||||||
- A Linux host on amd64, arm64, riscv64 or loong64
|
|
||||||
|
|
||||||
## Development Setup
|
Requirements: Go 1.27 or later, the [just](https://github.com/casey/just)
|
||||||
|
command runner, and a Linux host on amd64, arm64, riscv64 or loong64.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
|
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
|
||||||
cd gasm-devkit
|
cd gasm-devkit
|
||||||
just install # download module dependencies
|
just install # download module dependencies
|
||||||
just build # go vet + gofmt check
|
just build # go vet + gofmt check
|
||||||
just test # full test suite with race detector
|
just test # full suite, race detector, 80 % coverage gate
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Workflow
|
||||||
|
|
||||||
Every just recipe:
|
1. Branch from `development`; never commit directly to `main` (`main` is
|
||||||
|
release-only: merge from `development`, then tag).
|
||||||
|
2. Commit with [Conventional Commits](https://www.conventionalcommits.org/):
|
||||||
|
`type(scope): description`: subject line only, imperative mood,
|
||||||
|
lowercase after the colon, no trailing dot. Allowed types: `feat`,
|
||||||
|
`fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`,
|
||||||
|
`build`, `revert`. The only line after the subject is the trailer:
|
||||||
|
`Assisted-by: <model-name>`. No `Co-Authored-By`, no `Signed-off-by`,
|
||||||
|
no other trailers.
|
||||||
|
3. Record every user-visible change in `CHANGELOG.md` under
|
||||||
|
`## [development]` (categories: Added, Changed, Fixed, Removed,
|
||||||
|
Security).
|
||||||
|
4. Add or update tests; coverage must stay **at or above 80 %** (hard
|
||||||
|
gate, enforced by CI).
|
||||||
|
5. Update the documentation when behaviour, flags or the public surface
|
||||||
|
change.
|
||||||
|
6. Open a pull request against `development`.
|
||||||
|
|
||||||
| Recipe | What it does |
|
Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`;
|
||||||
|--------|-------------|
|
CI builds and publishes the binaries for all four architectures.
|
||||||
| `just` | List all recipes |
|
|
||||||
| `just install` | `go mod download` |
|
|
||||||
| `just build` | `go vet ./...` + `gofmt -l .` check — zero errors required |
|
|
||||||
| `just test` | `go test -race -count=1 -coverprofile=coverage.out ./...` + 80 % coverage gate |
|
|
||||||
| `just fmt` | `gofmt -w .` |
|
|
||||||
| `just run -- lint file.s` | Run the CLI with `go run` (args after `--`) |
|
|
||||||
| `just install-bin` | Install `gasm` into `$GOBIN` with the release version stamped |
|
|
||||||
| `just gen` | Regenerate `arch/*_gen.go` instruction tables from the Go toolchain |
|
|
||||||
| `just uninstall` | Remove build artefacts (`coverage.out`, `gasm`, `*.test`) |
|
|
||||||
|
|
||||||
## Running a Single Test
|
## Code style
|
||||||
|
|
||||||
|
`gofmt` and `go vet` via `just fmt` / `just build`; both must pass with
|
||||||
|
zero output; `go fix -diff ./...` must report nothing on touched packages.
|
||||||
|
|
||||||
|
- Standard library only in production code; `golang.org/x/arch` is used
|
||||||
|
in tests only (round-trip decoding) and is never linked into the `gasm`
|
||||||
|
binary.
|
||||||
|
- No cgo, no C, no external toolchains at runtime.
|
||||||
|
- Explicit `if err != nil`; errors wrapped with
|
||||||
|
`fmt.Errorf("context: %w", err)`; no panics outside `main`.
|
||||||
|
- The parser, lexer and formatter are hand-written; the `arch` instruction
|
||||||
|
tables are generated only via `_gen/gen.go` (`just gen`), never edited.
|
||||||
|
|
||||||
|
## Running a single test
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go test -run TestVexGroundTruth ./asm/
|
go test -run TestVexGroundTruth ./asm/
|
||||||
go test -run TestDifferentialLZ4Fuzz ./verify/
|
go test -run TestGroundTruthBasic ./verify/
|
||||||
|
go test -run TestGOObjectLinkAndRun ./asm/
|
||||||
|
go test -run TestFuzzWideCopy ./verify/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing the Debugger
|
The interactive debugger (`gasm debug`) requires a compiled binary on
|
||||||
|
`$PATH`; `go run` does not work for the traced child process. Install
|
||||||
|
first with `just install-bin`.
|
||||||
|
|
||||||
The interactive debugger (`gasm debug`) requires a compiled binary —
|
## CI (Gitea Actions)
|
||||||
`go run` does not work for the child process. Install first:
|
|
||||||
|
|
||||||
```sh
|
Workflows live in `.gitea/workflows/` and run on self-hosted runners:
|
||||||
just install-bin
|
|
||||||
gasm debug --func add testdata/verify/basic_amd64.s
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code Style
|
| Workflow | Trigger | What it does |
|
||||||
|
|----------|---------|--------------|
|
||||||
|
| Test | push / PR to `development` | gofmt check, `go vet`, `go test -race`, 80 % coverage gate |
|
||||||
|
| Release | tag `v*` | cross-compiles binaries for linux/{amd64,arm64,riscv64,loong64} and publishes the Gitea release |
|
||||||
|
|
||||||
See [AGENTS.md](AGENTS.md) for the full style guide. Key points:
|
The Definition of Done (`just build` + `just test` + `just fmt`) must
|
||||||
|
still pass locally before pushing.
|
||||||
|
|
||||||
- `gofmt` — zero diff.
|
## AI Contribution Policy
|
||||||
- `go vet` — zero warnings.
|
|
||||||
- Standard library only in production code; `golang.org/x/arch` in tests.
|
|
||||||
- No cgo, no C, no JavaScript.
|
|
||||||
- Hand-written Plan 9 assembly; tables generated only via `_gen/gen.go`.
|
|
||||||
|
|
||||||
## Branches and Releases
|
AI tools are welcome as productivity aids. What matters is that
|
||||||
|
contributions remain understandable, reviewable, and genuinely useful.
|
||||||
|
|
||||||
- `development` is the working branch.
|
- **Disclose AI use.** If you used AI to draft or generate any part of a
|
||||||
- `main` is release-only: `git merge --ff-only development`, then `git tag vX.Y.Z`.
|
commit, issue, pull request, or code review, say so clearly.
|
||||||
- Conventional Commits: `feat(asm): add EVEX gather and scatter`.
|
- **Commit messages:** end every commit with exactly one trailer:
|
||||||
- Every commit ends with `Assisted-by: <model-name>`.
|
`Assisted-by: <model-name>` (e.g. `Assisted-by: GLM 5.3`).
|
||||||
|
- **Pull requests and issues:** attribute AI assistance in one trailing
|
||||||
|
line, e.g. `_Assisted-by: GLM 5.3_`. Do not paste it into the PR
|
||||||
|
description as a section.
|
||||||
|
- **Take responsibility.** You remain accountable for the accuracy,
|
||||||
|
completeness, and intent of everything you submit.
|
||||||
|
- **Review before marking ready.** Read AI-generated diffs carefully, run
|
||||||
|
them locally, and add or update tests where appropriate.
|
||||||
|
- **Preferred models.** Prefer open-weight models with transparent
|
||||||
|
training data: **GLM**, **DeepSeek**, and **MiMo**.
|
||||||
|
|
||||||
## CI
|
## Reporting bugs
|
||||||
|
|
||||||
CI runs on every push to `development` and on pull requests:
|
|
||||||
|
|
||||||
- **Test** (`test.yml`) — `gofmt` check, `go vet`, `go test -race` and the
|
|
||||||
80 % coverage gate.
|
|
||||||
- **Release** (`release.yml`) — cross-compiles release binaries for
|
|
||||||
linux/{amd64,arm64,riscv64,loong64} on version tags and publishes them.
|
|
||||||
|
|
||||||
The Definition of Done (`just build` + `just test` + `just fmt`) must still
|
|
||||||
pass locally before pushing.
|
|
||||||
|
|
||||||
## AI-Assisted Contributions
|
|
||||||
|
|
||||||
AI agents may assist with code, documentation, tests, and review. All
|
|
||||||
AI-assisted changes must:
|
|
||||||
|
|
||||||
- Include the trailer `Assisted-by: <model-name>` in the commit message
|
|
||||||
(e.g. `Assisted-by: DeepSeek V4 Pro`).
|
|
||||||
- Follow the [AGENTS.md](AGENTS.md) rules.
|
|
||||||
- Pass the Definition of Done before committing.
|
|
||||||
|
|
||||||
Attribute agent authorship in issues and pull requests on one trailing
|
|
||||||
line:
|
|
||||||
|
|
||||||
```
|
|
||||||
_Assisted-by: Qwen 3.8 Max_
|
|
||||||
```
|
|
||||||
|
|
||||||
## Questions
|
|
||||||
|
|
||||||
Open an issue at
|
Open an issue at
|
||||||
[sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrbalvin/gasm-devkit/issues).
|
[sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrbalvin/gasm-devkit/issues)
|
||||||
|
with the version (`gasm --version`), OS and architecture, the exact
|
||||||
|
command, the full output, and the expected versus actual behaviour.
|
||||||
|
|
||||||
|
**Security issues:** email **opensource@petrbalvin.org** instead of opening
|
||||||
|
a public issue.
|
||||||
|
|||||||
@@ -1,149 +1,157 @@
|
|||||||
# gasm-devkit
|
# gasm-devkit
|
||||||
|
|
||||||
Developer tooling for **GAsm** — Go's built-in Plan 9 assembler.
|
Developer tooling for **GAsm**, Go's built-in Plan 9 assembler.
|
||||||
|
|
||||||
Go ships an assembler but no tooling for it. There is no syntax highlighting,
|
Go ships an assembler but no tooling for it: there is no syntax highlighting,
|
||||||
no autocomplete, no linter, no static analyser, no formatter, no standalone
|
no autocomplete, no linter, no static analyser, no formatter, no standalone
|
||||||
assembler and no debugger for `.s` files. Developers write assembly blind,
|
assembler and no debugger for `.s` files. Developers write assembly blind,
|
||||||
validate it by benchmark, and debug it by print statement.
|
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.
|
||||||
|
|
||||||
gasm-devkit is the missing toolkit. It is a single, self-contained binary —
|
## Features
|
||||||
`gasm` — that brings proper developer tooling to Plan 9 assembly:
|
|
||||||
|
|
||||||
```
|
- **Front end.** A hand-written lexer and an error-tolerant parser produce a
|
||||||
gasm tokens dump the lexical token stream
|
typed AST with source positions; `gasm tokens` and `gasm parse` expose them
|
||||||
gasm parse parse and report syntax errors
|
directly.
|
||||||
gasm fmt canonicalise formatting (gofmt for assembly)
|
- **Formatter.** `gasm fmt` canonicalises indentation, operand spacing,
|
||||||
gasm lint static checks
|
per-function mnemonic alignment and blank-line layout: `gofmt` for assembly,
|
||||||
gasm lsp language server (completion, hover, symbols, diagnostics, highlighting)
|
operating recursively on directories the way `go fmt` does.
|
||||||
gasm asm standalone assembler
|
- **Linter.** `gasm lint` runs 17 conservative static checks, among them
|
||||||
gasm verify dynamic analysis & verification
|
`undefined-label`, `abi-argsize` (declared frame vs the `// func` signature),
|
||||||
gasm debug source-level debugger
|
`register-clobber` (Go ABI register liveness over the control-flow graph),
|
||||||
gasm diff compare machine code of two .s files
|
`stack-imbalance`, `abi0-register-args` and `unencodable-instruction`.
|
||||||
gasm profile show basic-block structure of functions
|
- **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`
|
||||||
|
consumes in place of the toolchain's output.
|
||||||
|
- **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,
|
||||||
|
diagnostics, semantic-token highlighting, go-to-definition, find references,
|
||||||
|
rename, formatting, inlay hints, code actions, signature help, document
|
||||||
|
highlights and workspace symbol search over stdio.
|
||||||
|
- **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.
|
||||||
|
|
||||||
## Architecture support
|
### Architecture support
|
||||||
|
|
||||||
gasm-devkit targets every architecture Go's assembler speaks. The instruction
|
| Architecture | GOARCH | File suffix | Instructions recognised |
|
||||||
tables are **generated from the Go toolchain's own assembler source**
|
|--------------|-------------|--------------|---------------------------------------------|
|
||||||
(`cmd/internal/obj/<arch>`), so gasm-devkit recognises *every* mnemonic the
|
| AMD64 | `amd64` | `_amd64.s` | 1600 + common opcodes + traditional aliases |
|
||||||
real assembler accepts — not a hand-maintained subset that drifts and rots.
|
| ARM64 | `arm64` | `_arm64.s` | 538 + common opcodes |
|
||||||
|
| RISC-V | `riscv64` | `_riscv64.s` | 961 + common opcodes |
|
||||||
| Architecture | GOARCH | File suffix | Instructions recognised |
|
| LoongArch | `loong64` | `_loong64.s` | 799 + common opcodes |
|
||||||
|--------------|-------------|----------------|------------------------------------|
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
"Common opcodes" are the instructions shared by every architecture (`RET`,
|
"Common opcodes" are the instructions shared by every architecture (`RET`,
|
||||||
`JMP`, `NOP`, `CALL`, `TEXT`, `FUNCDATA`, `PCDATA`, …). AMD64 additionally
|
`JMP`, `NOP`, `CALL`, `TEXT`, `FUNCDATA`, `PCDATA`, ...). AMD64 additionally
|
||||||
carries the traditional conditional-jump spellings (`JZ`, `JNZ`, `JA`, `JC`,
|
carries the traditional conditional-jump spellings (`JZ`, `JNZ`, `JA`, `JC`,
|
||||||
…) that the assembler accepts as aliases. Regenerating the tables is one
|
...) that the assembler accepts as aliases. Regenerating the tables is one
|
||||||
command — `just gen` — and requires only a Go installation; the committed
|
command (`just gen`) and requires only a Go installation; the committed output
|
||||||
output has no runtime dependency on the toolchain.
|
has no runtime dependency on the toolchain.
|
||||||
|
|
||||||
## Supported Platforms
|
## Install
|
||||||
|
|
||||||
The toolkit runs on Linux. All four Linux architectures are supported as
|
Prebuilt binaries for linux/amd64, linux/arm64, linux/riscv64 and
|
||||||
hosts — amd64, arm64, riscv64 and loong64 — and the release matrix
|
linux/loong64 are on the
|
||||||
cross-compiles the same four targets.
|
[releases page](https://sourcedock.dev/petrbalvin/gasm-devkit/releases).
|
||||||
|
From source (Go 1.27 or later):
|
||||||
|
|
||||||
**FreeBSD support is planned for a future release.**
|
```sh
|
||||||
|
go install sourcedock.dev/petrbalvin/gasm-devkit/cmd/gasm@latest
|
||||||
|
```
|
||||||
|
|
||||||
## Principles
|
Or from a repository checkout, with the development version stamped:
|
||||||
|
|
||||||
- **Pure Go and GAsm only.** No C, no cgo, no external toolchains, no native
|
```sh
|
||||||
binaries, no JavaScript runtimes. The parser is hand-written; there is no
|
just install-bin
|
||||||
parser generator.
|
```
|
||||||
- **Self-contained.** The toolkit's production code depends only on the
|
|
||||||
standard library; one binary, no runtime data files. The single module
|
|
||||||
dependency, `golang.org/x/arch`, is used **only in tests** to validate the
|
|
||||||
instruction encoder by round-trip decoding — it is never linked into the
|
|
||||||
`gasm` binary.
|
|
||||||
- **Linux-only.** Runs natively on amd64, arm64, riscv64 and loong64 Linux
|
|
||||||
hosts; the release matrix cross-compiles the same four targets. Latest
|
|
||||||
stable Go only.
|
|
||||||
- **No vendor lock-in.** The integration surface is the Language Server
|
|
||||||
Protocol and a command-line interface — both open standards. No cloud
|
|
||||||
service, no proprietary API, no dependence on any one editor's internals.
|
|
||||||
- **Complete and verifiable.** Instruction coverage is generated from the
|
|
||||||
assembler's own source and regenerated on demand, so it cannot silently fall
|
|
||||||
behind the toolchain.
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
| Package | Purpose |
|
|
||||||
|---------|---------|
|
|
||||||
| `token` | Lexical token kinds and source positions. |
|
|
||||||
| `lexer` | Hand-written scanner for Plan 9 assembly. |
|
|
||||||
| `ast` | The abstract syntax tree. |
|
|
||||||
| `parser` | Line-oriented, error-tolerant parser producing the AST. |
|
|
||||||
| `arch` | amd64, arm64, riscv64 and loong64 register files and instruction tables. |
|
|
||||||
| `lint` | Conservative static checks (13 rules including unused-label, invalid-textflag, stack-imbalance). |
|
|
||||||
| `s` | A canonical formatter — `gofmt` for assembly. |
|
|
||||||
| `asm` | The standalone assembler: all four architecture encoders, linker, object-file emitters (ELF with DWARF5, GOOBJ). |
|
|
||||||
| `verify` | JIT execution substrate for dynamic analysis, combined ABI+fuzz differential testing. Assembly trampolines for all four architectures. |
|
|
||||||
| `debug` | Interactive ptrace debugger for all four architectures: single-stepping, breakpoints, hardware watchpoints, register and memory inspection. |
|
|
||||||
| `lsp` | Language Server Protocol server: completion, hover, symbols, diagnostics, semantic tokens, find references, rename, formatting, inlay hints. |
|
|
||||||
| `cmd/gasm` | The `gasm` binary tying it all together. |
|
|
||||||
| `_gen` | The generator that rebuilds the instruction tables from the Go toolchain. |
|
|
||||||
|
|
||||||
See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the design rationale and
|
|
||||||
data flow, and [`docs/DECISIONS.md`](docs/DECISIONS.md) for design decisions
|
|
||||||
deliberately postponed (with the analysis needed to pick them up again).
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
just install # download dependencies (there are none)
|
cat > hello_amd64.s <<'EOF'
|
||||||
just build # go vet + gofmt check — zero errors, zero warnings
|
#include "textflag.h"
|
||||||
just test # full suite, race detector, 80 % coverage gate
|
|
||||||
just fmt # gofmt the tree
|
// func add(a, b int) int
|
||||||
just gen # regenerate the instruction tables from the Go toolchain
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the binary and use it:
|
## Usage
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
just install-bin # installs gasm into $GOBIN
|
gasm fmt # reformat every .s below here, like go fmt
|
||||||
|
gasm fmt -w kernel_amd64.s # canonicalise one file in place
|
||||||
gasm --help # overview of commands and flags
|
gasm lint *.s # static checks
|
||||||
gasm tokens kernel_amd64.s # dump the token stream
|
gasm asm --format elf -o k.o k.s # assemble to a linkable ELF object
|
||||||
gasm parse kernel_amd64.s # parse, report syntax errors
|
gasm asm --format goobj -p pkg/path -o k.o k.s # Go object, consumed by go build
|
||||||
gasm fmt -w kernel_amd64.s # canonicalise in place
|
gasm verify --ground-truth k.s # byte-for-byte vs go tool asm
|
||||||
gasm fmt # reformat every .s below here, like go fmt
|
gasm verify --fuzz k.s # differential fuzz vs the go tool asm build
|
||||||
gasm lint *.s # static checks
|
gasm debug --func name k.s # interactive debugger
|
||||||
gasm asm --format elf -o k.o k.s # assemble to a linkable ELF object
|
|
||||||
gasm verify kernel_amd64.s # JIT-load and report functions
|
|
||||||
gasm verify --ground-truth k.s # byte-for-byte vs go tool asm
|
|
||||||
gasm verify --call decodeBlockAVX2 --buf src:64:hex...,dst:256:zero k.s
|
|
||||||
gasm verify --call sumTo --args n=57,base=0x1f k.s # scalar arguments
|
|
||||||
gasm debug --func name k.s # interactive debugger
|
|
||||||
gasm debug --func name --script cmds.txt --timeout 30s k.s # headless run
|
gasm debug --func name --script cmds.txt --timeout 30s k.s # headless run
|
||||||
gasm debug --func name --cover k.s # which labels did execution reach?
|
gasm debug --func name --cover k.s # which labels did execution reach?
|
||||||
gasm audit-instructions # encoder vs go tool asm name diff
|
gasm diff a.s b.s # compare machine code byte-for-byte
|
||||||
gasm scaffold differential k.s # generate a differential test skeleton
|
|
||||||
gasm diff a.s b.s # compare machine code byte-for-byte
|
|
||||||
gasm diff --map wideCopyAVX2=wideCopyAVX512 avx2.s avx512.s
|
gasm diff --map wideCopyAVX2=wideCopyAVX512 avx2.s avx512.s
|
||||||
gasm profile k.s # show basic-block structure
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow,
|
Run `gasm --help` for the command overview and `gasm <command> -h` for a
|
||||||
[docs/CLI.md](docs/CLI.md) for the command reference, and
|
command's flags. [docs/CLI.md](docs/CLI.md) is the full reference.
|
||||||
[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for setup and recipes.
|
|
||||||
|
|
||||||
## Editor integration
|
### Editor integration
|
||||||
|
|
||||||
`gasm lsp` speaks the Language Server Protocol over standard input/output, so
|
`gasm lsp` speaks the Language Server Protocol over standard input/output, so
|
||||||
any LSP-capable editor can use it — point your editor's LSP client at the
|
any LSP-capable editor can use it: point your editor's LSP client at the
|
||||||
binary and associate it with `.s` files. Syntax highlighting is delivered as
|
binary and associate it with `.s` files. Syntax highlighting is delivered as
|
||||||
**LSP semantic tokens**, so no editor-specific grammar is required. The server
|
LSP semantic tokens, so no editor-specific grammar is required. The server
|
||||||
infers the target architecture from the file-name suffix
|
infers the target architecture from the file-name suffix
|
||||||
(`_amd64.s` / `_arm64.s` / `_riscv64.s` / `_loong64.s`).
|
(`_amd64.s` / `_arm64.s` / `_riscv64.s` / `_loong64.s`).
|
||||||
|
|
||||||
## License
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
just install # download module dependencies
|
||||||
|
just build # go vet + gofmt check, zero errors and zero warnings
|
||||||
|
just test # full suite, race detector, 80 % coverage gate
|
||||||
|
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
|
||||||
|
- [docs/DECISIONS.md](docs/DECISIONS.md): deferred design decisions
|
||||||
|
- [CHANGELOG.md](CHANGELOG.md): release history
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
BSD-3-Clause — see [LICENSE](LICENSE).
|
||||||
|
|
||||||
BSD-3-Clause — see [LICENSE](LICENSE).
|
|
||||||
Copyright © 2026 [Petr Balvín](https://petrbalvin.org)
|
Copyright © 2026 [Petr Balvín](https://petrbalvin.org)
|
||||||
|
|||||||
+89
-60
@@ -7,7 +7,7 @@ Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrb
|
|||||||
## Design goals
|
## Design goals
|
||||||
|
|
||||||
1. **A real AST, not a grammar hack.** The linter, analyser, assembler and
|
1. **A real AST, not a grammar hack.** The linter, analyser, assembler and
|
||||||
language server all need to *reason* about assembly — not just colour it.
|
language server all need to *reason* about assembly, not just colour it.
|
||||||
So the centre of the toolkit is a hand-written lexer and a parser that
|
So the centre of the toolkit is a hand-written lexer and a parser that
|
||||||
produce a typed AST with source positions on every node.
|
produce a typed AST with source positions on every node.
|
||||||
2. **Architecture as data, not code.** Per-architecture differences (amd64,
|
2. **Architecture as data, not code.** Per-architecture differences (amd64,
|
||||||
@@ -26,7 +26,7 @@ Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrb
|
|||||||
graph TD
|
graph TD
|
||||||
SRC["source .s"] --> LEX["lexer<br/>token stream"]
|
SRC["source .s"] --> LEX["lexer<br/>token stream"]
|
||||||
LEX --> PAR["parser<br/>AST + diagnostics"]
|
LEX --> PAR["parser<br/>AST + diagnostics"]
|
||||||
LEX --> FMT["s<br/>re-space tokens"]
|
LEX --> FMT["format<br/>re-space tokens"]
|
||||||
PAR --> LINT["lint<br/>static checks"]
|
PAR --> LINT["lint<br/>static checks"]
|
||||||
PAR --> LSP["lsp server"]
|
PAR --> LSP["lsp server"]
|
||||||
LEX --> LSP
|
LEX --> LSP
|
||||||
@@ -69,8 +69,8 @@ instruction, comment, preprocessor) and dispatches. A malformed line is
|
|||||||
reported and skipped; it never aborts the file.
|
reported and skipped; it never aborts the file.
|
||||||
|
|
||||||
Operands are parsed into a faithful, flat representation. The amd64
|
Operands are parsed into a faithful, flat representation. The amd64
|
||||||
addressing modes — `reg`, `$imm`, `(base)`, `off(base)`, `(base)(index*scale)`,
|
addressing modes (`reg`, `$imm`, `(base)`, `off(base)`, `(base)(index*scale)`,
|
||||||
`name+off(FP)`, `name<>(SB)` — are all captured structurally, and the original
|
`name+off(FP)`, `name<>(SB)`) are all captured structurally, and the original
|
||||||
token text is retained for fidelity.
|
token text is retained for fidelity.
|
||||||
|
|
||||||
A deliberate boundary: the AST records **syntax only**. Whether a bare
|
A deliberate boundary: the AST records **syntax only**. Whether a bare
|
||||||
@@ -80,8 +80,8 @@ arch-agnostic and its output deterministic.
|
|||||||
|
|
||||||
### `arch`
|
### `arch`
|
||||||
|
|
||||||
Register files are generated programmatically (the regular `R8`–`R15`,
|
Register files are generated programmatically (the regular `R8`-`R15`,
|
||||||
`X0`–`X15`, `Y0`–`Y15`, `Z0`–`Z31`, `K0`–`K7` ranges) plus the irregularly
|
`X0`-`X15`, `Y0`-`Y15`, `Z0`-`Z31`, `K0`-`K7` ranges) plus the irregularly
|
||||||
named registers listed explicitly. Instruction names are **generated from the
|
named registers listed explicitly. Instruction names are **generated from the
|
||||||
Go toolchain's own assembler source** (`cmd/internal/obj/<arch>/anames.go`,
|
Go toolchain's own assembler source** (`cmd/internal/obj/<arch>/anames.go`,
|
||||||
plus the common opcodes and the per-architecture front-end aliases such as the
|
plus the common opcodes and the per-architecture front-end aliases such as the
|
||||||
@@ -95,11 +95,13 @@ fixed-arity instructions (`RET`, `NOP`, `JMP`, `CALL`) carry counts at all.
|
|||||||
|
|
||||||
### `lint`
|
### `lint`
|
||||||
|
|
||||||
Rules are conservative by design — silence beats a false positive. The rules
|
Rules are conservative by design: silence beats a false positive. The rules
|
||||||
are `unknown-instruction`, `operand-count`, `undefined-label`,
|
are `unknown-instruction`, `operand-count`, `undefined-label`,
|
||||||
`duplicate-label`, `missing-ret`, `missing-textflag-include`, `abi-argsize`,
|
`duplicate-label`, `missing-ret`, `missing-textflag-include`, `abi-argsize`,
|
||||||
`unreachable-code`, `register-clobber`, `funcdata-pcdata`, `unused-label`,
|
`unreachable-code`, `register-clobber`, `funcdata-pcdata`, `unused-label`,
|
||||||
`invalid-textflag` and `stack-imbalance`. Every diagnostic carries a stable
|
`invalid-textflag`, `stack-imbalance`, `register-width-mismatch`,
|
||||||
|
`abi0-register-args`, `nonportable-register-name` and
|
||||||
|
`unencodable-instruction`. Every diagnostic carries a stable
|
||||||
code so callers can disable rules individually, and arch-specific rules switch
|
code so callers can disable rules individually, and arch-specific rules switch
|
||||||
off entirely when the target architecture cannot be inferred from the file name.
|
off entirely when the target architecture cannot be inferred from the file name.
|
||||||
|
|
||||||
@@ -107,7 +109,7 @@ Two things keep the rules honest on real-world code:
|
|||||||
|
|
||||||
- **Pseudo-ops and macros are not instructions.** `unknown-instruction` knows
|
- **Pseudo-ops and macros are not instructions.** `unknown-instruction` knows
|
||||||
the assembler pseudo-ops (`BYTE`, `WORD`, `FUNCDATA`, `PCDATA`, …) and
|
the assembler pseudo-ops (`BYTE`, `WORD`, `FUNCDATA`, `PCDATA`, …) and
|
||||||
recognises macro invocations — an in-file `#define` name, or any identifier
|
recognises macro invocations: an in-file `#define` name, or any identifier
|
||||||
containing an underscore (no Plan 9 mnemonic ever does).
|
containing an underscore (no Plan 9 mnemonic ever does).
|
||||||
- **Macro-heavy files get the label/RET heuristics turned off.** Without a
|
- **Macro-heavy files get the label/RET heuristics turned off.** Without a
|
||||||
preprocessor, labels a macro defines are invisible, so `undefined-label` and
|
preprocessor, labels a macro defines are invisible, so `undefined-label` and
|
||||||
@@ -128,27 +130,27 @@ Two deeper analyses sit on top of the AST:
|
|||||||
parameters), and checks the total against the argument size declared in the
|
parameters), and checks the total against the argument size declared in the
|
||||||
`TEXT` directive. It only runs for stack-argument functions (a non-zero
|
`TEXT` directive. It only runs for stack-argument functions (a non-zero
|
||||||
declared arg area that is actually addressed through `FP`), and aborts
|
declared arg area that is actually addressed through `FP`), and aborts
|
||||||
silently on a type whose size it cannot determine — so it never guesses.
|
silently on a type whose size it cannot determine, so it never guesses.
|
||||||
- **`unreachable-code`.** Code after a `RET` and before the next label is
|
- **`unreachable-code`.** Code after a `RET` and before the next label is
|
||||||
dead. The check is suppressed for any function whose reachability cannot be
|
dead. The check is suppressed for any function whose reachability cannot be
|
||||||
decided statically: those using PC-relative jumps (`JMP 2(PC)`),
|
decided statically: those using PC-relative jumps (`JMP 2(PC)`),
|
||||||
register-indirect branches (`JALR`/`JR`/`JIRL`/`BR`/`BLR`), or living in a
|
register-indirect branches (`JALR`/`JR`/`JIRL`/`BR`/`BLR`), or living in a
|
||||||
file with `#ifdef` conditionals. `UNDEF` is deliberately not a terminator —
|
file with `#ifdef` conditionals. `UNDEF` is deliberately not a terminator:
|
||||||
code after it is occasionally intentional metadata.
|
code after it is occasionally intentional metadata.
|
||||||
- **`register-clobber` (register liveness).** The linter builds the function's
|
- **`register-clobber` (register liveness).** The linter builds the function's
|
||||||
control-flow graph (basic blocks split at labels and after branches, with
|
control-flow graph (basic blocks split at labels and after branches, with
|
||||||
fall-through and jump-target edges), computes a conservative per-instruction
|
fall-through and jump-target edges), computes a conservative per-instruction
|
||||||
register def/use, and runs the standard backward liveness iteration to a fixed
|
register def/use, and runs the standard backward liveness iteration to a fixed
|
||||||
point. On top of that it flags writes to the registers the **Go ABI** fixes
|
point. On top of that it flags writes to the registers the **Go ABI** fixes
|
||||||
across calls that are never saved and restored — calibrated from
|
across calls that are never saved and restored, calibrated from
|
||||||
`cmd/compile/abi-internal.md`, *not* the platform ABI: Go's stack-based ABI0
|
`cmd/compile/abi-internal.md`, *not* the platform ABI: Go's stack-based ABI0
|
||||||
has no System V style callee-saved registers (amd64 `BX`, `R12`–`R15` and
|
has no System V style callee-saved registers (amd64 `BX`, `R12`-`R15` and
|
||||||
the like are caller-saved or permanent scratch, and hand-written kernels may
|
the like are caller-saved or permanent scratch, and hand-written kernels may
|
||||||
clobber them freely). The audited set is the frame pointer and the
|
clobber them freely). The audited set is the frame pointer and the
|
||||||
the frame pointer, the goroutine pointer per architecture (amd64 `BP`/`R14`, arm64 `R18`/`R28`/
|
goroutine pointer per architecture (amd64 `BP`/`R14`, arm64 `R18`/`R28`/
|
||||||
`R29`, riscv64 `X27`, loong64 `R22`); the goroutine pointer is reported only
|
`R29`, riscv64 `X27`, loong64 `R22`); the goroutine pointer is reported only
|
||||||
when the function can reach the runtime — it is not `NOSPLIT` or makes a
|
when the function can reach the runtime (it is not `NOSPLIT` or makes a
|
||||||
call — since the ABI0 transition machinery restores it on those paths, and
|
call), since the ABI0 transition machinery restores it on those paths, and
|
||||||
NOSPLIT call-free leaves may use it (the runtime's own assembly does). It
|
NOSPLIT call-free leaves may use it (the runtime's own assembly does). It
|
||||||
runs only on macro-free files, where
|
runs only on macro-free files, where
|
||||||
no opaque macro can perform the save/restore.
|
no opaque macro can perform the save/restore.
|
||||||
@@ -157,16 +159,16 @@ Two deeper analyses sit on top of the AST:
|
|||||||
reference) and a literal index is range-checked; a named index constant such
|
reference) and a literal index is range-checked; a named index constant such
|
||||||
as `$PCDATA_StackMapIndex` is accepted without a range check.
|
as `$PCDATA_StackMapIndex` is accepted without a range check.
|
||||||
|
|
||||||
### `s`
|
### `format`
|
||||||
|
|
||||||
The formatter works on the **token stream, not the AST**, so it preserves
|
The formatter works on the **token stream, not the AST**, so it preserves
|
||||||
every line — comments and blanks included. It normalises indentation, operand
|
every line, comments and blanks included. It normalises indentation, operand
|
||||||
spacing, per-function mnemonic alignment and blank-line layout: a new block
|
spacing, per-function mnemonic alignment and blank-line layout: a new block
|
||||||
(a label, `TEXT` or `GLOBL`) is preceded by exactly one blank line (comments
|
(a label, `TEXT` or `GLOBL`) is preceded by exactly one blank line (comments
|
||||||
leading a block stay with it), runs of blanks collapse to one, and a `RET`
|
leading a block stay with it), runs of blanks collapse to one, and a `RET`
|
||||||
terminates the body so the next function's doc comment stays at column 0. It
|
terminates the body so the next function's doc comment stays at column 0. It
|
||||||
is idempotent and its output always round-trips through the parser. With a
|
is idempotent and its output always round-trips through the parser. With a
|
||||||
directory argument — or none — it reformats every `.s` file below it in
|
directory argument, or none, it reformats every `.s` file below it in
|
||||||
place and lists the files changed, the way `go fmt` does (`.` and `_`
|
place and lists the files changed, the way `go fmt` does (`.` and `_`
|
||||||
directories are skipped).
|
directories are skipped).
|
||||||
|
|
||||||
@@ -176,13 +178,20 @@ The server speaks JSON-RPC 2.0 with `Content-Length` framing over any
|
|||||||
`io.Reader`/`io.Writer` (normally stdin/stdout). It maintains an in-memory
|
`io.Reader`/`io.Writer` (normally stdin/stdout). It maintains an in-memory
|
||||||
document store, republishes diagnostics on every change, and provides:
|
document store, republishes diagnostics on every change, and provides:
|
||||||
|
|
||||||
- **completion** — instructions, registers, pseudo-registers, textflag macros
|
- **completion**: instructions, registers, pseudo-registers, textflag macros
|
||||||
and local labels;
|
and local labels;
|
||||||
- **hover** — instruction summaries and register descriptions from `arch`;
|
- **hover**: instruction summaries and register descriptions from `arch`;
|
||||||
- **document symbols** — `TEXT` functions with their labels, plus `GLOBL`/`DATA`;
|
- **document symbols**: `TEXT` functions with their labels, plus `GLOBL`/`DATA`;
|
||||||
- **semantic tokens** — syntax highlighting delivered as LSP semantic tokens,
|
- **semantic tokens**: syntax highlighting delivered as LSP semantic tokens,
|
||||||
classified with the lexer plus `arch` (instructions, registers by class,
|
classified with the lexer plus `arch` (instructions, registers by class,
|
||||||
pseudo-registers, labels, immediates, comments, directives, textflag macros).
|
pseudo-registers, labels, immediates, comments, directives, textflag macros);
|
||||||
|
- **navigation**: go-to-definition from a label reference to its definition,
|
||||||
|
find references, document highlights of every use of the symbol under the
|
||||||
|
cursor, rename, and workspace symbol search over the open documents;
|
||||||
|
- **assists**: document formatting through the `format` package, inlay hints
|
||||||
|
(the frame size after the TEXT argument area), signature help (the callee's
|
||||||
|
`// func` signature while the cursor is on a `CALL`), and code actions
|
||||||
|
offering quick fixes for the `missing-ret` and `unused-label` diagnostics.
|
||||||
|
|
||||||
Semantic tokens are the key to editor-agnostic highlighting: the editor renders
|
Semantic tokens are the key to editor-agnostic highlighting: the editor renders
|
||||||
them from the standard LSP legend, so no editor-specific grammar is needed.
|
them from the standard LSP legend, so no editor-specific grammar is needed.
|
||||||
@@ -192,7 +201,7 @@ them from the standard LSP legend, so no editor-specific grammar is needed.
|
|||||||
The standalone assembler (Phase 2). Its core is an amd64 instruction encoder:
|
The standalone assembler (Phase 2). Its core is an amd64 instruction encoder:
|
||||||
a REX/ModR-M/SIB/displacement/immediate engine plus the scalar instruction set,
|
a REX/ModR-M/SIB/displacement/immediate engine plus the scalar instruction set,
|
||||||
with the Plan 9 operand order (source first) mapped onto the x86 encoding.
|
with the Plan 9 operand order (source first) mapped onto the x86 encoding.
|
||||||
Every encoding is validated by decoding it again with `golang.org/x/arch` — the
|
Every encoding is validated by decoding it again with `golang.org/x/arch`, the
|
||||||
one module dependency, used in tests only and never linked into the binary.
|
one module dependency, used in tests only and never linked into the binary.
|
||||||
|
|
||||||
A **RISC-V encoder** (Phase 5, RV64IMAFDC + RVC compression) encodes the full
|
A **RISC-V encoder** (Phase 5, RV64IMAFDC + RVC compression) encodes the full
|
||||||
@@ -232,12 +241,12 @@ fixed point, and jump-to-jump chains are folded (a conditional jump to a label
|
|||||||
whose only instruction is an unconditional jump is redirected to the ultimate
|
whose only instruction is an unconditional jump is redirected to the ultimate
|
||||||
target) exactly as the Go toolchain's linker does before it encodes branches.
|
target) exactly as the Go toolchain's linker does before it encodes branches.
|
||||||
The `FP`/`SP` pseudo-
|
The `FP`/`SP` pseudo-
|
||||||
registers are translated onto the hardware stack pointer — `x+N(FP)` becomes
|
registers are translated onto the hardware stack pointer: `x+N(FP)` becomes
|
||||||
`(N+8)(SP)` for a zero-frame function and `(N+frame+16)(SP)` once a frame
|
`(N+8)(SP)` for a zero-frame function and `(N+frame+16)(SP)` once a frame
|
||||||
pointer is set up, with the matching Go prologue/epilogue generated — so the
|
pointer is set up, with the matching Go prologue/epilogue generated, so the
|
||||||
output is byte-identical to the Go assembler for these cases. SIMD is handled
|
output is byte-identical to the Go assembler for these cases. SIMD is handled
|
||||||
by a VEX (AVX/AVX2) encoder — the two- and three-byte VEX prefixes with XMM/YMM
|
by a VEX (AVX/AVX2) encoder (the two- and three-byte VEX prefixes with XMM/YMM
|
||||||
registers — across eight operand forms: the three-operand NDS form, the
|
registers) across eight operand forms: the three-operand NDS form, the
|
||||||
two-operand reg/rm form, the immediate-shift form (plus the variable-count
|
two-operand reg/rm form, the immediate-shift form (plus the variable-count
|
||||||
shifts, which share the NDS shape with the count in an XMM register or
|
shifts, which share the NDS shape with the count in an XMM register or
|
||||||
memory), the immediate shuffle form (`VPSHUFD`, `VPERMQ`), the
|
memory), the immediate shuffle form (`VPSHUFD`, `VPERMQ`), the
|
||||||
@@ -245,24 +254,24 @@ three-operand-plus-immediate form (`VSHUFPD`,
|
|||||||
`VPERM2I128`, `VINSERTI128`), the lane-extract form (`VEXTRACTI128`,
|
`VPERM2I128`, `VINSERTI128`), the lane-extract form (`VEXTRACTI128`,
|
||||||
`VEXTRACTF128`, where the YMM source occupies the reg field and the XMM or
|
`VEXTRACTF128`, where the YMM source occupies the reg field and the XMM or
|
||||||
memory destination r/m), the direction-sensitive moves (`VMOVDQU`, `VMOVUPD`,
|
memory destination r/m), the direction-sensitive moves (`VMOVDQU`, `VMOVUPD`,
|
||||||
`VMOVD`, `VMOVQ`, `VMOVSD`), the floating-point and FMA arithmetic — the
|
`VMOVD`, `VMOVQ`, `VMOVSD`), the floating-point and FMA arithmetic: the
|
||||||
packed double operations (`VADDPD`/`VSUBPD`/`VMULPD`/`VDIVPD`/`VMINPD`/
|
packed double operations (`VADDPD`/`VSUBPD`/`VMULPD`/`VDIVPD`/`VMINPD`/
|
||||||
`VMAXPD`), the unpacks (`VUNPCKHPD`/`VUNPCKLPD`), the scalar SD and SS
|
`VMAXPD`), the unpacks (`VUNPCKHPD`/`VUNPCKLPD`), the scalar SD and SS
|
||||||
operations, `VMOVDDUP`, `VXORPD`, the width-changing conversions
|
operations, `VMOVDDUP`, `VXORPD`, the width-changing conversions
|
||||||
(`VCVTDQ2PS`, `VCVTPS2PD`, `VCVTDQ2PD`, and the `VCVTPD2DQX`/`Y` and
|
(`VCVTDQ2PS`, `VCVTPS2PD`, `VCVTDQ2PD`, and the `VCVTPD2DQX`/`Y` and
|
||||||
`VCVTTPD2DQX`/`Y` spellings, whose length follows the wider source) and
|
`VCVTTPD2DQX`/`Y` spellings, whose length follows the wider source) and
|
||||||
`VFMADD231PD` — and the no-operand `VZEROUPPER`, together with `VPERMD` and
|
`VFMADD231PD`, and the no-operand `VZEROUPPER`, together with `VPERMD` and
|
||||||
the scalar families (`CMOVcc`, `SETcc`, `LZCNT`/`TZCNT`, the extending moves,
|
the scalar families (`CMOVcc`, `SETcc`, `LZCNT`/`TZCNT`, the extending moves,
|
||||||
`CVTSx2SD`, `IMUL3`) and the EVEX (AVX-512) prefix — the four-byte prefix with
|
`CVTSx2SD`, `IMUL3`) and the EVEX (AVX-512) prefix, the four-byte prefix with
|
||||||
5-bit register fields (Z0–Z31, X/Y 16–31, with the mod=11 quirk that carries
|
5-bit register fields (Z0-Z31, X/Y 16-31, with the mod=11 quirk that carries
|
||||||
rm[4] in X̄), opmask registers (K0–K7 as operands, mask destinations and
|
rm[4] in X̄), opmask registers (K0-K7 as operands, mask destinations and
|
||||||
explicit merging/zeroing masks — written the way Go writes them, as a K
|
explicit merging/zeroing masks, written the way Go writes them, as a K
|
||||||
operand among the operands plus a `.Z` mnemonic suffix), and the compressed
|
operand among the operands plus a `.Z` mnemonic suffix), and the compressed
|
||||||
disp8×N displacement, whose multiplier follows the memory operand's size —
|
disp8×N displacement, whose multiplier follows the memory operand's size,
|
||||||
covering every instruction the go-flac and go-lz4 AVX2/AVX-512 kernels use,
|
covering every instruction the go-flac and go-lz4 AVX2/AVX-512 kernels use,
|
||||||
plus the common AVX-512 F/BW integer set, the floating-point and conversion
|
plus the common AVX-512 F/BW integer set, the floating-point and conversion
|
||||||
set (the packed double and single arithmetic, the scalar SD/SS forms —
|
set (the packed double and single arithmetic, the scalar SD/SS forms
|
||||||
whose EVEX encodings serve masked and zeroing use — `VMOVDDUP`, the
|
(whose EVEX encodings serve masked and zeroing use), `VMOVDDUP`, the
|
||||||
replicating moves, and the width-changing conversions, including the
|
replicating moves, and the width-changing conversions, including the
|
||||||
`VCVTPD2DQ`/`VCVTTPD2DQ` family whose length follows the wider source
|
`VCVTPD2DQ`/`VCVTTPD2DQ` family whose length follows the wider source
|
||||||
operand), and the wider AVX-512 set: ternary logic, lane shuffles, inserts
|
operand), and the wider AVX-512 set: ternary logic, lane shuffles, inserts
|
||||||
@@ -272,21 +281,21 @@ expand/compress family, the broadcasts, the opmask-register instructions
|
|||||||
moves and the remaining extending/narrowing moves, the floating-point
|
moves and the remaining extending/narrowing moves, the floating-point
|
||||||
helper and conversion tail (VRCP14*, VRSQRT14*, VGETEXP*, VGETMANT*,
|
helper and conversion tail (VRCP14*, VRSQRT14*, VGETEXP*, VGETMANT*,
|
||||||
VSCALEF*, VRNDSCALE*, VREDUCE*, VFIXUPIMM*, VRANGE*, VFPCLASS* with an
|
VSCALEF*, VRNDSCALE*, VREDUCE*, VFIXUPIMM*, VRANGE*, VFPCLASS* with an
|
||||||
opmask destination, and the VCVT* conversions — signed, unsigned and
|
opmask destination, and the VCVT* conversions, signed, unsigned and
|
||||||
truncating, including the length-suffixed X/Y spellings and the
|
truncating, including the length-suffixed X/Y spellings and the
|
||||||
mask/vector conversions VPMOVM2*/VPMOV*2M, and the scalar conversions
|
mask/vector conversions VPMOVM2*/VPMOV*2M, and the scalar conversions
|
||||||
between vector and general-purpose registers (VCVT{,T}S{D,S}2SI{,Q} and
|
between vector and general-purpose registers (VCVT{,T}S{D,S}2SI{,Q} and
|
||||||
the unsigned forms, VCVTSI2*/VCVTUSI2*), and gather/scatter with VSIB addressing — both the
|
the unsigned forms, VCVTSI2*/VCVTUSI2*), and gather/scatter with VSIB addressing, both the
|
||||||
VEX spelling with a vector mask register and the EVEX spelling with an
|
VEX spelling with a vector mask register and the EVEX spelling with an
|
||||||
explicit K mask, where the EVEX length follows the VSIB index register,
|
explicit K mask, where the EVEX length follows the VSIB index register,
|
||||||
not the data register. The EVEX mnemonic
|
not the data register. The EVEX mnemonic
|
||||||
suffixes — rounding modes (.RN_SAE/.RD_SAE/.RU_SAE/.RZ_SAE),
|
suffixes (rounding modes (.RN_SAE/.RD_SAE/.RU_SAE/.RZ_SAE),
|
||||||
suppress-all-exceptions (.SAE) and memory broadcast (.BCST) — set the EVEX
|
suppress-all-exceptions (.SAE) and memory broadcast (.BCST)) set the EVEX
|
||||||
b bit and the L'L rounding-control field (broadcast keeps the vector length
|
b bit and the L'L rounding-control field (broadcast keeps the vector length
|
||||||
and scales disp8 by the element size), and combine with the .Z zeroing
|
and scales disp8 by the element size), and combine with the .Z zeroing
|
||||||
suffix. Every encoding is validated two ways: by
|
suffix. Every encoding is validated two ways: by
|
||||||
round-trip decoding through `golang.org/x/arch`, and byte-for-byte against
|
round-trip decoding through `golang.org/x/arch`, and byte-for-byte against
|
||||||
the machine code the real Go assembler emits — a comparison that holds for
|
the machine code the real Go assembler emits, a comparison that holds for
|
||||||
whole functions: all 27 functions of both kernels assemble to exactly the Go
|
whole functions: all 27 functions of both kernels assemble to exactly the Go
|
||||||
toolchain's bytes, the lone exception being the displacements of the
|
toolchain's bytes, the lone exception being the displacements of the
|
||||||
static-constant loads, which the Go linker fills at link time.
|
static-constant loads, which the Go linker fills at link time.
|
||||||
@@ -300,14 +309,17 @@ the bytes are self-consistent at any base address. References to symbols no
|
|||||||
object-file emitters turn the whole image into a linkable object: the ELF
|
object-file emitters turn the whole image into a linkable object: the ELF
|
||||||
writer (`gasm asm --format elf`) lays the code and data out as `.text`/`.data`
|
writer (`gasm asm --format elf`) lays the code and data out as `.text`/`.data`
|
||||||
sections, exports a symbol per
|
sections, exports a symbol per
|
||||||
`TEXT` and `GLOBL` (the `<>` ones local, the rest global) and emit one
|
`TEXT` and `GLOBL` (the `<>` ones local, the rest global), emits one
|
||||||
PC-relative relocation per static-symbol reference — undefined external
|
PC-relative relocation per static-symbol reference, undefined external
|
||||||
symbols included, so the output links with the system toolchain. The GOOBJ
|
symbols included, and appends the DWARF5 debug sections
|
||||||
|
(`.debug_abbrev`, `.debug_info`, `.debug_line`, `.debug_line_str`, and a
|
||||||
|
`.debug_frame` CFI section on amd64) so `addr2line` and GDB/LLDB can debug
|
||||||
|
the output. The GOOBJ
|
||||||
emitter (`gasm asm --format goobj`) writes the format the Go linker consumes
|
emitter (`gasm asm --format goobj`) writes the format the Go linker consumes
|
||||||
directly: the functions as non-package symbols (the way `cmd/asm` records
|
directly: the functions as non-package symbols (the way `cmd/asm` records
|
||||||
assembly symbols), the `GLOBL` data, one `FuncInfo` per function and the
|
assembly symbols), the `GLOBL` data, one `FuncInfo` per function and the
|
||||||
pc-value tables — `pcsp` built from the prologue and epilogue stack
|
pc-value tables (`pcsp` built from the prologue and epilogue stack
|
||||||
boundaries, plus flat `pcfile`, `pcline` and `pcinline` tables — so a
|
boundaries, plus flat `pcfile`, `pcline` and `pcinline` tables), so a
|
||||||
gasm-assembled object drops into a `go build` in place of the toolchain's.
|
gasm-assembled object drops into a `go build` in place of the toolchain's.
|
||||||
The object preamble (the version-and-experiment header the linker compares
|
The object preamble (the version-and-experiment header the linker compares
|
||||||
verbatim) is captured from the installed `go tool asm`, so the output is
|
verbatim) is captured from the installed `go tool asm`, so the output is
|
||||||
@@ -315,18 +327,21 @@ always consistent with the toolchain that links it. RISC-V and LoongArch
|
|||||||
GOOBJ emission share this emitter: the loong64 marker with
|
GOOBJ emission share this emitter: the loong64 marker with
|
||||||
R_LOONG64_ADDR_HI/LO relocation types, and the riscv64 marker with a single
|
R_LOONG64_ADDR_HI/LO relocation types, and the riscv64 marker with a single
|
||||||
R_RISCV_PCREL_ITYPE/STYPE relocation per AUIPC pair (plus `R_RISCV_JAL` for
|
R_RISCV_PCREL_ITYPE/STYPE relocation per AUIPC pair (plus `R_RISCV_JAL` for
|
||||||
`CALL sym(SB)`) — the model `cmd/asm`
|
`CALL sym(SB)`), the model `cmd/asm`
|
||||||
writes, not the ELF HI20/LO12 pair — and both link into a real `go build` for
|
writes, not the ELF HI20/LO12 pair, and both link into a real `go build` for
|
||||||
their `GOARCH`. Per function, the emitter also writes the two DWARF
|
their `GOARCH`. Per function, the emitter also writes the two DWARF
|
||||||
symbols the linker's DWARF pass reads verbatim — the subprogram DIE
|
symbols the linker's DWARF pass reads verbatim: the subprogram DIE
|
||||||
(`SDWARFFCN`) and the `.debug_line` state-machine program (`SDWARFLINES`),
|
(`SDWARFFCN`) and the `.debug_line` state-machine program (`SDWARFLINES`),
|
||||||
both built the way `cmd/asm` builds them (the DIE carries the
|
both built the way `cmd/asm` builds them (the DIE carries the
|
||||||
R_DWTXTADDR_U4 address reference; the line program one row per source-line
|
R_DWTXTADDR_U4 address reference; the line program one row per source-line
|
||||||
change, in the same special-opcode encoding) — and the pc-value deltas are
|
change, in the same special-opcode encoding), and the pc-value deltas are
|
||||||
in the architecture's MinLC units, as the runtime's `pcvalue` expects.
|
in the architecture's MinLC units, as the runtime's `pcvalue` expects.
|
||||||
External cross-package references remain future work (the amd64 and RISC-V
|
Cross-package external references resolve on all four architectures: when
|
||||||
paths resolve them; LoongArch does not yet); the rest of Phase 2 is those
|
`img.Externals` is non-empty, the GOOBJ emitter locates the referenced
|
||||||
and the remaining EVEX forms.
|
package's `.a` archive via `go list -json -export`, reads the GOOBJ symbol
|
||||||
|
definitions the linker reads, and wires the resolved package and symbol
|
||||||
|
indices into the emission, so a gasm-assembled object links against the
|
||||||
|
compiled packages it references.
|
||||||
|
|
||||||
### `verify`
|
### `verify`
|
||||||
|
|
||||||
@@ -343,6 +358,9 @@ stack pointer in a package global and jumps to the target), and recovers
|
|||||||
control when the function RETs into `leaveJIT` (which restores the Go stack
|
control when the function RETs into `leaveJIT` (which restores the Go stack
|
||||||
and returns). A 64-byte pad below the return address accommodates the
|
and returns). A 64-byte pad below the return address accommodates the
|
||||||
ABIInternal wrapper that the Go runtime interposes on assembly functions.
|
ABIInternal wrapper that the Go runtime interposes on assembly functions.
|
||||||
|
Every supported architecture carries its own hand-written trampoline pair
|
||||||
|
(`trampoline_amd64.s`, `trampoline_arm64.s`, `trampoline_riscv64.s`,
|
||||||
|
`trampoline_loong64.s`), so `Call` works wherever the toolkit runs.
|
||||||
|
|
||||||
`Load` / `LoadSource` / `LoadAST` parse, assemble and map a `.s` file in one
|
`Load` / `LoadSource` / `LoadAST` parse, assemble and map a `.s` file in one
|
||||||
step, returning a `Kernel` whose `CallFunc` method marshals the argument block
|
step, returning a `Kernel` whose `CallFunc` method marshals the argument block
|
||||||
@@ -351,16 +369,20 @@ assembler’s `Image.Bytes()` provides the code-and-data concatenation.
|
|||||||
|
|
||||||
The `gasm verify` CLI subcommand exposes this: it loads a file, reports the
|
The `gasm verify` CLI subcommand exposes this: it loads a file, reports the
|
||||||
available functions and (with `-smoke`) calls each NOSPLIT function with zeroed
|
available functions and (with `-smoke`) calls each NOSPLIT function with zeroed
|
||||||
arguments to confirm the trampoline round-trips. `gasm verify --fuzz` combines
|
arguments to confirm the trampoline round-trips. The `-smoke` and `-abi`
|
||||||
|
sweeps run in parallel and each inside a child process, so a function that
|
||||||
|
faults is reported without ending the sweep. `gasm verify --fuzz` combines
|
||||||
ABI checks (sentinel registers, canary, stack bounds) with differential fuzz
|
ABI checks (sentinel registers, canary, stack bounds) with differential fuzz
|
||||||
testing, comparing the JIT-assembled kernel against the portable Go reference
|
testing, comparing the JIT-assembled kernel against the portable Go reference
|
||||||
bit-for-bit while verifying the ABI contract on every iteration. When a fuzz
|
bit-for-bit while verifying the ABI contract on every iteration. When a fuzz
|
||||||
iteration crashes or mismatches, `FuzzResult.CrashInput` stores the exact input
|
iteration crashes or mismatches, `FuzzResult.CrashInput` stores the exact input
|
||||||
for reproducibility. `gasm verify --call <func> --buf name:size:pattern`
|
for reproducibility. `gasm verify --call <func> --buf name:size:pattern`
|
||||||
invokes a single function with user-supplied buffers (patterns: zero, ones,
|
invokes a single function with user-supplied buffers (patterns: zero, ones,
|
||||||
seq, or hex), printing the ABI0 argument block before and after the call —
|
seq, or hex), printing the ABI0 argument block before and after the call,
|
||||||
useful for partial functions (e.g. decoders) that crash on random input but
|
useful for partial functions (e.g. decoders) that crash on random input but
|
||||||
should succeed on valid data. `gasm verify --ground-truth` compares the
|
should succeed on valid data; `--args name=value,...` supplies scalar
|
||||||
|
arguments (decimal or `0x` hex) alongside the buffers. `gasm verify
|
||||||
|
--ground-truth` compares the
|
||||||
assembled machine code byte-for-byte against `go tool asm` (relocation sites
|
assembled machine code byte-for-byte against `go tool asm` (relocation sites
|
||||||
masked), reporting any encoding drift.
|
masked), reporting any encoding drift.
|
||||||
|
|
||||||
@@ -375,8 +397,15 @@ The child pins its goroutine to the OS thread with `runtime.LockOSThread`
|
|||||||
so the traced thread is the one executing JIT code. The REPL provides
|
so the traced thread is the one executing JIT code. The REPL provides
|
||||||
single-step, register inspection (GPR + YMM/XMM via `PTRACE_GETFPREGS`),
|
single-step, register inspection (GPR + YMM/XMM via `PTRACE_GETFPREGS`),
|
||||||
label resolution, named buffer allocation with pattern filling
|
label resolution, named buffer allocation with pattern filling
|
||||||
(`--buf name:size:pattern` — zero, ones, seq, or hex), and breakpoint
|
(`--buf name:size:pattern`: zero, ones, seq, or hex), and breakpoint
|
||||||
management.
|
management. Breakpoints accept conditions
|
||||||
|
(`break <label> if <reg> <op> <val>`, including register-against-register
|
||||||
|
comparisons), and hardware watchpoints work on all four architectures.
|
||||||
|
For non-interactive use, `--script` runs REPL commands from a file (or
|
||||||
|
stdin) and exits, `--timeout` kills the debuggee when a run hangs (the
|
||||||
|
watchdog is armed before the ptrace attach, so a sandboxed debuggee cannot
|
||||||
|
block it), and `--cover` runs to completion with a breakpoint on every
|
||||||
|
label and reports which blocks executed.
|
||||||
|
|
||||||
## Extension points
|
## Extension points
|
||||||
|
|
||||||
|
|||||||
+35
-8
@@ -48,7 +48,9 @@ an error-severity diagnostic is found.
|
|||||||
Rules: `unknown-instruction`, `operand-count`, `undefined-label`,
|
Rules: `unknown-instruction`, `operand-count`, `undefined-label`,
|
||||||
`duplicate-label`, `missing-ret`, `missing-textflag-include`,
|
`duplicate-label`, `missing-ret`, `missing-textflag-include`,
|
||||||
`abi-argsize`, `unreachable-code`, `register-clobber`,
|
`abi-argsize`, `unreachable-code`, `register-clobber`,
|
||||||
`funcdata-pcdata`.
|
`funcdata-pcdata`, `unused-label`, `invalid-textflag`,
|
||||||
|
`stack-imbalance`, `register-width-mismatch`, `abi0-register-args`,
|
||||||
|
`nonportable-register-name` and `unencodable-instruction`.
|
||||||
|
|
||||||
## `gasm asm [--format raw|elf|goobj] [-p pkg] [-o out] <file>`
|
## `gasm asm [--format raw|elf|goobj] [-p pkg] [-o out] <file>`
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ Assemble FILE, map it into executable memory, and run dynamic checks.
|
|||||||
| `--smoke` | Call each NOSPLIT function with zeroed args |
|
| `--smoke` | Call each NOSPLIT function with zeroed args |
|
||||||
| `--call <func>` | Invoke a single function with `--buf` instead of the sweeps |
|
| `--call <func>` | Invoke a single function with `--buf` instead of the sweeps |
|
||||||
| `--buf <spec>` | Buffer spec for `--call`: `name:size:pattern[,name:size:pattern]` |
|
| `--buf <spec>` | Buffer spec for `--call`: `name:size:pattern[,name:size:pattern]` |
|
||||||
|
| `--args <spec>` | Scalar args for `--call`: `name=value[,name=value]` (decimal or `0x` hex) |
|
||||||
| `--repeat <n>` | Number of times to repeat a `--call` invocation (default: 1) |
|
| `--repeat <n>` | Number of times to repeat a `--call` invocation (default: 1) |
|
||||||
|
|
||||||
The `--fuzz` mode runs each function in a subprocess; a partial function
|
The `--fuzz` mode runs each function in a subprocess; a partial function
|
||||||
@@ -85,18 +88,23 @@ partial functions with valid data instead.
|
|||||||
The `--call` mode parses the `// func` signature, allocates the requested
|
The `--call` mode parses the `// func` signature, allocates the requested
|
||||||
buffers (`zero`, `ones`, `seq`, or a hex blob), builds the ABI0 argument
|
buffers (`zero`, `ones`, `seq`, or a hex blob), builds the ABI0 argument
|
||||||
block with buffer pointers/lengths/capacities at the matching parameter
|
block with buffer pointers/lengths/capacities at the matching parameter
|
||||||
offsets, and prints the arg block before and after the call — showing
|
offsets, and prints the arg block before and after the call, showing
|
||||||
return values and any output written to the buffers.
|
return values and any output written to the buffers. Scalar parameters
|
||||||
|
are supplied with `--args` (decimal, or `0x` hex) at their ABI0 offsets.
|
||||||
|
|
||||||
## `gasm debug --func <name> [--buf spec] <file.s>`
|
## `gasm debug [--func <name>] [--buf spec] [--script file] <file.s>`
|
||||||
|
|
||||||
Interactive debugger for JIT-assembled functions (amd64, arm64, riscv64, loong64). Requires a
|
Interactive debugger for JIT-assembled functions (amd64, arm64, riscv64,
|
||||||
compiled binary on `$PATH` (not `go run`).
|
loong64). Requires a compiled binary on `$PATH` (not `go run`).
|
||||||
|
|
||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `--func` | Function to debug (required) |
|
| `--func` | Function to debug (required) |
|
||||||
| `--buf` | Buffer spec: `name:size:pattern[,name:size:pattern]` |
|
| `--buf` | Buffer spec: `name:size:pattern[,name:size:pattern]` |
|
||||||
|
| `--args <file>` | File containing the ABI0 argument block |
|
||||||
|
| `--script <file>` | Run REPL commands from a file (one per line) and exit; `-` reads stdin |
|
||||||
|
| `--timeout <dur>` | Kill the debuggee after this duration (e.g. `30s`); for headless `--script` runs |
|
||||||
|
| `--cover` | Run to completion with breakpoints on every label and report which blocks executed |
|
||||||
|
|
||||||
REPL commands:
|
REPL commands:
|
||||||
|
|
||||||
@@ -146,6 +154,23 @@ each function's labels, their offsets, and the block boundaries. This is
|
|||||||
the static structure; for runtime execution counts, use `gasm verify
|
the static structure; for runtime execution counts, use `gasm verify
|
||||||
--fuzz` which exercises the code paths.
|
--fuzz` which exercises the code paths.
|
||||||
|
|
||||||
|
## `gasm audit-instructions`
|
||||||
|
|
||||||
|
Compare the gasm amd64 encoder against the installed `go tool asm` and
|
||||||
|
print the diff: superset encodings (gasm-only spellings, shippable via
|
||||||
|
`gasm asm --format goobj`), known-but-unencodable names (the encoder
|
||||||
|
backlog) and go-only names (feature gaps). The Go side is probed
|
||||||
|
black-box with a battery of operand shapes per mnemonic, so the audit
|
||||||
|
tracks whatever toolchain `go env GOROOT` provides.
|
||||||
|
|
||||||
|
## `gasm scaffold differential <file.s>`
|
||||||
|
|
||||||
|
Print a differential test skeleton for every `// func` signature in
|
||||||
|
FILE. The generated test seeds random states, drives the kernel and a
|
||||||
|
portable reference (`<name>Portable`), and compares outputs
|
||||||
|
byte-for-byte. Write the reference bodies, place the file in the
|
||||||
|
kernel's package, and run it in CI.
|
||||||
|
|
||||||
## `gasm lsp`
|
## `gasm lsp`
|
||||||
|
|
||||||
Run the language server over standard input/output (JSON-RPC 2.0 with
|
Run the language server over standard input/output (JSON-RPC 2.0 with
|
||||||
@@ -154,5 +179,7 @@ associate it with `.s` files. The target architecture is inferred from
|
|||||||
the file-name suffix (`_amd64.s`, `_arm64.s`, `_riscv64.s`,
|
the file-name suffix (`_amd64.s`, `_arm64.s`, `_riscv64.s`,
|
||||||
`_loong64.s`).
|
`_loong64.s`).
|
||||||
|
|
||||||
Provides: completion, hover, document symbols, diagnostics, and
|
Provides: completion, hover, document symbols, diagnostics, semantic
|
||||||
semantic-token highlighting.
|
tokens, go-to-definition, find references, rename, document formatting,
|
||||||
|
inlay hints, code actions, signature help, document highlights, and
|
||||||
|
workspace symbol search.
|
||||||
|
|||||||
+2
-2
@@ -14,7 +14,7 @@ why, the options on the table, and the trigger that should reopen it.
|
|||||||
would have required either `golang.org/x/tools` or an in-house parser), the
|
would have required either `golang.org/x/tools` or an in-house parser), the
|
||||||
resolver reads the **GOOBJ data directly** from the target package's `.a`
|
resolver reads the **GOOBJ data directly** from the target package's `.a`
|
||||||
archive. The `.a` file contains a `_go_.o` member whose GOOBJ s is the
|
archive. The `.a` file contains a `_go_.o` member whose GOOBJ s is the
|
||||||
same one gasm writes — the parser reuses the same layout (`blkSymdef`,
|
same one gasm writes; the parser reuses the same layout (`blkSymdef`,
|
||||||
`blkNonpkgdef`, the string table), so no new dependency was needed.
|
`blkNonpkgdef`, the string table), so no new dependency was needed.
|
||||||
|
|
||||||
**How it works.**
|
**How it works.**
|
||||||
@@ -23,7 +23,7 @@ same one gasm writes — the parser reuses the same layout (`blkSymdef`,
|
|||||||
2. `extractGOOBJ` reads the ar archive, finds the `_go_.o` member, skips
|
2. `extractGOOBJ` reads the ar archive, finds the `_go_.o` member, skips
|
||||||
the `"go object …\n!\n"` preamble and parses the GOOBJ header.
|
the `"go object …\n!\n"` preamble and parses the GOOBJ header.
|
||||||
3. `goobjFile.symbols()` walks `blkSymdef` and `blkNonpkgdef` in definition
|
3. `goobjFile.symbols()` walks `blkSymdef` and `blkNonpkgdef` in definition
|
||||||
order — the same order the linker uses — to build the symbol → index
|
order (the same order the linker uses) to build the symbol-to-index
|
||||||
mapping.
|
mapping.
|
||||||
4. `resolveExternalSymbols` wires the resolved `{PkgIdx, SymIdx}` into the
|
4. `resolveExternalSymbols` wires the resolved `{PkgIdx, SymIdx}` into the
|
||||||
GOOBJ emission.
|
GOOBJ emission.
|
||||||
|
|||||||
+17
-9
@@ -5,7 +5,7 @@ Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrb
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **Go** 1.27+ with `toolchain go1.27.0`
|
- **Go** 1.27+ with `toolchain go1.27.0`
|
||||||
- **just** — the command runner; every task below is a just recipe
|
- **just**, the command runner; every task below is a just recipe
|
||||||
- No external dependencies beyond the Go toolchain
|
- No external dependencies beyond the Go toolchain
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -14,12 +14,17 @@ Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrb
|
|||||||
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
|
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
|
||||||
cd gasm-devkit
|
cd gasm-devkit
|
||||||
just install # go mod download
|
just install # go mod download
|
||||||
just build # go vet + gofmt — must pass with zero output
|
just build # go vet + gofmt, must pass with zero output
|
||||||
just test # full suite, race detector, 80 % coverage gate
|
just test # full suite, race detector, 80 % coverage gate
|
||||||
```
|
```
|
||||||
|
|
||||||
## Just Recipes
|
## Just Recipes
|
||||||
|
|
||||||
|
### `just install`
|
||||||
|
|
||||||
|
`go mod download`. The only module dependency, `golang.org/x/arch`, is
|
||||||
|
used in tests only.
|
||||||
|
|
||||||
### `just build`
|
### `just build`
|
||||||
|
|
||||||
Runs `go vet ./...` and checks `gofmt -l .` produces no output. This is
|
Runs `go vet ./...` and checks `gofmt -l .` produces no output. This is
|
||||||
@@ -28,10 +33,13 @@ the minimum bar before any commit.
|
|||||||
### `just test`
|
### `just test`
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go test -race -count=1 -coverprofile=coverage.out ./...
|
go test -race -count=1 ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
Plus an `awk` gate that fails if total coverage is below 80 %.
|
Plus a coverage run over the ten analysable packages (arch, asm, ast,
|
||||||
|
format, lexer, lint, lsp, parser, token, verify; `debug` and `cmd/gasm`
|
||||||
|
need hardware or are CLI glue) and an `awk` gate that fails if total
|
||||||
|
coverage is below 80 %.
|
||||||
|
|
||||||
### `just fmt`
|
### `just fmt`
|
||||||
|
|
||||||
@@ -61,7 +69,7 @@ embedded via `-ldflags "-X main.version=..."`.
|
|||||||
Regenerates the architecture instruction tables in `arch/` by parsing
|
Regenerates the architecture instruction tables in `arch/` by parsing
|
||||||
the Go toolchain's own assembler source
|
the Go toolchain's own assembler source
|
||||||
(`$GOROOT/src/cmd/internal/obj/<arch>/anames.go`). Requires a Go
|
(`$GOROOT/src/cmd/internal/obj/<arch>/anames.go`). Requires a Go
|
||||||
installation. Output is committed — no runtime dependency on the
|
installation. Output is committed, with no runtime dependency on the
|
||||||
toolchain.
|
toolchain.
|
||||||
|
|
||||||
### `just uninstall`
|
### `just uninstall`
|
||||||
@@ -72,15 +80,15 @@ Removes `coverage.out`, the `gasm` binary, and `*.test` artefacts.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
go test -run TestVexGroundTruth ./asm/
|
go test -run TestVexGroundTruth ./asm/
|
||||||
go test -run TestDifferentialLZ4Fuzz ./verify/
|
go test -run TestGroundTruthBasic ./verify/
|
||||||
go test -run TestFLACDecorrelate ./verify/
|
|
||||||
go test -run TestGOObjectLinkAndRun ./asm/
|
go test -run TestGOObjectLinkAndRun ./asm/
|
||||||
|
go test -run TestFuzzWideCopy ./verify/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Debugger Note
|
## Debugger Note
|
||||||
|
|
||||||
`gasm debug` spawns a child process from the binary on `$PATH`. It does
|
`gasm debug` spawns a child process from the binary on `$PATH`. It does
|
||||||
not work with `go run` — install first:
|
not work with `go run`; install first:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
just install-bin
|
just install-bin
|
||||||
@@ -97,7 +105,7 @@ ast/ Abstract syntax tree
|
|||||||
parser/ Line-oriented parser
|
parser/ Line-oriented parser
|
||||||
arch/ Register and instruction tables (generated)
|
arch/ Register and instruction tables (generated)
|
||||||
lint/ Static analysis rules
|
lint/ Static analysis rules
|
||||||
s/ Canonical formatter
|
format/ Canonical formatter
|
||||||
lsp/ Language Server Protocol server
|
lsp/ Language Server Protocol server
|
||||||
asm/ Standalone assembler, encoder, object emitters
|
asm/ Standalone assembler, encoder, object emitters
|
||||||
verify/ JIT execution, differential testing, ABI checks
|
verify/ JIT execution, differential testing, ABI checks
|
||||||
|
|||||||
Reference in New Issue
Block a user