Files
gasm-sdk/docs/DECISIONS.md
T
petrbalvin a5a59d6503
Test / vet (push) Successful in 48s
Test / test (push) Successful in 2m34s
Test / build (push) Successful in 41s
fix(verify): gate JIT verification to amd64 until trampolines are hardened
2026-08-30 22:48:48 +02:00

4.7 KiB

Deferred decisions

Design decisions deliberately postponed, with enough context to pick them up again without re-deriving the analysis. Each entry records what is deferred, why, the options on the table, and the trigger that should reopen it.


GOOBJ external (cross-package) symbol references

Status: resolved (v0.29.0+, 2026-08-07).

Approach taken. Instead of parsing the compiler's iexport data (which 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 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, blkNonpkgdef, the string table), so no new dependency was needed.

How it works.

  1. go list -json -export <pkg> finds the target package's .a file.
  2. extractGOOBJ reads the ar archive, finds the _go_.o member, skips the "go object …\n!\n" preamble and parses the GOOBJ header.
  3. goobjFile.symbols() walks blkSymdef and blkNonpkgdef in definition order (the same order the linker uses) to build the symbol-to-index mapping.
  4. resolveExternalSymbols wires the resolved {PkgIdx, SymIdx} into the GOOBJ emission.

The resolver is invoked automatically when img.Externals is non-empty; it runs go list as a subprocess (consistent with toolchainObjectPreamble which already calls go tool asm). All symbol data is cached per package for the lifetime of the GOOBJ emission.

2026-08-30 non-amd64 JIT execution trampolines

Status: open (blocks runtime verification on arm64, riscv64 and loong64 hosts).

State. The per-architecture trampolines compile for all targets, the kernels they execute are byte-for-byte correct against go tool asm, and under qemu-aarch64 the arm64 kernel demonstrably executes and stores its result correctly. The failure is on the return path into Go code: arm64 and loong64 take a SIGSEGV after the kernel's RET (the Go-side unwind through leaveJIT and its interposed ABIInternal wrapper is the suspect), and riscv64 returns cleanly but with an untouched result area. amd64 is unaffected (the checked trampoline saves and restores BP/R14 and the flow is validated end to end).

Evidence harness. verify/jit_arch_test.go (plain call) and verify/abi_arch_test.go (checked call) are GOARCH-guarded tests; build the test binary per target (GOARCH=arm64 go test -c -o v.test ./verify/) and run it under qemu-aarch64-static from the verify/ directory. A minimal reproducer pattern lives in the qemu exploration notes: verify loads, the kernel executes, the fault follows the return.

Fix direction. Compare the amd64 checked trampoline (GLOBL/DATA raw address, explicit SP/BP/R14 save-restore) against the arm64/riscv64/ loong64 leaveJIT unwind, in particular the interaction with the ABIInternal wrapper that reflect.ValueOf(leaveJIT).Pointer() returns. The plain-call path (no sentinels) fails the same way, so the checked path is not the variable.


2026-08-29 tooling round

  • lint abi0-register-args: flags kernels whose // func parameters are never read from the FP frame. Motivated by a real latent bug: kernels reading arguments from registers pass every test while the autogenerated F.abi0 wrapper happens to leave the caller's register values intact, and break on a toolchain upgrade.
  • lint nonportable-register-name: the RAX/EAX register spellings are a gasm extension; go tool asm rejects them, so files using them only link through the gasm goobj path.
  • lint unencodable-instruction: a mnemonic in the architecture table that asm.Encodable rejects is flagged at edit time instead of failing at assembly time.
  • audit-instructions: black-box diff of the encoder against go tool asm. As of this round the tables fully overlap on names; the audit exists to catch drift in both directions (future supersets and future gaps).
  • scaffold differential: generates the direct-call differential skeleton (two independent seed sets, output and in-place buffer comparison) that a pipeline-level fuzz can never replace.
  • verify --args: scalar arguments for -call, closing the repro gap where only buffers could be supplied.
  • debug --script/--timeout/--cover: headless debugging with a watchdog armed before the ptrace attach (untracing sandboxes hang the attach), and label-level block coverage for the "did my test ever enter that branch" question.
  • Superset policy remains: gasm may accept spellings and encodings go tool asm lacks, but such kernels ship only via gasm asm --format goobj; the audit reports the superset surface. The register-alias superset is warned about by lint because the default go build path cannot consume it.