By-VALUE Variant parameters are miscompiled (silent garbage)
Found while building pylib (2026-07-19): procedure show2(v: Variant) —
by value, no const — compiles but the callee reads garbage: show2(2)
printed 4264384, and a variant VAR passed by value printed its TAG.
const v: Variant (by-ref) is correct and is what pylib uses. Either
implement true 16-byte by-value marshalling or reject by-value Variant
params with a diagnostic. Repro: scratchpad p7.pas pattern in the
feature-nilpy-list session log.
Root cause (2026-07-19)
Two halves, both needed:
-
Callee. A Variant is 16 bytes, so
AllocParamgives its slot POINTER size — the slot holds an address, never the value. ButIR_LEA'sskParamdisjunct ("this slot holds a pointer the caller passed") listedIsRef / IsArray / frozen-string / tySetand nottyVariant, so a by-value Variant param tookleaof its own slot.const v: Variantwas unaffected because the parser marks a const record/variant paramIsRef, which the disjunct already covered — exactly why const worked and by-value did not. Missing in all six backends (x86-64, i386, aarch64, arm32, riscv32, xtensa). -
Caller.
IRLowerCallArg's variant-boxing branch fired only for a NON-variant arg. A variant arg to a by-value Variant param therefore passed the caller's own address, giving reference semantics: a callee write would have leaked out once (1) was fixed. The branch now also fires for a variant arg when the param is notIsRef, routing it through a hidden temp. The synthesizedtmp := arglowers to the existing ARC-correct 16-byteIR_VAR_STORE(retain-before-release), andtmpis an ordinary variant local, so the epilogue'sPXXVarClearreleases it.
Verification
test/test_variant_byvalue_param.pas (new, wired into make test): reads,
callee writes NOT reaching the caller, var still writing back, several
variant params in one call, literals, function return, float payload.
Byte-identical output on x86-64 / i386 / aarch64 / arm32.
Gate: --tier full GREEN (1523/1523, self-host fixedpoint) + self-host
byte-identical.