2026-07-20 16:28:00 +02:00
|
|
|
# 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
|
|
|
|
|
|
2026-08-08 16:18:56 +02:00
|
|
|
**Status:** resolved (v0.29.0+, 2026-08-07).
|
2026-07-20 16:28:00 +02:00
|
|
|
|
2026-08-08 16:18:56 +02:00
|
|
|
**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`
|
2026-08-29 17:12:53 +02:00
|
|
|
archive. The `.a` file contains a `_go_.o` member whose GOOBJ s is the
|
2026-08-30 10:40:25 +02:00
|
|
|
same one gasm writes; the parser reuses the same layout (`blkSymdef`,
|
2026-08-08 16:18:56 +02:00
|
|
|
`blkNonpkgdef`, the string table), so no new dependency was needed.
|
2026-07-20 16:28:00 +02:00
|
|
|
|
2026-08-08 16:18:56 +02:00
|
|
|
**How it works.**
|
2026-07-20 16:28:00 +02:00
|
|
|
|
2026-08-08 16:18:56 +02:00
|
|
|
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
|
2026-08-30 10:40:25 +02:00
|
|
|
order (the same order the linker uses) to build the symbol-to-index
|
2026-08-08 16:18:56 +02:00
|
|
|
mapping.
|
|
|
|
|
4. `resolveExternalSymbols` wires the resolved `{PkgIdx, SymIdx}` into the
|
|
|
|
|
GOOBJ emission.
|
2026-07-20 16:28:00 +02:00
|
|
|
|
2026-08-08 16:18:56 +02:00
|
|
|
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-29 14:26:08 +02:00
|
|
|
|
2026-08-30 22:27:20 +02:00
|
|
|
## 2026-08-30 non-amd64 JIT execution trampolines
|
|
|
|
|
|
2026-08-31 12:13:43 +02:00
|
|
|
**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.
|
2026-08-30 22:27:20 +02:00
|
|
|
|
|
|
|
|
**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 14:26:08 +02:00
|
|
|
## 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.
|