← board

bug: C→riscv32 by-value record result emits PXXMemMove but the C compile never injects builtinheap

Symptom

Every riscv32 conformance shard (0-6) and every riscv32 cross-run job went red in one sweep and stayed red across every full run since. riscv32-only — arm32 / aarch64 / i386 pass the same jobs.

Deterministic repro (reproduced on borg 2026-07-21, HEAD de2669f2)

tools/testmgr.py --tier full --job 'test-riscv32#src:test/ccross_args.c@1'

fails with:

pascal26:429: error: compiler error: PXXMemMove not found
  near:     end  >>> function PalIn6Any

i.e. ./compiler/pascal26 --target=riscv32 test/ccross_args.c ... cannot compile a C function that returns a record (struct) by value.

Root cause

PXXMemMove is defined in compiler/builtin/builtinheap.pas (line 158). The riscv32 by-value-record-result lowering does FindProc('PXXMemMove') and Error('compiler error: PXXMemMove not found') if it is absent (compiler/symtab.inc:6041/6120, mirrored in ir_codegen_arm32.inc). When the Pascal frontend compiles for riscv32, builtinheap is in scope and the proc is found — that path is done and verified (feature-riscv32-record-function-results, 2026-06-23). When the C frontend compiles for riscv32, builtinheap is NOT injected, so the same codegen path hits the missing proc.

So: C-frontend + riscv32 + by-value record result → PXXMemMove not found. The Pascal path is fine; the gap is the C compile not providing the builtin its own riscv32 record-return codegen depends on.

Newly EXERCISED, not newly introduced

No riscv32 / symtab / builtin / codegen commit landed in the cascade window — the suspects (18790cf7, 862bb4fe) are weeks old, and the code just before the first red is uforth/nilpy/docs. The C cross-conformance matrix (b385a381) added riscv32 coverage earlier; a corpus/shard/timeout shift (candidate: 99736aca per-program timeout rescale) appears to have newly routed a record-returning C program onto riscv32. The underlying gap has existed since C-frontend riscv32 record-return was reachable; nothing regressed in behaviour, coverage caught up to it.

Why arm32 passes (needs confirming in the fix)

arm32 uses the identical FindProc('PXXMemMove') pattern (ir_codegen_arm32.inc:3113) yet its C conformance is green. Either arm32 C compiles DO inject builtinheap (then riscv32 should mirror that), or no arm32 conformance program in the current sharding returns a record by value (then the gap is latent there too, not fixed). The fix should establish which, so it does not just move the red to arm32 on the next corpus reshuffle.

Fix direction (Track A's call)

Ensure the C-frontend riscv32 (and by symmetry arm32/xtensa) record-return path has PXXMemMove available — inject builtinheap into the C compile when the record-copy/record-result lowering can fire, or provide the memmove primitive the C path can always resolve. Do NOT special-case the one failing program.

Scope / handoff

Track A (shared symtab.inc + riscv codegen + builtin injection). The overnight regression-cascade-6906a3416548 stub is superseded by this ticket; the second stale stub regression-cascade-3d46e52fc733 is the OLD (2026-07-20 early, the 461-job flood sha) qemu-collapse event and unrelated — safe to reject.

Also covers test-lua-cross (2026-07-21)

test-lua-cross builds lua for all four cross targets (aarch64 arm32 i386 riscv32); the job is red because its riscv32 leg fails on the same PXXMemMove not found — lua's C hits a by-value struct return under --target=riscv32. So test-lua-cross in the 6906a341 cascade is NOT a separate failure; it is this bug under a non-riscv-looking job name. It will go green with the fix. Current real red surface from this one root: 17 test-*riscv32* jobs + test-lua-cross = 18, matching the cascade exactly.

Log