← board

c-testsuite 00204: calling-convention battery (structs 1..17 bytes by value, HFAs, varargs)

Failing test

Approach

Re-run after bug-c-init-designated-and-nested + bug-c-float-single-precision land (its structs use string/float inits); then diff section by section vs .expected and file/fix per size class. x86-64 SysV first, then cross targets.

Gate

Drop 00204.c from test/c-conformance/pxx.skip; runner green.

Triage 2026-07-07

00204 COMPILES; the first "Arguments:" section prints BLANK where the struct fields (struct s1 { char x[1]; } = {"0"} ... s17) should appear — so passing a struct BY VALUE drops its data across the size classes. This is the whole struct-by-value ABI battery (1..17-byte structs, HFA float structs, structs through ...), not a single bug. v180 fixed the 8-byte case; this exercises every class + register-return + varargs-of-struct. Large, deep ABI work per size class (SysV first, then cross) — focused multi-step session.

2026-07-08 (fable-c) — %Lf landed; scope narrowed to HFA float structs

Progress on 00204 after the init/float tickets cleared:

Scope sharpened 2026-07-09 (cfront-agent) — gap is VARARGS-only, non-varargs HFA works

Retested at HEAD (217/220 conformance). Confirmed by direct repro vs gcc oracle:

This is a focused variadic-ABI + va_arg(struct) feature, NOT a broad struct-ABI rewrite. Non-varargs struct-by-value (all size classes) + HFA are done.

Codegen map + minimal repro 2026-07-09 (cfront-agent) — released for a focused session

Minimal repro (fails vs gcc): a variadic fn that va_args a struct —

struct FF{float x,y;}; struct S9{char s[9];};
void vp(int n,...){ va_list ap; va_start(ap,n);
  struct FF f=va_arg(ap,struct FF); struct S9 c=va_arg(ap,struct S9);
  printf("%.1f,%.1f %s\n",f.x,f.y,c.s); va_end(ap); }
// vp(1,f,c) -> pxx "ff -0.0,0.0 s9 <garbage>"; gcc "3.1,4.4 ABCDEFGH"

Root: va_arg(ap, struct T) (cparser.inc:506-557) picks __pxx_va_arg_gp (TypeIsFloat(tyRecord)=false), which returns a pointer to ONE 8-byte GP slot; then AN_DEREF with ASTTk=tyRecord. So a struct is read as a single 8-byte scalar — wrong for >8-byte structs (S9=9B needs 2 slots) and it doesn't copy RecSize. The variadic CALLER side (IR_CALL arg marshalling, ir_codegen.inc:3319-3360) also treats a struct arg as a single 8-byte GP value.

Since 00204 is all-pxx (its myprintf is pxx, structs never cross to glibc), the fix does NOT need true SysV SSE/HFA classification — it needs the variadic struct CALLER and va_arg(struct) to be mutually consistent through the GP save area: caller places a struct's ceil(RecSize/8) eightbytes into consecutive GP regs/overflow; va_arg(struct T) returns a pointer to the next ceil(RecSize/8) GP slots and copies RecSize bytes (AN_DEREF tyRecord must memcpy RecSize). (Non-varargs struct-by-value + HFA already work — see prior note.)

Codegen sites (x86-64): IR_CALL arg classify ir_codegen.inc:3319-3360 (struct→GP single 8B today); float XMM machinery reusable at 3442/3481; struct return via hidden dest symtab.inc:5169. va_arg helpers __pxx_va_arg_gp/_fp/_cross* + __pxx_va_start_impl (cparser.inc:506-639). Field walk: UClsFBase/UClsFCount/ UFldTk/UFldOff_ (symtab.inc). Bounded to x86-64 first; cross backends untouched.

Released to unfinished (scoped, not landed — NO compiler change this session, so the self-host gate is untouched). Next picker: a focused variadic-struct session.

RESOLVED 2026-07-09 (A+B+C agent) — variadic struct = one POINTER slot, double-deref

Root cause was SIMPLER than the "ceil(RecSize/8) eightbytes" plan above (that analysis was wrong). The C caller ABI already marshals EVERY by-value struct arg — named or variadic — as a POINTER to a private copy in ONE GP/word slot (IRLowerCallArg, ir.inc ~1662: CProgramMode records are always copied to a temp and passed by address). So the caller side needed NO change. The only bug was va_arg(ap, struct T): it derefed the slot ADDRESS as a record (reading the pointer's 8 bytes as struct data) instead of loading the pointer OUT of the slot first. Fix = one extra deref: the record va_arg path now emits *(struct T*)(*(void**)__pxx_va_arg_gp(&ap)) — deref the slot to get the pointer, deref the pointer to get the record; assignment copies RecSize. One GP slot consumed regardless of struct size (S9=9B still one slot). Applied uniformly across x86-64 / aarch64-cross / 32-bit-cross paths in cparser.inc (the by-pointer ABI is target-agnostic; 32-bit passes PTR size to __pxx_va_arg_cross32, not the struct size). NO backend / ir_codegen edits — pure cparser frontend change.

Gate met: minimal repro == gcc; 00204 byte-identical, dropped from pxx.skip; make test-c-conformance = 218/0/2; self-host fixedpoint byte-identical (one-step converge, incl --threadsafe); test-core green (regression test test/cvariadic_struct_b208.c → exit 42, wired in); quick GREEN; lua GREEN; cross backends untouched. Stable re-pinned v177→v178 so B/C get the fix. The pre-existing crtl %.1f rounding quirk (4.75→4.7 vs gcc 4.8) is unrelated (Track B, separate).

COPY-PASTE KICKOFF PROMPT (fresh session, variadic struct passing)

You are Track A+B+C (compiler core + C frontend), on master, sole-A confirmed (you may self-resolve shared-internals changes). Task: implement C variadic struct passing + va_arg(struct) and turn c-testsuite 00204 green. Read this ticket first — scope, repro, root cause, and codegen map are already settled; do NOT re-derive them.

VERIFIED FACTS (do not re-check):

MINIMAL REPRO (fails today; gcc prints "3.1,4.4 ABCDEFGH"): #include <stdarg.h> extern int printf(const char*,...); struct FF{float x,y;}; struct S9{char s[9];}; void vp(int n,...){ va_list ap; va_start(ap,n); struct FF f=va_arg(ap,struct FF); struct S9 c=va_arg(ap,struct S9); printf("%.1f,%.1f %s\n",f.x,f.y,c.s); va_end(ap); } int main(){ struct FF f={3.1f,4.4f}; struct S9 c={"ABCDEFGH"}; vp(1,f,c); return 0; } pxx today: "ff -0.0,0.0 s9 <garbage>".

ROOT CAUSE:

APPROACH (x86-64 first; keep GP-consistent, no SSE classification):

  1. va_arg(struct T): when vt=tyRecord, return a pointer to the next ceil(RecSize/8) GP slots (advance gp_offset / overflow_arg_area by that many 8-byte slots, honoring the reg-save-area→overflow transition the scalar path already implements), and copy RecSize bytes to a result temp. Verify AN_DEREF with ASTTk=tyRecord actually memcpy's RecSize (if not, materialize a temp + record-copy). May need a size-parametrized helper (mirror __pxx_va_arg_cross32's size arg) or a new __pxx_va_arg_agg(ap, size).
  2. Variadic CALLER: a struct passed as a variadic arg must place its ceil(RecSize/8) eightbytes into consecutive GP arg regs (rdi..r9), overflow to the stack, so they land contiguously in the callee's GP save area / overflow. Ensure the AL vector-count (ir_codegen.inc ~3481) is unaffected (structs use GP, contribute 0 to AL).
  3. Confirm caller and va_arg agree on slot count and ordering for 1-slot (FF, 8B) and 2-slot (S9, 9B) structs. Test the minimal repro FIRST, then 00204.

CODEGEN SITES (investigator map):

GATE: minimal repro matches gcc; drop 00204 from test/c-conformance/pxx.skip; make test-c-conformance = 218 pass / 0 fail / 2 skip; self-host fixedpoint BYTE-IDENTICAL; quick tier + lua/core green; cross targets unaffected (this is x86-64-only — do NOT edit ir_codegen386/arm32/aarch64/riscv32/xtensa). If any codegen changed the stable binary needs it: make stabilize then make pin (watch pin.log; verify VERSION advanced). Commit with a regression test (test/cvariadic_struct_bNNN.c → exit 42, wire into test-core), update this ticket, board-md, push.

LANDMINES: