diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 220a2061..944103b1 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -22957,152 +22957,168 @@ fn cgdot(c: *cgen, n: *node) void = { // element), so the leaf load is `(field.offset)(AX)` either way. // Bypasses cgindex deliberately — cgindex's final MOVQ would // truncate a value-struct element to 8 bytes. + // C3 (task #8): keyed on the checker-stamped tinfo (lhs.type_ / + // idxbase.type_), not tnode KINDs + structlookup-by-name — the + // name-key dropped any base typed via an N_TNAME alias (`type + // result = []capture`, F10) into the silent fallbacks below. + // Mirrors cstage cgen.c case N_DOT N_INDEX-lhs arm 1:1; #209/#211 + // name-keyed→tinfo-SSoT cluster. if (lhs != nil) { if (lhs.kind == nkind.N_INDEX) { let idxbase: *node = lhs.lhs; if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) { - let lc: *local = localfindnode(c, idxbase.str); - // #21 (READ twin of #11): a module-GLOBAL base makes - // localfindnode return nil, so this field-offset-aware - // branch was skipped and `g[i].field` fell through to - // the module-qualified fallback below (garbage — no - // main.g load at all). Resolve the global's tnode via - // letvartnode/defvartnode (the same source cgindex's - // global arm uses) and dispatch the base load by shape: - // array -> LEAQ name(SB) (the symbol IS the storage), - // slice/ptr -> MOVQ name(SB) (the symbol's first word - // IS the .ptr). Mirrors cstage cgen.c's #21 arm. - let tn: *node = nil; - let isglobal: bool = false; - let gname: str; - gname.ptr = nil; gname.len = 0; - if (lc != nil) { - tn = lc.tnode; - } else { - tn = letvartnode(c, idxbase.str); - if (tn == nil) { tn = defvartnode(c, idxbase.str); }; - if (tn != nil) { isglobal = true; gname = idxbase.str; }; - }; - if (tn != nil) { - let elemt: *node = nil; - let baseisarray: bool = false; - let tk: nkind = tn.kind; - if (tk == nkind.N_TSLICE) { elemt = tn.lhs; }; - if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; }; - if (tk == nkind.N_TPTR) { elemt = tn.lhs; }; - let sname: str; - sname.ptr = nil; sname.len = 0; - let viaptr: bool = false; - if (elemt != nil) { - if (elemt.kind == nkind.N_TPTR) { - let inner: *node = elemt.lhs; - if (inner != nil) { if (inner.kind == nkind.N_TNAME) { - sname = inner.str; - viaptr = true; - };}; - } else { if (elemt.kind == nkind.N_TNAME) { - sname = elemt.str; + let elemt: *tinfo = lhs.type_: *tinfo; + let elemu: *tinfo = elemt; + for (elemu != nil && elemu.kind == tykind.TY_NAMED) { elemu = elemu.under; }; + let st: *tinfo = nil; + let viaptr: bool = false; + if (elemu != nil) { + if (elemu.kind == tykind.TY_PTR) { + let pin: *tinfo = elemu.sub; + for (pin != nil && pin.kind == tykind.TY_NAMED) { pin = pin.under; }; + if (pin != nil) { if (pin.kind == tykind.TY_STRUCT) { + st = pin; + viaptr = true; };}; + } else { if (elemu.kind == tykind.TY_STRUCT) { + st = elemu; + };}; + }; + if (st != nil) { + let fnd: *tfield = nil; + let fwalk: *tfield = st.fields; + for (fwalk != nil) { + if (streq(fwalk.name, fld)) { fnd = fwalk; break; }; + fwalk = fwalk.tnext; }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - let esz: i32 = elemsizeofc(c, tn); - cgexpr(c, lhs.rhs); // idx → AX - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - if (isglobal) { - if (baseisarray) { - emitline("\tLEAQ\t"); - emitsymname(c, gname); - emitline("(SB), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitsymname(c, gname); - emitline("(SB), BX\n"); - }; - } else { - if (baseisarray) { - emitline("\tLEAQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - }; - }; - emitline("\tADDQ\tAX, BX\n"); - if (viaptr) { - emitline("\tMOVQ\t(BX), AX\n"); - } else { - emitline("\tMOVQ\tBX, AX\n"); - }; - // #270-1a: an `[N]T`-typed field of an - // array element (`a[i].m[j]`) — leave the - // field's ADDRESS, a base for the outer - // index, NEVER deref. AX holds &a[i]; the - // field address is &a[i]+foff. The #135 - // read-side for `d.m[i]`, applied to an - // array-element base. Without this an array - // field fell to fieldloadop below and loaded - // its first 8 bytes as a value → garbage - // base → SEGFAULT in the outer index. - if (tinfoisarray(fi.tnode.type_: *tinfo)) { - if (fi.foff != 0) { - emitline("\tADDQ\t$"); - emitint(fi.foff: i64); - emitline(", AX\n"); - }; - return; - }; - if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { - // str/slice: the 3-word {ptr,len,cap} - // slice header (#1). AX holds the - // element base, so load .ptr (which - // targets AX) LAST. Matches the - // caseB *struct slice arm and - // cgslicehdr(D_AX). - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); - emitline(", BX\n"); - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 16): i64, "AX"); - emitline(", CX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - if (isfloattype(c, fi.tnode)) { - let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; - emitline("\t"); - emitline(mov); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", X0\n"); - return; - }; - let lop: str = fieldloadop(c, fi); - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - fi = fi.finext; + let bu: *tinfo = idxbase.type_: *tinfo; + for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + let baseisarray: bool = false; + let baseok: bool = false; + if (bu != nil) { + if (bu.kind == tykind.TY_ARRAY) { baseisarray = true; baseok = true; }; + if (bu.kind == tykind.TY_SLICE) { baseok = true; }; + if (bu.kind == tykind.TY_PTR) { baseok = true; }; + }; + let lc: *local = localfindnode(c, idxbase.str); + // #21 (READ twin of #11): a module-GLOBAL base makes + // localfindnode return nil, so this field-offset-aware + // branch was skipped and `g[i].field` fell through to + // the module-qualified fallback below (garbage — no + // main.g load at all). Classify via the let registry / + // array-typed def registry (cstage let_islet || + // def_isarraydef) and dispatch the base load by shape: + // array -> LEAQ name(SB) (the symbol IS the storage), + // slice/ptr -> MOVQ name(SB) (the symbol's first word + // IS the .ptr). Mirrors cstage cgen.c's #21 arm. + let isglobal: bool = false; + if (lc == nil) { + if (isletvar(c, idxbase.str)) { + isglobal = true; + } else { + let dtn: *node = defvartnode(c, idxbase.str); + if (dtn != nil) { + if (dtn.kind == nkind.N_TARRAY) { isglobal = true; }; }; }; }; + if (fnd != nil && baseok && (lc != nil || isglobal)) { + let esz: i32 = elemt.size: i32; + cgexpr(c, lhs.rhs); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (isglobal) { + if (baseisarray) { + emitline("\tLEAQ\t"); + emitsymname(c, idxbase.str); + emitline("(SB), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitsymname(c, idxbase.str); + emitline("(SB), BX\n"); + }; + } else { + if (baseisarray) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + }; + }; + emitline("\tADDQ\tAX, BX\n"); + if (viaptr) { + emitline("\tMOVQ\t(BX), AX\n"); + } else { + emitline("\tMOVQ\tBX, AX\n"); + }; + let foff: i64 = fnd.offset: i64; + let ft: *tinfo = fnd.type_; + let fu: *tinfo = ft; + for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; }; + // #270-1a: an `[N]T`-typed field of an + // array element (`a[i].m[j]`) — leave the + // field's ADDRESS, a base for the outer + // index, NEVER deref. AX holds &a[i]; the + // field address is &a[i]+foff. The #135 + // read-side for `d.m[i]`, applied to an + // array-element base. Without this an array + // field fell to the scalar load below and loaded + // its first 8 bytes as a value → garbage + // base → SEGFAULT in the outer index. + if (fu != nil && fu.kind == tykind.TY_ARRAY) { + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", AX\n"); + }; + return; + }; + if (fu != nil && (fu.kind == tykind.TY_STR + || fu.kind == tykind.TY_SLICE)) { + // str/slice: the 3-word {ptr,len,cap} + // slice header (#1). AX holds the + // element base, so load .ptr (which + // targets AX) LAST. Matches the + // caseB *struct slice arm and + // cgslicehdr(D_AX). + emitline("\tMOVQ\t"); + emitdispreg(foff + 8, "AX"); + emitline(", BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(foff + 16, "AX"); + emitline(", CX\n"); + emitline("\tMOVQ\t"); + emitdispreg(foff, "AX"); + emitline(", AX\n"); + return; + }; + if (typeisfloat(ft)) { + let mov: str = "MOVSD"; + if (typeisf32(ft)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\t"); + emitdispreg(foff, "AX"); + emitline(", X0\n"); + return; + }; + let fsz: i32 = 8; + if (ft != nil) { fsz = ft.size: i32; }; + let lop: str = loadopsz(typeissigned(ft), fsz); + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(foff, "AX"); + emitline(", AX\n"); + return; + }; }; };}; }; @@ -23631,24 +23647,6 @@ fn cgdot(c: *cgen, n: *node) void = { }; }; }; - // C2/C3 boundary: `xs[i].fld` with an IDENT index base is the - // enumerated N_INDEX-base arm's territory (cstage handles it - // type-keyed); reaching HERE means wwstage's name-keyed arm above - // missed (the F10 alias-tnode class). Routing it through the - // read-resolver would emit a different (albeit correct) sequence - // than cstage's arm → cs≠ww. Loud until C3 re-keys that arm onto - // stamped tinfo (task #8). - if (lhs != nil) { - if (lhs.kind == nkind.N_INDEX) { - if (lhs.lhs != nil) { - if (lhs.lhs.kind == nkind.N_IDENT) { - let mg: str = "cgdot: ident-indexed field read unwired in wwstage (C3/task #8)\n"; - os.write(2, mg.ptr, mg.len: u64); - os.exit(1); - }; - }; - }; - }; // cstage reads ptr-chained fields (`a.p.f`, any root) in its // chained-*struct arm; wwstage's #70 mirror above is gated // root-local — the uncovered remainder (global / indexed roots) diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 7b34e05b..7f9e12c1 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -2879,152 +2879,168 @@ fn cgdot(c: *cgen, n: *node) void = { // element), so the leaf load is `(field.offset)(AX)` either way. // Bypasses cgindex deliberately — cgindex's final MOVQ would // truncate a value-struct element to 8 bytes. + // C3 (task #8): keyed on the checker-stamped tinfo (lhs.type_ / + // idxbase.type_), not tnode KINDs + structlookup-by-name — the + // name-key dropped any base typed via an N_TNAME alias (`type + // result = []capture`, F10) into the silent fallbacks below. + // Mirrors cstage cgen.c case N_DOT N_INDEX-lhs arm 1:1; #209/#211 + // name-keyed→tinfo-SSoT cluster. if (lhs != nil) { if (lhs.kind == nkind.N_INDEX) { let idxbase: *node = lhs.lhs; if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) { - let lc: *local = localfindnode(c, idxbase.str); - // #21 (READ twin of #11): a module-GLOBAL base makes - // localfindnode return nil, so this field-offset-aware - // branch was skipped and `g[i].field` fell through to - // the module-qualified fallback below (garbage — no - // main.g load at all). Resolve the global's tnode via - // letvartnode/defvartnode (the same source cgindex's - // global arm uses) and dispatch the base load by shape: - // array -> LEAQ name(SB) (the symbol IS the storage), - // slice/ptr -> MOVQ name(SB) (the symbol's first word - // IS the .ptr). Mirrors cstage cgen.c's #21 arm. - let tn: *node = nil; - let isglobal: bool = false; - let gname: str; - gname.ptr = nil; gname.len = 0; - if (lc != nil) { - tn = lc.tnode; - } else { - tn = letvartnode(c, idxbase.str); - if (tn == nil) { tn = defvartnode(c, idxbase.str); }; - if (tn != nil) { isglobal = true; gname = idxbase.str; }; - }; - if (tn != nil) { - let elemt: *node = nil; - let baseisarray: bool = false; - let tk: nkind = tn.kind; - if (tk == nkind.N_TSLICE) { elemt = tn.lhs; }; - if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; }; - if (tk == nkind.N_TPTR) { elemt = tn.lhs; }; - let sname: str; - sname.ptr = nil; sname.len = 0; - let viaptr: bool = false; - if (elemt != nil) { - if (elemt.kind == nkind.N_TPTR) { - let inner: *node = elemt.lhs; - if (inner != nil) { if (inner.kind == nkind.N_TNAME) { - sname = inner.str; - viaptr = true; - };}; - } else { if (elemt.kind == nkind.N_TNAME) { - sname = elemt.str; + let elemt: *tinfo = lhs.type_: *tinfo; + let elemu: *tinfo = elemt; + for (elemu != nil && elemu.kind == tykind.TY_NAMED) { elemu = elemu.under; }; + let st: *tinfo = nil; + let viaptr: bool = false; + if (elemu != nil) { + if (elemu.kind == tykind.TY_PTR) { + let pin: *tinfo = elemu.sub; + for (pin != nil && pin.kind == tykind.TY_NAMED) { pin = pin.under; }; + if (pin != nil) { if (pin.kind == tykind.TY_STRUCT) { + st = pin; + viaptr = true; };}; + } else { if (elemu.kind == tykind.TY_STRUCT) { + st = elemu; + };}; + }; + if (st != nil) { + let fnd: *tfield = nil; + let fwalk: *tfield = st.fields; + for (fwalk != nil) { + if (streq(fwalk.name, fld)) { fnd = fwalk; break; }; + fwalk = fwalk.tnext; }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - let esz: i32 = elemsizeofc(c, tn); - cgexpr(c, lhs.rhs); // idx → AX - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - if (isglobal) { - if (baseisarray) { - emitline("\tLEAQ\t"); - emitsymname(c, gname); - emitline("(SB), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitsymname(c, gname); - emitline("(SB), BX\n"); - }; - } else { - if (baseisarray) { - emitline("\tLEAQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - }; - }; - emitline("\tADDQ\tAX, BX\n"); - if (viaptr) { - emitline("\tMOVQ\t(BX), AX\n"); - } else { - emitline("\tMOVQ\tBX, AX\n"); - }; - // #270-1a: an `[N]T`-typed field of an - // array element (`a[i].m[j]`) — leave the - // field's ADDRESS, a base for the outer - // index, NEVER deref. AX holds &a[i]; the - // field address is &a[i]+foff. The #135 - // read-side for `d.m[i]`, applied to an - // array-element base. Without this an array - // field fell to fieldloadop below and loaded - // its first 8 bytes as a value → garbage - // base → SEGFAULT in the outer index. - if (tinfoisarray(fi.tnode.type_: *tinfo)) { - if (fi.foff != 0) { - emitline("\tADDQ\t$"); - emitint(fi.foff: i64); - emitline(", AX\n"); - }; - return; - }; - if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { - // str/slice: the 3-word {ptr,len,cap} - // slice header (#1). AX holds the - // element base, so load .ptr (which - // targets AX) LAST. Matches the - // caseB *struct slice arm and - // cgslicehdr(D_AX). - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); - emitline(", BX\n"); - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 16): i64, "AX"); - emitline(", CX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - if (isfloattype(c, fi.tnode)) { - let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; - emitline("\t"); - emitline(mov); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", X0\n"); - return; - }; - let lop: str = fieldloadop(c, fi); - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - fi = fi.finext; + let bu: *tinfo = idxbase.type_: *tinfo; + for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + let baseisarray: bool = false; + let baseok: bool = false; + if (bu != nil) { + if (bu.kind == tykind.TY_ARRAY) { baseisarray = true; baseok = true; }; + if (bu.kind == tykind.TY_SLICE) { baseok = true; }; + if (bu.kind == tykind.TY_PTR) { baseok = true; }; + }; + let lc: *local = localfindnode(c, idxbase.str); + // #21 (READ twin of #11): a module-GLOBAL base makes + // localfindnode return nil, so this field-offset-aware + // branch was skipped and `g[i].field` fell through to + // the module-qualified fallback below (garbage — no + // main.g load at all). Classify via the let registry / + // array-typed def registry (cstage let_islet || + // def_isarraydef) and dispatch the base load by shape: + // array -> LEAQ name(SB) (the symbol IS the storage), + // slice/ptr -> MOVQ name(SB) (the symbol's first word + // IS the .ptr). Mirrors cstage cgen.c's #21 arm. + let isglobal: bool = false; + if (lc == nil) { + if (isletvar(c, idxbase.str)) { + isglobal = true; + } else { + let dtn: *node = defvartnode(c, idxbase.str); + if (dtn != nil) { + if (dtn.kind == nkind.N_TARRAY) { isglobal = true; }; }; }; }; + if (fnd != nil && baseok && (lc != nil || isglobal)) { + let esz: i32 = elemt.size: i32; + cgexpr(c, lhs.rhs); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (isglobal) { + if (baseisarray) { + emitline("\tLEAQ\t"); + emitsymname(c, idxbase.str); + emitline("(SB), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitsymname(c, idxbase.str); + emitline("(SB), BX\n"); + }; + } else { + if (baseisarray) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + }; + }; + emitline("\tADDQ\tAX, BX\n"); + if (viaptr) { + emitline("\tMOVQ\t(BX), AX\n"); + } else { + emitline("\tMOVQ\tBX, AX\n"); + }; + let foff: i64 = fnd.offset: i64; + let ft: *tinfo = fnd.type_; + let fu: *tinfo = ft; + for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; }; + // #270-1a: an `[N]T`-typed field of an + // array element (`a[i].m[j]`) — leave the + // field's ADDRESS, a base for the outer + // index, NEVER deref. AX holds &a[i]; the + // field address is &a[i]+foff. The #135 + // read-side for `d.m[i]`, applied to an + // array-element base. Without this an array + // field fell to the scalar load below and loaded + // its first 8 bytes as a value → garbage + // base → SEGFAULT in the outer index. + if (fu != nil && fu.kind == tykind.TY_ARRAY) { + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", AX\n"); + }; + return; + }; + if (fu != nil && (fu.kind == tykind.TY_STR + || fu.kind == tykind.TY_SLICE)) { + // str/slice: the 3-word {ptr,len,cap} + // slice header (#1). AX holds the + // element base, so load .ptr (which + // targets AX) LAST. Matches the + // caseB *struct slice arm and + // cgslicehdr(D_AX). + emitline("\tMOVQ\t"); + emitdispreg(foff + 8, "AX"); + emitline(", BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(foff + 16, "AX"); + emitline(", CX\n"); + emitline("\tMOVQ\t"); + emitdispreg(foff, "AX"); + emitline(", AX\n"); + return; + }; + if (typeisfloat(ft)) { + let mov: str = "MOVSD"; + if (typeisf32(ft)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\t"); + emitdispreg(foff, "AX"); + emitline(", X0\n"); + return; + }; + let fsz: i32 = 8; + if (ft != nil) { fsz = ft.size: i32; }; + let lop: str = loadopsz(typeissigned(ft), fsz); + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(foff, "AX"); + emitline(", AX\n"); + return; + }; }; };}; }; @@ -3553,24 +3569,6 @@ fn cgdot(c: *cgen, n: *node) void = { }; }; }; - // C2/C3 boundary: `xs[i].fld` with an IDENT index base is the - // enumerated N_INDEX-base arm's territory (cstage handles it - // type-keyed); reaching HERE means wwstage's name-keyed arm above - // missed (the F10 alias-tnode class). Routing it through the - // read-resolver would emit a different (albeit correct) sequence - // than cstage's arm → cs≠ww. Loud until C3 re-keys that arm onto - // stamped tinfo (task #8). - if (lhs != nil) { - if (lhs.kind == nkind.N_INDEX) { - if (lhs.lhs != nil) { - if (lhs.lhs.kind == nkind.N_IDENT) { - let mg: str = "cgdot: ident-indexed field read unwired in wwstage (C3/task #8)\n"; - os.write(2, mg.ptr, mg.len: u64); - os.exit(1); - }; - }; - }; - }; // cstage reads ptr-chained fields (`a.p.f`, any root) in its // chained-*struct arm; wwstage's #70 mirror above is gated // root-local — the uncovered remainder (global / indexed roots) diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index bf61b94f..e65cef83 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -22957,152 +22957,168 @@ fn cgdot(c: *cgen, n: *node) void = { // element), so the leaf load is `(field.offset)(AX)` either way. // Bypasses cgindex deliberately — cgindex's final MOVQ would // truncate a value-struct element to 8 bytes. + // C3 (task #8): keyed on the checker-stamped tinfo (lhs.type_ / + // idxbase.type_), not tnode KINDs + structlookup-by-name — the + // name-key dropped any base typed via an N_TNAME alias (`type + // result = []capture`, F10) into the silent fallbacks below. + // Mirrors cstage cgen.c case N_DOT N_INDEX-lhs arm 1:1; #209/#211 + // name-keyed→tinfo-SSoT cluster. if (lhs != nil) { if (lhs.kind == nkind.N_INDEX) { let idxbase: *node = lhs.lhs; if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) { - let lc: *local = localfindnode(c, idxbase.str); - // #21 (READ twin of #11): a module-GLOBAL base makes - // localfindnode return nil, so this field-offset-aware - // branch was skipped and `g[i].field` fell through to - // the module-qualified fallback below (garbage — no - // main.g load at all). Resolve the global's tnode via - // letvartnode/defvartnode (the same source cgindex's - // global arm uses) and dispatch the base load by shape: - // array -> LEAQ name(SB) (the symbol IS the storage), - // slice/ptr -> MOVQ name(SB) (the symbol's first word - // IS the .ptr). Mirrors cstage cgen.c's #21 arm. - let tn: *node = nil; - let isglobal: bool = false; - let gname: str; - gname.ptr = nil; gname.len = 0; - if (lc != nil) { - tn = lc.tnode; - } else { - tn = letvartnode(c, idxbase.str); - if (tn == nil) { tn = defvartnode(c, idxbase.str); }; - if (tn != nil) { isglobal = true; gname = idxbase.str; }; - }; - if (tn != nil) { - let elemt: *node = nil; - let baseisarray: bool = false; - let tk: nkind = tn.kind; - if (tk == nkind.N_TSLICE) { elemt = tn.lhs; }; - if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; }; - if (tk == nkind.N_TPTR) { elemt = tn.lhs; }; - let sname: str; - sname.ptr = nil; sname.len = 0; - let viaptr: bool = false; - if (elemt != nil) { - if (elemt.kind == nkind.N_TPTR) { - let inner: *node = elemt.lhs; - if (inner != nil) { if (inner.kind == nkind.N_TNAME) { - sname = inner.str; - viaptr = true; - };}; - } else { if (elemt.kind == nkind.N_TNAME) { - sname = elemt.str; + let elemt: *tinfo = lhs.type_: *tinfo; + let elemu: *tinfo = elemt; + for (elemu != nil && elemu.kind == tykind.TY_NAMED) { elemu = elemu.under; }; + let st: *tinfo = nil; + let viaptr: bool = false; + if (elemu != nil) { + if (elemu.kind == tykind.TY_PTR) { + let pin: *tinfo = elemu.sub; + for (pin != nil && pin.kind == tykind.TY_NAMED) { pin = pin.under; }; + if (pin != nil) { if (pin.kind == tykind.TY_STRUCT) { + st = pin; + viaptr = true; };}; + } else { if (elemu.kind == tykind.TY_STRUCT) { + st = elemu; + };}; + }; + if (st != nil) { + let fnd: *tfield = nil; + let fwalk: *tfield = st.fields; + for (fwalk != nil) { + if (streq(fwalk.name, fld)) { fnd = fwalk; break; }; + fwalk = fwalk.tnext; }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - let esz: i32 = elemsizeofc(c, tn); - cgexpr(c, lhs.rhs); // idx → AX - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - if (isglobal) { - if (baseisarray) { - emitline("\tLEAQ\t"); - emitsymname(c, gname); - emitline("(SB), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitsymname(c, gname); - emitline("(SB), BX\n"); - }; - } else { - if (baseisarray) { - emitline("\tLEAQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - }; - }; - emitline("\tADDQ\tAX, BX\n"); - if (viaptr) { - emitline("\tMOVQ\t(BX), AX\n"); - } else { - emitline("\tMOVQ\tBX, AX\n"); - }; - // #270-1a: an `[N]T`-typed field of an - // array element (`a[i].m[j]`) — leave the - // field's ADDRESS, a base for the outer - // index, NEVER deref. AX holds &a[i]; the - // field address is &a[i]+foff. The #135 - // read-side for `d.m[i]`, applied to an - // array-element base. Without this an array - // field fell to fieldloadop below and loaded - // its first 8 bytes as a value → garbage - // base → SEGFAULT in the outer index. - if (tinfoisarray(fi.tnode.type_: *tinfo)) { - if (fi.foff != 0) { - emitline("\tADDQ\t$"); - emitint(fi.foff: i64); - emitline(", AX\n"); - }; - return; - }; - if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { - // str/slice: the 3-word {ptr,len,cap} - // slice header (#1). AX holds the - // element base, so load .ptr (which - // targets AX) LAST. Matches the - // caseB *struct slice arm and - // cgslicehdr(D_AX). - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); - emitline(", BX\n"); - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 16): i64, "AX"); - emitline(", CX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - if (isfloattype(c, fi.tnode)) { - let mov: str = "MOVSD"; - if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; - emitline("\t"); - emitline(mov); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", X0\n"); - return; - }; - let lop: str = fieldloadop(c, fi); - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - fi = fi.finext; + let bu: *tinfo = idxbase.type_: *tinfo; + for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + let baseisarray: bool = false; + let baseok: bool = false; + if (bu != nil) { + if (bu.kind == tykind.TY_ARRAY) { baseisarray = true; baseok = true; }; + if (bu.kind == tykind.TY_SLICE) { baseok = true; }; + if (bu.kind == tykind.TY_PTR) { baseok = true; }; + }; + let lc: *local = localfindnode(c, idxbase.str); + // #21 (READ twin of #11): a module-GLOBAL base makes + // localfindnode return nil, so this field-offset-aware + // branch was skipped and `g[i].field` fell through to + // the module-qualified fallback below (garbage — no + // main.g load at all). Classify via the let registry / + // array-typed def registry (cstage let_islet || + // def_isarraydef) and dispatch the base load by shape: + // array -> LEAQ name(SB) (the symbol IS the storage), + // slice/ptr -> MOVQ name(SB) (the symbol's first word + // IS the .ptr). Mirrors cstage cgen.c's #21 arm. + let isglobal: bool = false; + if (lc == nil) { + if (isletvar(c, idxbase.str)) { + isglobal = true; + } else { + let dtn: *node = defvartnode(c, idxbase.str); + if (dtn != nil) { + if (dtn.kind == nkind.N_TARRAY) { isglobal = true; }; }; }; }; + if (fnd != nil && baseok && (lc != nil || isglobal)) { + let esz: i32 = elemt.size: i32; + cgexpr(c, lhs.rhs); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (isglobal) { + if (baseisarray) { + emitline("\tLEAQ\t"); + emitsymname(c, idxbase.str); + emitline("(SB), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitsymname(c, idxbase.str); + emitline("(SB), BX\n"); + }; + } else { + if (baseisarray) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + }; + }; + emitline("\tADDQ\tAX, BX\n"); + if (viaptr) { + emitline("\tMOVQ\t(BX), AX\n"); + } else { + emitline("\tMOVQ\tBX, AX\n"); + }; + let foff: i64 = fnd.offset: i64; + let ft: *tinfo = fnd.type_; + let fu: *tinfo = ft; + for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; }; + // #270-1a: an `[N]T`-typed field of an + // array element (`a[i].m[j]`) — leave the + // field's ADDRESS, a base for the outer + // index, NEVER deref. AX holds &a[i]; the + // field address is &a[i]+foff. The #135 + // read-side for `d.m[i]`, applied to an + // array-element base. Without this an array + // field fell to the scalar load below and loaded + // its first 8 bytes as a value → garbage + // base → SEGFAULT in the outer index. + if (fu != nil && fu.kind == tykind.TY_ARRAY) { + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", AX\n"); + }; + return; + }; + if (fu != nil && (fu.kind == tykind.TY_STR + || fu.kind == tykind.TY_SLICE)) { + // str/slice: the 3-word {ptr,len,cap} + // slice header (#1). AX holds the + // element base, so load .ptr (which + // targets AX) LAST. Matches the + // caseB *struct slice arm and + // cgslicehdr(D_AX). + emitline("\tMOVQ\t"); + emitdispreg(foff + 8, "AX"); + emitline(", BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(foff + 16, "AX"); + emitline(", CX\n"); + emitline("\tMOVQ\t"); + emitdispreg(foff, "AX"); + emitline(", AX\n"); + return; + }; + if (typeisfloat(ft)) { + let mov: str = "MOVSD"; + if (typeisf32(ft)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\t"); + emitdispreg(foff, "AX"); + emitline(", X0\n"); + return; + }; + let fsz: i32 = 8; + if (ft != nil) { fsz = ft.size: i32; }; + let lop: str = loadopsz(typeissigned(ft), fsz); + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(foff, "AX"); + emitline(", AX\n"); + return; + }; }; };}; }; @@ -23631,24 +23647,6 @@ fn cgdot(c: *cgen, n: *node) void = { }; }; }; - // C2/C3 boundary: `xs[i].fld` with an IDENT index base is the - // enumerated N_INDEX-base arm's territory (cstage handles it - // type-keyed); reaching HERE means wwstage's name-keyed arm above - // missed (the F10 alias-tnode class). Routing it through the - // read-resolver would emit a different (albeit correct) sequence - // than cstage's arm → cs≠ww. Loud until C3 re-keys that arm onto - // stamped tinfo (task #8). - if (lhs != nil) { - if (lhs.kind == nkind.N_INDEX) { - if (lhs.lhs != nil) { - if (lhs.lhs.kind == nkind.N_IDENT) { - let mg: str = "cgdot: ident-indexed field read unwired in wwstage (C3/task #8)\n"; - os.write(2, mg.ptr, mg.len: u64); - os.exit(1); - }; - }; - }; - }; // cstage reads ptr-chained fields (`a.p.f`, any root) in its // chained-*struct arm; wwstage's #70 mirror above is gated // root-local — the uncovered remainder (global / indexed roots) diff --git a/test/wcc/805_placeaddr_store.c b/test/wcc/805_placeaddr_store.c index 8d86ae8a..e65e045b 100644 --- a/test/wcc/805_placeaddr_store.c +++ b/test/wcc/805_placeaddr_store.c @@ -35,9 +35,17 @@ * offset-0 scalars worked by coincidence). TK_AMP's silent tail is the * same resolver-or-loud (`&threads[0].cap` used to SEGFAULT on deref, * reviewer-A route). The C1.25 raw-byte readbacks graduate to typed - * depth-2 reads below. wwstage cgdot/cgun mirror symmetrically; its - * name-keyed ident-indexed arm gap stays LOUD (C3/task #8 re-key), as - * does the ptr-chained global-root remainder (task #37). + * depth-2 reads below. wwstage cgdot/cgun mirror symmetrically; the + * ptr-chained global-root remainder stays LOUD (task #37). + * + * C3 (F7+F10, task #8): wwstage's cgdot N_INDEX-base arm re-keyed from + * tnode KINDs + structlookup-by-name onto the checker-stamped tinfo + * (#209/#211 name-keyed→tinfo-SSoT cluster), mirroring cstage's arm + * 1:1. Pre-C3 a base typed via an N_TNAME alias (`type result = + * []capture`) missed the name-keyed arm and died at the interim C2 + * loud guard (and pre-C2, fell silently into the SB fallback). The + * c3_* rows below pin the alias-typed base class plus the composed + * regex hot shape and the free()-operand consumption (FA6). * * row | shape | want * --------------------+----------------------------------------+------ @@ -86,6 +94,17 @@ * reject_assign_callbs| mk()[0].pc = (assign-tail text keeps | BUILD_FAIL * | its carrier post arr_field_idx_store) | * reject_addrof_callbs| &mk()[0].pc (TK_AMP loud tail) | BUILD_FAIL + * c3_alias_slice_reads| F10: alias-slice base l[i].f — narrow | 65 + * | signed/unsigned, i64, f64, str, []T, | + * | [N]T-field-then-index | + * c3_alias_arr_viaptr | F10: alias [2]S base + alias []*S | 66 + * | (viaptr element) reads | + * c3_letbound_alias | p11b essence sans task-#14 `?`: let | 67 + * | m: result = f(); m[i].field reads | + * c3_composed_match | F7: match(re.insts[(*ts)[i].pc]) + | 68 + * | (*ts)[i] compound/store (p7 hot shape) | + * c3_free_operand | FA6: free((*ts)[i].slicefield) reads | 69 + * | through the operand (pA9) | * * BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring, * both stages) — a build that fails for any other reason (parse error, @@ -911,6 +930,190 @@ static const struct row rows[] = { "\treturn (*p): i32;\n" "};\n", BUILD_FAIL, "unsupported address-of shape" }, + + /* C3 (task #8) F10 class: the index BASE is typed via an N_TNAME + * alias — pre-C3 wwstage's name-keyed arm missed it (interim C2 + * loud guard). Field-kind matrix mirrors the cstage arm's + * dispatch: narrow signed/unsigned, 8B, float, str header, slice + * header, [N]T field as outer-index base (#270-1a). Construction + * goes through DIRECT-typed locals: the alias-typed `= []` / + * arrlit-init/global-DATA sites are a separate filed family + * (unmasked behind C3, not cgdot). */ + { "c3_alias_slice_reads", + "package main;\n" + "type wide = struct { b: u8, w: i16, d: u32, q: i64, f: f64,\n" + "\ts: str, xs: []size, m: [2]u32 };\n" + "type wlist = []wide;\n" + "fn mkw(k: i64) wide = {\n" + "\tlet xs: []size = [];\n" + "\tlet x0: size = (10 + k): size;\n" + "\tlet x1: size = (20 + k): size;\n" + "\tappend(xs, x0);\n" + "\tappend(xs, x1);\n" + "\tlet m0: u32 = (40 + k): u32;\n" + "\tlet m1: u32 = (50 + k): u32;\n" + "\treturn wide { b = (k + 1): u8, w = (0 - k): i16,\n" + "\t\td = (100 + k): u32, q = k, f = 2.5f64, s = \"ab\",\n" + "\t\txs = xs, m = [m0, m1] };\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet l0: []wide = [];\n" + "\tlet w1: wide = mkw(1);\n" + "\tlet w2: wide = mkw(2);\n" + "\tappend(l0, w1);\n" + "\tappend(l0, w2);\n" + "\tlet l: wlist = l0;\n" + "\tif (l[0].b != 2u8) { return 1; };\n" + "\tif (l[0].w != -1i16) { return 2; };\n" + "\tif (l[1].d != 102u32) { return 3; };\n" + "\tif (l[1].q != 2i64) { return 4; };\n" + "\tif (l[0].f != 2.5f64) { return 5; };\n" + "\tif (l[0].s.len != 2) { return 6; };\n" + "\tif (l[1].xs[1] != (22: size)) { return 7; };\n" + "\tif (l[0].m[1] != 51u32) { return 8; };\n" + "\treturn 65;\n" + "};\n", + 65, NULL }, + + /* F10 sibling bases: alias-typed [2]S array (local), and an + * alias-typed []*S whose ELEMENT is a pointer (the arm's viaptr + * deref hop). */ + { "c3_alias_arr_viaptr", + "package main;\n" + "type wide = struct { b: u8, q: i64, xs: []size };\n" + "type warr = [2]wide;\n" + "type pw = *wide;\n" + "type plist = []pw;\n" + "fn mkw(k: i64) wide = {\n" + "\tlet xs: []size = [];\n" + "\tlet x0: size = (10 + k): size;\n" + "\tappend(xs, x0);\n" + "\treturn wide { b = (k + 1): u8, q = k, xs = xs };\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet w3: wide = mkw(3);\n" + "\tlet w4: wide = mkw(4);\n" + "\tlet a0: [2]wide = [w3, w4];\n" + "\tlet a: warr = a0;\n" + "\tif (a[0].q != 3i64) { return 1; };\n" + "\tif (a[1].b != 5u8) { return 2; };\n" + "\tlet w5: wide = mkw(5);\n" + "\tlet ps0: []pw = [];\n" + "\tappend(ps0, &w5);\n" + "\tlet ps: plist = ps0;\n" + "\tif (ps[0].q != 5i64) { return 3; };\n" + "\tif (ps[0].b != 6u8) { return 4; };\n" + "\tif (ps[0].xs[0] != (15: size)) { return 5; };\n" + "\treturn 66;\n" + "};\n", + 66, NULL }, + + /* p11b's F10 essence with the task-#14-gated `?` factored out: a + * let bound from a fn returning the alias, indexed-field reads off + * it (m's tnode is the N_TNAME alias `result`). */ + { "c3_letbound_alias", + "package main;\n" + "type capture = struct { content: str, start: size, end: size };\n" + "type result = []capture;\n" + "fn mk() result = {\n" + "\tlet caps: []capture = [];\n" + "\tappend(caps, capture { content = \"xy\", start = 1, end = 3 });\n" + "\tappend(caps, capture { content = \"z\", start = 4, end = 5 });\n" + "\treturn caps;\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet m: result = mk();\n" + "\tif (len(m) != 2) { return 1; };\n" + "\tif (m[0].start != 1) { return 2; };\n" + "\tif (m[0].content.len != 2) { return 3; };\n" + "\tif (m[1].end != 5) { return 4; };\n" + "\treturn 67;\n" + "};\n", + 67, NULL }, + + /* F7: the composed regex run_thread hot shape — a tagged-slice + * field indexed by a deref-index-field chain, match-dispatched, + * plus compound/store back through the same spine (p7_composed + * core; the strings.frombytes tail lives with task #29). */ + { "c3_composed_match", + "package main;\n" + "type inst_lit = rune;\n" + "type inst_any = void;\n" + "type inst_match = bool;\n" + "type inst = (inst_lit | inst_any | inst_match);\n" + "type re_t = struct { insts: []inst, n_reps: size };\n" + "type capture = struct { content: str, start: size, end: size };\n" + "type thread = struct { pc: size, root_capture: capture, failed: bool };\n" + "fn step(re: *re_t, ts: *[]thread, i: size) i32 = {\n" + "\tmatch (re.insts[(*ts)[i].pc]) {\n" + "\tcase let lt: inst_lit => {\n" + "\t\t(*ts)[i].root_capture = capture { content = \"hit\", start = 2, end = 4 };\n" + "\t\t(*ts)[i].pc += 1;\n" + "\t\treturn 1;\n" + "\t};\n" + "\tcase inst_any => {\n" + "\t\t(*ts)[i].failed = true;\n" + "\t\treturn 2;\n" + "\t};\n" + "\tcase let m: inst_match => {\n" + "\t\t(*ts)[i].pc += 2;\n" + "\t\treturn 3;\n" + "\t};\n" + "\t};\n" + "\treturn 9;\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet insts: []inst = [];\n" + "\tappend(insts, ('a': inst_lit));\n" + "\tlet av: inst_any;\n" + "\tlet v: inst = av;\n" + "\tappend(insts, v);\n" + "\tappend(insts, (true: inst_match));\n" + "\tlet re: re_t = re_t { insts = insts, n_reps = 0 };\n" + "\tlet ts: []thread = [];\n" + "\tlet z: capture = capture { content = \"\", start = 0, end = 0 };\n" + "\tappend(ts, thread { pc = 0, root_capture = z, failed = false });\n" + "\tif (step(&re, &ts, 0) != 1) { return 1; };\n" + "\tif (ts[0].pc != 1) { return 2; };\n" + "\tif (ts[0].root_capture.start != 2) { return 3; };\n" + "\tif (step(&re, &ts, 0) != 2) { return 4; };\n" + "\tif (!ts[0].failed) { return 5; };\n" + "\tlet p: *[]thread = &ts;\n" + "\t(*p)[0].pc += 1;\n" + "\tif (ts[0].pc != 2) { return 6; };\n" + "\tif (step(&re, &ts, 0) != 3) { return 7; };\n" + "\tif (ts[0].pc != 4) { return 8; };\n" + "\treturn 68;\n" + "};\n", + 68, NULL }, + + /* FA6 (pA9): free() consumes its operand via cgexpr — the + * deref-index-field reads inside the operand are C3's reads, the + * free arm itself is sound. Pure acceptance row. */ + { "c3_free_operand", + "package main;\n" + "type capture = struct { content: str, start: size };\n" + "type thread = struct { pc: size, captures: []capture,\n" + "\trep_counters: []size };\n" + "fn delete_thread(i: size, threads: *[]thread) void = {\n" + "\tfree((*threads)[i].captures);\n" + "\tfree((*threads)[i].rep_counters);\n" + "\tdelete((*threads)[i]);\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet z: []capture;\n" + "\tlet zr: []size;\n" + "\tlet xs: []thread = [];\n" + "\tappend(xs, thread { pc = 1, captures = z, rep_counters = zr });\n" + "\tappend(xs, thread { pc = 2, captures = z, rep_counters = zr });\n" + "\tappend(xs, thread { pc = 3, captures = z, rep_counters = zr });\n" + "\tdelete_thread(1, &xs);\n" + "\tif (len(xs) != 2) { return 1; };\n" + "\tif (xs[0].pc != 1) { return 2; };\n" + "\tif (xs[1].pc != 3) { return 3; };\n" + "\treturn 69;\n" + "};\n", + 69, NULL }, }; /* errlog_has — the build-failure stderr must carry the row's expected