kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

Backend refactor cross-architecture findings

Date: 2026-07-19
Revision tested: 8d7927db (refactor(opt): make scratch and clobber ownership explicit)

Run

make test-cross TARGET=all DEPTH=full KIT_VM=1 RUN=1

The run covered all 19 configured target selectors and completed in about 3h32m:

Findings

Scope Finding
Hosted x64, O0/O1 Aggregate/sret returns are corrupt across Linux, FreeBSD, Windows, and macOS. x64_copy_bytes() resolves a stack source through R11 while R11 holds the sret destination, redirecting stores into the stack frame.
x64 and RV64, O1 Overflow-result tests miscompile.
Arm32, O1 Two aggregate-return cases abort emission with no instruction-local native temporary.
Windows x64 Packed indirect musttail is rejected at O0 and returns 173 at O1.
Linux RV64 Initialized C TLS returns zero under both glibc and musl at O0/O1.
FreeBSD RV64 dynamic 334 cases per FreeBSD matrix fail before execution because libc.so.7 uses System V ELF OSABI and carries FreeBSD identity in NT_FREEBSD_ABI_TAG; Kit classifies it as Linux. The other 10 failures are the x64 regressions above.

All actual AArch64 cases passed. RV32 toy/parser coverage, all glibc/musl libc suites, and non-x64 front/back amalgam executions also passed.

Linux x64 QEMU-user processes reported SIGSEGV and then hung; the affected test containers were stopped, so the aggregate log records status 137. Native FreeBSD and macOS runs reproduced the segmentation fault.

Artifact

Full log: build/test-logs/test-cross-all-full.log

The tested binaries were built from clean revision 8d7927db. Workspace edits made after that build were not included in this run.

RV64 follow-up

The RV64 failures above had three separate causes:

Targeted validation after the fixes:

This was a targeted RV64 follow-up, not a repeat of the full 19-target matrix.

RV32 follow-up

RV32 uses the same RISC-V NativeTarget implementation as RV64, with xlen=32, so the follow-up emphasized the refactor-sensitive shared paths: temporary and clobber ownership, intrinsic operand locations, call marshalling, small aggregate arguments, TLS, and both RV32 ABIs used by the test harness.

The original full cross log was already clean for RV32: the Toy corpus passed 427 cases with two intentional AArch64-only skips, and the parser corpus passed 970 cases with 64 intentional skips. Current-tree validation added:

No RV32-specific or shared production-code change was required. The shared immediate-location correction made during the RV64 follow-up remains the appropriate architectural fix and is covered by the RV32 overflow probes.

ARM32 follow-up

The clean 8d7927db revision reproduced both ARM32 failures from the original matrix exactly: 130_record_sret_return in the Toy corpus and 6_8_6_4_05_struct_return_mixed_fp_int in the C corpus passed at O0, while O1 aborted after the first three ABI result registers made a fourth temporary unavailable. The shared native emitter must load a frame/stack return part directly into its final ABI register; routing it through a temporary creates a false fifth-register requirement. The current emitter does that direct load. The shared regression now fills an entire four-register result bank, matching the ARM32 failure rather than testing only two parts.

The audit also found two independent contract bugs that the original corpus could not expose reliably because Kit was both caller and callee:

The shared direct-asm binder also used an I64 location as the address carrier for every memory operand. It now selects I32 or I64 from the target pointer width, keeping this target-neutral path valid on ARM32 and RV32.

Validation on the corrected tree:

Artifacts:

X64 follow-up

The hosted x64 failures exposed a broader ownership problem than the original R11 aggregate-copy corruption. Exact-width 3/5/6/7-byte loads and stores chose address and data temporaries through backend-global part_scratch state while the generic x64 memory emitter independently borrowed the same private bank. Those operations now stabilize the complete effective address in an explicitly selected private register for their local phase and use the other private register for chunk data. The backend no longer carries part_scratch.

Call setup had the same hidden-state shape. Stack bias, parallel-copy scratch, tail-call state, and indirect-callee parking now live in a local X64CallMarshalCtx; the old backend-global call_callee_parked flag is gone. The mutating interface is named marshal_call/marshal_ret and fills a NativeCallPhase, making its ownership and lifetime distinct from the pure call_stack_bytes frame-sizing query. Dead call-plan clobber/return mask fields were removed.

The cross-architecture audit then consolidated the shared scratch/clobber model:

Two cross-harness ownership bugs were also corrected while validating x64: FreeBSD/Windows full selectors now pass only the requested VM architecture, and parse startup objects are cached per architecture rather than sharing a mutable parse_start.o across concurrent workers. The x64 execution lane now classifies the IEEE-binary128 semantic fixtures as inapplicable (while retaining the layout/macro probe), matching the actual SysV/Apple x87 and Win64 double-aliased long double ABIs.

Final validation, in the requested platform order: