← board

riscv32 drops interface releases in six shapes

Measured — riscv32 is the only target that differs

test native / i386 / arm32 / aarch64 riscv32
test_interface_byval_param_no_leak 16 / 16 10 / 16
test_interface_arc freed=3 freed=1
test_interface_arc_exc reassign freed=2, unwind freed=3 freed=1, freed=1
test_interface_as_cast_retains 7 / 7 5 / 7
test_interface_ascast_temp_lifetime prints destroy 7 never destroys
test_interface_call_result_move 9 / 9 8 / 9 (self-returning released 0, want 1)
test_interface_com_value_param after nil freed=1 freed=0

test_interface_byval_param_no_leak is the most informative: const and var parameters release correctly (5/5 both), while plain BY-VALUE parameters release 0 of 1, 4 of 5 and 49 of 50. A by-value interface parameter carries a +1 the callee owns and must drop; riscv32 drops it only when something else happens to.

Likely one cause, not six

Every failing shape is "an interface handle held in a slot riscv32's epilogue does not walk": a by-value PARAM (not a local), an as-cast TEMP, a call-result temp. riscv32's EmitProcEpilog arm is also visibly the thinnest of the five — it has the static-array and scalar-AnsiString arms and (since 2026-08-21) the dynamic-array one, but no variant arm, no record-with-managed-fields arm, and no promotable-int arm, all of which the other four carry. Diff riscv32's arm list against aarch64's; the differences ARE the bug list, exactly as [[bug-a-no-dyn-array-scope-exit-release-on-four-backends]] found.

Check first whether riscv32's cleanup loop visits skParam at all — every arm in it is gated Syms[i].Kind = skLocal, and a by-value interface parameter is skParam.

Gate

The seven tests above matching the native answer under tools/run_target.sh riscv32; the 53-test cross differential no worse than baseline; self-host fixedpoint + tools/gate.sh quick.

Resolution (2026-08-21)

All seven tests in the table now produce output identical to the native oracle under tools/run_target.sh riscv32.

One cause, as the ticket suspected — but not the one it named

The ticket said to check whether the cleanup loop visits skParam. It does not need to: every other target gates on skLocal too and releases by-value interface parameters correctly, because a by-value param IS a local by the time the epilogue walks the scope.

The actual cause is one line long: riscv32's arm of the scope-exit release had no COM interface case at all. Not a narrower case, not a wrong register — the arm was absent, so an interface handle in a slot was simply never walked. Every failing shape in the table (by-value param, as-cast temp, call-result temp, value param) is the same slot with a different name.

That was invisible while the release loops lived inline in each of EmitProcEpilog's six branches. After [[refactor-a-the-missing-layer-between-frontends-and-backends]] put all six arms in one procedure, the matrix reads off in one grep:

arm x86-64 i386 arm32 aarch64 xtensa riscv32
COM interface yes yes yes yes was missing
static array of managed yes yes yes yes yes
ansistring yes yes yes yes yes yes
variant yes yes yes was missing
promotable int yes yes was missing
record w/ managed fields yes yes yes was missing
dynamic array yes yes yes yes yes

riscv32's four gaps are all closed here (COM interface, variant, promotable int, record-with-managed-fields), because they are the same omission and fixing one of six while the table is on screen is how the next one gets rediscovered in three months.

Not covered — and now written down

i386 and arm32 still have holes in that table: i386 has no variant, no promotable-int and no record arm; arm32 has no promotable-int arm. Those are silent leaks of exactly this kind and are filed as [[bug-a-scope-exit-release-matrix-has-four-holes-left-on-i386-and-arm32]]. xtensa's row is nearly empty and is Track S work — its exception runtime exists only under the Call0 ABI.

Gate

tools/gate.sh quick GREEN (self-host fixedpoint 111s). All seven listed tests byte-equal to the native oracle on riscv32, plus test_exceptions, test_cross_exception and test_interface_arc unchanged. Cross-target breadth is Track T's, against this sha.

Log