C compound literals (struct S){...} — file scope SIGSEGVs, init battery fails
- Type: feature/bug. Track C.
- Found: 2026-07-06 c-testsuite run.
Failing tests
- 00149:
struct S *s = &(struct S){ 1, 2 };at file scope — exit 139 (SIGSEGV) - 00150: nested file-scope compound literal with designators — exit 139
- 00216: init battery — empty structs (
typedef struct {} empty_s;),(empty_s){}compound-literal member init,022octal in init. Compile error "expected C expression". (Also overlaps bug-c-init-designated-and-nested.)
Needed
C99 6.5.2.5: compound literal = anonymous object; at file scope static storage duration (address is a link-time constant), at block scope automatic. Parser accepts something today (149/150 COMPILE then crash) — likely treated as cast of brace list producing garbage pointer.
Gate
Drop 00149.c/00150.c/00216.c from test/c-conformance/pxx.skip; runner green.
Triage 2026-07-07
Not implemented in any position: (struct S){1,2} as a local/inline expression
-> CERR "expected C expression" (parsed as a cast, then the { derails); the
file-scope &(struct S){...} parses but SIGSEGVs. Needs: (1) ParseCPrimary/cast
path to disambiguate (type){...} (compound literal) from (type)expr (cast);
(2) materialize an anonymous object — static storage at file scope, automatic at
block scope — initialize it (reuse the braced-init machinery), and yield its
value / address. Multi-part feature, focused session.
Parser hook located 2026-07-08 (a-agent) — the reuse blocker
The disambiguation point is ParseCUnary's cast branch (cparser.inc ~1447):
after if (CurTok.Kind = tkLParen) and CIsCastAhead parses castTk := ParseCDeclType
and Expect(tkRParen), a following CurTok.Kind = tkBegin ({) means COMPOUND
LITERAL, not a cast — today it falls into ParseCUnary() which sees { and
derails. Add: if CurTok.Kind = tkBegin then <materialize + init + yield lvalue>.
The real blocker is REUSE: the brace-init machinery (record field init, array
element init, nested braces, zero-fill tail) lives INLINE inside
ParseCLocalDeclAST (~2760-2920) and ParseCGlobalVarDecl, not as a callable
"init this lvalue/symbol from the brace list at TokPos" helper. A clean compound-
literal impl wants that factored out first (a CParseBraceInitInto(symIdx, tk, recId) returning an init AST), then:
- block scope: alloc a hidden local of castTk, CParseBraceInitInto it, yield an
AN_IDENT lvalue (so
&(T){..}and(T){..}.fwork). - file scope (00149/00150): alloc a static/global anon object, same init, yield its address as a link-time constant. So the sequencing is: (1) extract the brace-init helper, (2) wire the two compound-literal sites. Step 1 is the bulk and de-risks designated-init too ([[feature-c-designated-init-compound-literals]]). Focused session.
Progress 2026-07-08 (a-agent) — 00149 + 00150 FIXED (file-scope), 00216 remains
File-scope T *p = &(RecType){...} implemented in ParseCGlobalVarDecl: a new
&(...) branch materialises an anonymous static record object (name keyed by the
brace token position so both driver passes dedup to one object), initialises it
via the existing deferred aggregate walker (CAggInit / CEmitDeferredCAggInits —
same path a named struct S g = {...} global uses, so positional AND nested
designated bodies work), then binds p to its address (PendingInit Elem=-2
address-of-sym). 00149 (positional) and 00150 (nested designated {.b=2,.a=1},
&gs1, array member {[0]=1,1+1}) both GREEN. Self-host byte-identical, quick
tier green, conformance 208 pass / 0 fail / 12 skip (was 206). Dropped 00149/00150
from pxx.skip.
Remaining (ticket stays open):
- 00216 — full init battery:
[a ... b]range designators, flex-array members, unnamed struct/union members (-fms-extensions), fn-ptr reloc tables. Overlaps [[bug-c-init-designated-and-nested]]. Errors before reaching the CL work ("stray token at top level: sys_ni") — parse-level, not the CL path. - BLOCK-SCOPE compound literals
(T){...}as an inline expression (the ParseCUnary cast-branch hook, ~1447) — still unimplemented; needed for tcc/zlib-style code, no conformance test isolates it yet.
2026-07-09 (A+B+C agent) — RESOLVED: c-testsuite 00216 GREEN, conformance 220/220/0
00216 is byte-identical to the gcc oracle; dropped from pxx.skip; make test-c-conformance = 220 pass / 0 fail / 0 skip. The task's premise ("only inline
compound literals block 00216") was wrong — reaching 00216 green took SEVEN fixes,
each gcc-verified + self-host byte-identical + a test-core regression (b216-b225):
- Record compound literals
(T){...}— base node AN_COMPOUND_LITERAL=81 + gaps A (postfix), B (whole-record-value elements + CInitPeekElemTk/CAggCurSym/CInitPath save-restore), C (file-scope CL in global array). (b216-b219) - C lexer form-feed/vtab whitespace (#12/#11) — the "cumulative desync" ghost. (b220)
- Anonymous struct/union member braced-designated init — UFldAnonRec group marker, walker re-groups a promoted anon member for an inner brace. (b221)
- Redundant struct-to-struct cast
(struct S)x= identity, not AN_PTR_CAST retag (retag of an address-carried record SEGV'd on copy). (b222) - Range designator
[lo...hi]must not inflate a fn-ptr array's length (sizeof doubled ->t[i]()ran off the end into NULL -> SIGSEGV). (b223) - Flexible array member
T x[]is 0-size in sizeof (was parsed as [1]). (b224) - File-scope struct var initialized by a compound literal
= (T){...}/= ((T){...})(was left zero). (b225)
Also satisfies [[feature-c-designated-init-compound-literals]] record-CL acceptance and resolves [[bug-c-anonymous-member-designated-init]] + [[bug-c-fullfile-cumulative-parser-desync]]. Pinned (compiler changed). Cross targets untouched (frontend/IR only). RESOLVED — moving to done.
2026-07-09 (A+B+C agent) — COMPOUND LITERALS COMPLETE; 00216 blocked by TWO pre-existing NON-CL bugs (superseded — all resolved, see above)
Inline compound literals (T) + braced init are now fully implemented and pinned
(the AN_COMPOUND_LITERAL node from the ATTEMPT note, re-applied + all 3 gaps closed).
Each gcc-verified (exit 42) + self-host byte-identical + regression test in test-core:
- base (b216): flat block-scope — by-value arg,
&(T){..}+->, struct-init RHS, designated(T){.c=5,.a=1}. New node defs.inc=81; ParseCUnary cast-branch hook →CParseCompoundLiteralRec; IRLowerAST/IRLowerAddress emit init chain then IR_LEA temp; IsASTLValue + ResolveNodeRec recognise it. - gap A (b217): postfix
(T){..}.f/->/[i]— splitParseCPostfixTail(node)so the CL node flows through the postfix tail. - gap B (b218): whole-record-VALUE initializer elements (compound literal, struct
lvalue
ls,*ptr,->field,(struct S)cast) — were SILENTLY miscompiled by descending into subfields. Fixed via a non-consuming type peek (CInitPeekElemTk/CInitElemIsWholeRecord) → copy wholesale (IR_COPY_REC). Also fixed two shared-state leaks the nested CL exposed:CInitLocalAggregatenow saves/restoresCAggCurSym;CInitPeekElemTksnapshots/restores the sharedCInitPath*frames the speculative parse would clobber. - gap C (b219): file-scope CL in a global array
struct W g[]={((struct W){f}),f}— works for free once the node exists (the global array walker's emit-mode leaf uses ParseCExpr). Was "expected C expression" on v185; exit 42 now.
Conformance held at 219/0/1 (no regression). feature-c-designated-init-compound- literals' compound-literal acceptance is satisfied by this work.
00216 is NOT yet green — it needs TWO further, PRE-EXISTING, non-CL features
Retested with the CL work in: 00216 no longer desyncs at global_wrap, but still fails. Both remaining blockers reproduce on the PINNED v185 binary (i.e. they predate and are unrelated to compound literals):
- Anonymous struct/union member braced-designated init (
-fms-extensions):union UV {struct {u8 a,b;}; struct S s;}; union UV g = {{.b=7,.a=8}};→ "expected C expression". The anon struct's fields are PROMOTED flat into the union, so the walker treats the union's field[0] as scalara, sees the inner brace as a braced-scalar, and chokes on the.bdesignator. Needs the walker to treat a promoted anonymous aggregate as one member for a matching inner brace. (Positional{{6,5}}and union-level promoted designators{.b=8,.a=7}already work.) - Cumulative parser desync (brace/state imbalance detected at EOF as "stray token at top level" with empty SVal): appears only when NEARLY ALL of 00216's functions are present together (each subset — tcwr, table+multi, SE+zeroinit, foo — compiles fine alone or in pairs; the full set desyncs by one). Not a fixed-array cap (only 15 types; caps are 2048+). A pre-existing accumulation bug in the two-pass driver. → Filed as separate tickets; 00216 stays on pxx.skip until both land. See [[bug-c-anonymous-member-designated-init]] and [[bug-c-fullfile-cumulative-parser-desync]].
2026-07-09 STATUS — 00216 is 4/5 done; ONLY inline compound literals remain (v185)
Pieces 1-4 + piece 3's designators all landed and pinned (v180→v185), each
gcc-verified + self-host byte-identical + regression test in test-core. The single
remaining blocker for c-testsuite 00216 (→ 220/220) is piece 5: inline compound
literals (T){...} — needs an AN_COMPOUND_LITERAL IR node (see the ATTEMPT
note below; the parser-comma hack SEGVs). That node also unblocks 00216's
file-scope global_wrap[] and closes feature-c-designated-init-compound-literals'
compound-literal acceptance. Detailed 5-piece map + status follows.
2026-07-09 (A+B+C agent) — 00216 mapped to 5 sub-features across 4 init paths; range designators LANDED
Retested 00216 at HEAD (v179). It is NOT one gap — it needs 5 distinct sub-features, spread over 4 SEPARATE initializer code paths, each self-host- critical. Precise map (minimal repros each confirmed):
- Range designators
[lo ... hi] = v— the recursive aggregate-init walker (CInitWalkArray, the[designator branch). DONE this session: expands the range, re-seeking the one value's tokens per index; covers local + global struct-member arrays (both route through the walker via CEmitDeferredCAggInits). gcc-verified, test/crange_designator_b210.c → exit 42. Overlapping ranges resolve left-to-right. - Range designators in the GLOBAL SCALAR-array path — a DIFFERENT loop
(
ParseCGlobalVarDecl~5352) gated byCBraceFlatIntInitCountAt, which rejects[lo...hi](only[int]).int g[4]={[0...2]=7}silently zero-fills. TODO: accept the range in the flat-int scanner + the emit loop. - Global FUNCTION-POINTER arrays — typedef arrays DONE 2026-07-09:
fptr t[N] = {a,b}/ unsizedfp t[] = {&sq,&dbl}now work. The fn-ptr global path only recognised INLINE declarators(*t[N])(); a typedef array left the trailing[N]after the name unconsumed (desync). Now that[N]is parsed (wasArr set) and&funcelement prefixes accepted; the branch already emits arrKind=3 proc-address PendingInits (Kind=2 → AN_PROCADDR). gcc-verified, test/cfnptr_typedef_array_b214.c → exit 42, 219/0/1. Designators + ranges in the fn-ptr scanner DONE 2026-07-09 (v185): the scanner tracks a per-element target index (arrTgt[]) and replicates a range's proc across[lo ... hi]; later single[k]=designators override earlier range slots (PendingInit last-wins). The exact 00216 reloc table{[0 ... 2]=&sni, [0]=one, [1]=two, [2]=three}now materialises correctly (prints one/two/three). gcc-verified, test/cfnptr_range_table_b215.c → exit 42. Piece 3 COMPLETE. - Nested designators
.a.j = v— DONE 2026-07-09: after.namepositions to a field, a continuation.sub/[i]now descends the full chain viaCInitDesignatedDescend(pushes path frames, inits the designated leaf or braced subobject), then resumes positionally. Handles record.record.scalar (.a.j), 3-level (.b.c.y), reordered, local + global. gcc-verified, test/cnested_designator_b213.c → exit 42, self-host byte-identical, 219/0/1. - Inline compound literals
(T){...}as expressions —ParseCUnarycast branch (~1719): aftercastTk=ParseCDeclType; Expect(')'), a following{is a compound literal, today it recurses into ParseCUnary and derails. The blocker is REUSE: materialize an anonymous object (automatic at block scope, static at file scope), init it via the brace machinery, yield an lvalue — needs the local brace-init extracted into a callableCParseBraceInitInto. Self-host-fragile (shared init walker). Once this lands, the walker's EMIT-mode leaf (which uses ParseCExpr) gets compound-literal VALUES +&func+ casts for free, unblocking{((struct Wrap){inc}), inc}and.a=(struct A){1,2}.
Order to finish: (2)+(3) global paths, (4) nested designators, then (5) compound literals (biggest + riskiest). (1) is committed. Remaining pieces parked in backlog — each a focused, self-host-verified change.
Block-scope compound literals — AN_COMPOUND_LITERAL IR node PROVEN for flat cases 2026-07-09
Second attempt (the right layer). Added an AN_COMPOUND_LITERAL AST node
(defs.inc = 81) and integrated it — this WORKS for flat block-scope compound
literals and is the design the next session should re-apply:
- defs.inc:
AN_COMPOUND_LITERAL = 81; IVal = hidden record temp sym, Left = init statement chain (AN_SEQ of CMakeZeroLocal + CInitLocalAggregate stores over the temp), recId = Syms[IVal].RecName. - cparser.inc ParseCUnary: after the cast type+
), ifCurTok=tkBeginand the type is a record (castTk=tyRecord, castDepth=0, CTypeBaseRec>=REC_UCLASS_BASE), callCParseCompoundLiteralRec(recId)which allocs the temp, splices zero+stores into one AN_SEQ, and returns the node. (Capture clRecId := CTypeBaseRec BEFORE Expect(')').) - ir.inc IRLowerAST + IRLowerAddress: emit Left (IRMarkStatementNode), then
yield
IR_LEA temp— records are carried BY ADDRESS in this IR, so by-value args / assignment RHS copy from it correctly. This is why the node works where the AST-comma didn't. - ir.inc IsASTLValue and symtab.inc ResolveNodeRec: add the node (lvalue; rec = temp's RecName).
VERIFIED vs gcc (all exit 42): (struct P){3,4} as a by-value arg; &(struct P){1,2,3}
p->field;struct FF f = (struct FF){1.5f,2.5f}; designated(struct P){.c=5,.a=1}. C-mode-gated + inert in Pascal self-host (self-build OK).
REVERTED (kept v185 clean) because three cases were still wrong and one SILENTLY miscompiled — must not land a silent-wrong path. Remaining before it can land:
- Postfix
(T){...}.field/[i]— ParseCUnary's cast branchExits, so the CL result skips ParseCPostfix → "expected C expression". Route the CL node back through the postfix-tail so.f/->/[i]chain onto it. - Nested CL as a designated sub-value
(struct Out){.a = (struct In){...}}— SILENTLY WRONG: the outer walker descends into.a's fields and misassigns the inner CL. The walker's leaf/member path must detect a whole-record value (CL or struct rvalue) for a record-typed field and assign it WHOLE (IR_COPY_REC) instead of descending. (Same "whole-aggregate-value element" gap that.daddr = phdr->daddrneeds.) - File-scope CL in a global array (00216
global_wrap[]) — SEGV: the global record-array walker replays at main via ParseCExpr; a CL temp AllocVar'd during that deferred emit has the wrong CurProc/storage context. File-scope CLs have static storage (C99) — likely needs the static-anon-object path (like done 00149/00150&(T){...}), not the automatic block-scope temp.
Block-scope compound-literal ATTEMPT 1 2026-07-09 — parser-comma approach FAILS at IR lowering
Tried the obvious block-scope impl in ParseCUnary's cast branch: on (recTk){
materialise tmpSym := AllocVar('',tyRecord) (LastTypeRecId=recId), zero via
CMakeZeroLocal, init via CInitLocalAggregate (chain mode), then splice the
zero+element-store AN_ASSIGNs and a terminal AN_IDENT into a right-nested AN_COMMA
so the whole thing is one expression yielding the object. Compiles + self-hosts,
but at RUNTIME:
- by-value use
sum((struct P){3,4})→ SIGSEGV (the record temp reached via the comma isn't materialised/copied correctly by IRLowerCallArg's record path). &(struct P){...}→ "Unsupported linear node in IR codegen" (address-of an AN_COMMA is not lowerable). So the AST-comma trick is the wrong layer: a record temp that must be statement-initialised then used as an lvalue/by-value can't ride inside an expression this IR expects. The right fix is IR-level materialisation — either (a) a dedicated AN_COMPOUND_LITERAL node that IRLowerAST lowers to "emit the init statements into the current block, yield the temp's address/lvalue", or (b) hoist the init like IRLowerCallArg already does for frozen-string-concat args (spill to a hidden local, pass its address). (b) only covers the call-arg position; (a) is general. Reverted; tree stays at v181. This remains the self-host-fragile keystone and the last 00216 blocker.
Assessment 2026-07-08 (cfront-agent) — released; remaining work is deep, needs the factor-out
Confirmed the remaining two pieces are NOT bounded wire-ups:
- Block-scope
(T){...}: the file-scope fix reusedCAggInit/CEmitDeferredCAggInits, but that is a DEFERRED mechanism — it records (sym, brace-token-pos) and replays the init atmainfor globals/statics. A block-scope compound literal must init an AUTOMATIC local INLINE at the expression point, which needs the inline local brace-init path (ParseCLocalDeclAST~2760-2920) as a callableCParseBraceInitInto(lvalue)helper. That factor-out is the bulk and is self-host-fragile (touches the shared init walker). No conformance test isolates block-scope CL yet. - 00216: errors at parse level ("stray token at top level: sys_ni") on range
designators
[a...b]/ flex arrays / unnamed members — belongs with [[bug-c-init-designated-and-nested]], not the CL path. Both want a dedicated focused session (extract the brace-init helper first). Released back to backlog unclaimed; 00149/00150 stay fixed.