← board

C: large (>16-byte) record passed by value gives garbage in the callee

A small record by-value param works (struct {int a;} -> 42). A 24-byte record (struct B {long a,b,c;}) passed by value reads garbage in the callee: int g(struct B s){return (int)s.b;} with s.b=42 returns 200.

Repro:

struct B { long a, b, c; };
int g(struct B s) { return (int)s.b; }      /* want 42, gets 200 */
int main(void){ struct B x; x.a=1; x.b=42; x.c=3; return g(x); }

Impact: blocks C-varargs va_list passing (lua's luaL_error -> lua_pushvfstring(L, fmt, argp) hands a 24-byte va_list by value). Local va_arg (int/long/ptr/string) already works; only passing the va_list to another function hits this. Likely the by-value copy / SysV register-vs-stack classification for records >16 bytes in the C call path.

Root cause (2026-06-26)

Threshold is >8 bytes, not 24: 8B record param works, 16B+ reads garbage (struct{long a,b} s.b=42 -> 120).

This is the System V struct-by-value-param ABI, which pxx's C frontend does not model for records >8 bytes. Blocks va_list passing (24B va_list -> lua luaO_pushvfstring).

Fix design (by-ref + caller copy; true C by-value)

  1. C frontend: a record param is isRef:=True so callee field access DEREFs ([[slot]+off]). C-only (cparser), so Pascal self-host stays byte-identical.
  2. Caller (IRLowerCallArg): a C record arg ALWAYS copies to a hidden temp and passes &temp (not &caller's original) — true by-value (callee mutations stay local). Must distinguish a C by-value record param from a Pascal var record param (genuine by-ref, NO copy): needs a per-param "by-value record" marker (or gate on the callee being a C/CProgramMode proc) so the shared Pascal path is untouched.
  3. Verify: 16B/24B record param == gcc; va_list passing == gcc; then lua RUNS.