# 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 ` 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:** resolved for riscv64 (validated end to end under qemu-user) and arm64 (fix in place, consistent with the observed frame convention); open for loong64 until hardware validation. **Root cause (found 2026-08-31).** The trampolines advanced SP past the leave-address slot after loading it, while the assembled kernels read their first argument at SP+8 per the frame convention (the amd64 path already kept SP on that slot). Removing the advance fixed riscv64 immediately (plain and checked ABI tests pass under qemu-user); the arm64 kernel's pre-fix trace showed exactly the same SP+8 reading. The apparent arm64/loong64 "crashes in the JIT" turned out to be dominated by an unrelated instability: the Go 1.26 and 1.27 runtimes crash under qemu-user arm64 emulation (GC worker start, identical signature with the JIT tests skipped, both qemu 7.2 and 10.2), and the Go loong64 runtime does not start at all. `gasm verify` therefore keeps loong64 kernels on the ground-truth path until hardware validation; the GOARCH-guarded tests (`verify/jit_arch_test.go`, `verify/abi_arch_test.go`) are the hardware validation entry point. **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.