← board

Fell out of bug-a-a-variant-converted-to-ansistring-leaks-whenever-the-result-is-a-temporary as a residual, and is deliberately NOT closed by it. Filed so the question has an owner rather than sitting as a caveat in a test header.

What was measured

test/test_variant_string_temp_leaks.pas, 400 trips over nine arms, live at exit, before and after that fix:

target before after
x86-64 1549 2
aarch64 1549 2
i386 3616 1
arm32 3856 2
riscv32 364 1

The fix cleared every target, so nothing here is currently leaking and this is not urgent. What it exposes is that the targets did not agree BEFORE it, and they had no reason to disagree: the defect it fixed lives in shared IR, so all five should have leaked the same amount. Two of them leaked more than twice as much and one leaked a quarter as much.

The allocation COUNT differs too, which a leak alone does not explain: i386 and riscv32 ran 8671 allocations before the fix and 6850 after, arm32 7707 -> 5411, while x86-64 and aarch64 sat at 5411 both times. A pure ownership fix should not move how many times the program allocates.

Why it matters even though everything is green

The nearby release-matrix work (bug-a-scope-exit-release-matrix-has-four-holes-left-on-i386-and-arm32) found that i386 had no variant, promotable-int or record arm at all. These numbers are consistent with more of that class still being present and currently MASKED — masked because the arms in this program now balance, not because the holes were filled. A masked hole reappears the moment a shape reaches it by another route.

Where to start

Not from this program, which mixes nine arms. Split it: run each arm alone on each target and find which arm produces the divergence. The arms are already separate procedures. riscv32 being LOW rather than high is the most informative row — a target that leaks less than the shared-IR defect can explain is either freeing something it should not, or not allocating something the others do, and the allocation-count column says the second is in play.

Do not treat "all five are under the bound now" as an answer. That is the condition under which this is cheap to investigate, not evidence there is nothing there.

Narrowed by a negative result (frankB, 2026-09-01) — NOT the general release matrix

frankB swept 20 shapes that measure clean on x86-64 across all five targets, 1000 trips each, at 42507851cdde: interfaces (local, reassigned, function result, in a dyn array, as a parameter), 2-D dyn arrays including shrink and regrow, for..in over strings and over records, a class with a destructor, try/finally, an open-array parameter, a concat loop, variant+promo in one record, 3-deep nesting, inherited managed fields, and dyn-array-of-variant and dyn-array-of-promo as record members.

Every cell identical across x86-64 / i386 / arm32 / riscv32 / aarch64, and every one clean (1 to 10). No spread at all.

That matters because it removes the hypothesis this ticket was filed under. If the scope-exit release matrix were broadly short on i386 and arm32, at least one of twenty shapes covering interfaces, nested dyn arrays, destructors, for..in and finally should have shown it. So the spread is specific to the variant string-temp family rather than general to managed memory on those targets, and the search should start there rather than in the release matrix.

The harness was shown to reflect the target rather than falling back, with a positive instance rather than an assertion: a member-array run produced 12 on i386 and riscv32 against 7 on x86-64 and aarch64 — a real 32-vs-64-bit split from the promo stride. So the identical rows are identical because the counts match, not because one binary ran five times. That is the check that makes a 20-shape zero mean something.