wcc+w6c_ww: typed dot-read resolver — kill both silent N_DOT fallbacks (F4)

A typed depth-2+ field chain behind an index/deref spine
(threads[0].cap.end, (*p)[i].f.g) aborted the cgdot chain walker
(N_IDENT roots only) and fell into the module-qualified-leaf fallback
— a silent `MOVQ <leaf>(SB)` global read of a colliding symbol
(p6min10 exit 66) or a link error. Single-dot reads behind a deref-
index ((*ts)[i].pc, (*p)[i].slicefield) fell into the bottom catch-all
instead, which is offset- and header-blind: offset-0 scalars read
correctly by COINCIDENCE, nonzero offsets and slice headers were
silently wrong in BOTH stages (FA3, pA5). `&threads[0].cap` dropped
the address-of silently and SEGFAULTed at the deref (task #6,
reviewer-A route).

cgplaceaddr (C1) grows an N_IDENT root (local / let / DATA-backed
def) and the N_INDEX base gate relaxes to recursion, so indexed-ident
and deref-rooted spines resolve; enumerated arms still dispatch first,
keeping every pre-C1 shape's asm. case N_DOT routes any TYPED read no
arm matched through the resolver (scalar fldloadop, float X0, str/
slice 3-word header, [N]T address); the module-leaf fallback is gated
to UNTYPED chains, the catch-all to untyped-str pseudo-fields, and
the TK_AMP tail is resolver-or-loud. Leaf kinds without a register
convention (tagged, aggregate) and unaddressable shapes die LOUD
(rule 7). wwstage mirrors symmetrically; two of its arm gaps must not
take the resolver (its sequence differs from cstage's arms — cs!=ww):
ident-indexed alias reads loud-cite C3 (task #8) and non-local-rooted
ptr-chains loud-cite task #37. A third verdict divergence is comment-
documented at the wwstage aggregate gate: cstage's let-init consumes
`let c = (*ts)[i].cap` BEFORE its N_DOT tail (emitting NO copy — the
F5 bug), so that shape cs-builds/ww-louds until the F5 let-copy lands
(task #7); absent from the gate corpus.

806 identroot_dot graduates from BUILD_FAIL: the C2 ident root makes
append(h.xs, v) through *holder resolve via C1.5's place consumer
(run-verified, byte-id). p6min9/p6min10 read-halves are fixed but the
probes stay blocked on the #36 literal under-copy this commit
unmasked (struct-ident field rhs copies 8B; repro filed with the
task).

test/805: +7 rows (typed depth-2 behind ident-index incl the 777
global-collision pin, deref-index, width/float/[N]u8 matrix, FA3
slice-field + .cap-behind-spine, &-route with compound-through-
pointer, C1's reject_tail graduated to stores, neutrality pins) and
+4 reject rows pinning the new loud texts; the C1.25 raw-byte
readbacks graduate to typed depth-2 reads.
This commit is contained in:
2026-06-04 10:51:56 +09:00
parent b630a7cf20
commit 76994a8279
6 changed files with 1112 additions and 220 deletions

View File

@@ -2019,12 +2019,13 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals)
/* cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
* expression into dst_reg; returns 1 when the shape is wired, 0
* otherwise (the caller loud-stops — rule 7, never a silent drop).
* F6 resolver, commit C1: only the deref-rooted spine is wired —
* `(*p)[i].f` as N_UN(STAR) root, N_INDEX hop over a slice/array
* place, N_DOT struct-field hop with one deref for a *struct base.
* Ident-rooted spines stay with the enumerated N_ASSIGN arms so this
* resolver never perturbs their asm; the F4 read-walker and F5
* let-copy accrete here in follow-up commits. ADDRESS COMPUTATION
* F6 resolver, commit C1: `(*p)[i].f` as N_UN(STAR) root, N_INDEX hop
* over a slice/array place, N_DOT struct-field hop with one deref for
* a *struct base. C2 (F4 read-walker) adds the N_IDENT root (local /
* let / DATA-backed def) so indexed-ident spines (`threads[0].cap.end`)
* resolve too. Enumerated arms still win at every dispatch site (they
* are checked first), so shapes that worked pre-C1 keep their asm; the
* F5 let-copy accretes here in a follow-up commit. ADDRESS COMPUTATION
* ONLY — every call-site keeps its own load/store/copy emission.
* Clobbers AX/CX (cgexpr on index / pointer operands) and balances
* its own PUSHQ/POPQ; dst_reg must not be AX or CX. */
@@ -2032,6 +2033,19 @@ static int
cgplaceaddr(Cg *c, Node *n, int dst_reg, Local *locals)
{
if (n == NULL) return 0;
if (n->kind == N_IDENT) {
int off = localfind(locals, n->str);
if (off != 0) {
ins2(c, A_LEAQ, amem(D_BP, off), areg(dst_reg));
return 1;
}
if (let_islet(n->str) || def_isstructdef(n->str)
|| def_isarraydef(n->str)) {
ins2(c, A_LEAQ, masym(c, n->str), areg(dst_reg));
return 1;
}
return 0;
}
if (n->kind == N_UN && n->op == TK_STAR) {
/* &(*e) is e's value — no load. */
cgexpr(c, n->lhs, locals);
@@ -2042,11 +2056,10 @@ cgplaceaddr(Cg *c, Node *n, int dst_reg, Local *locals)
Node *base = n->lhs;
Node *idx = n->rhs;
if (base == NULL || idx == NULL) return 0;
/* Deref base only: ident/dot index bases all have
* enumerated arms; routing them here would change
* their asm. */
if (!(base->kind == N_UN && base->op == TK_STAR))
return 0;
/* C2: any addressable base — recursion decides (deref /
* ident / dot / index spine). Ident-rooted shapes with
* enumerated arms never reach the resolver (those arms
* dispatch first), so their asm is untouched. */
Type *bu = type_chase_named(base->type);
if (bu == NULL) return 0;
if (bu->kind != TY_SLICE && bu->kind != TY_ARRAY)
@@ -3809,9 +3822,18 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_ADDQ, areg(D_BX), areg(D_AX));
break;
}
/* Other shapes (& on a complex expr): silent drop,
* mirrors the pre-existing fallback. */
break;
/* C2 (F4 family, reviewer-A route): address-of through
* an indexed/deref dot spine (`&threads[0].cap`,
* `&(*p)[i].f`) — the spine walker above roots only at
* idents. Route the place address through cgplaceaddr
* (read-twin in case N_DOT). Any remaining shape dies
* LOUD: the pre-C2 silent drop left stale AX as the
* "address" — a gate-blind SEGFAULT at the deref. */
if (cgplaceaddr(c, opnd, D_BX, locals)) {
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
break;
}
fatal("unsupported address-of shape");
}
cgexpr(c, n->lhs, locals);
switch (n->op) {
@@ -9514,8 +9536,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
* linker surfaces a clean undefined-symbol error on the leaf
* — mirrors the bare-N_IDENT unresolved fallback used by
* single-segment N_DOTs. Keeps cstage / wwstage byte-aligned
* on the cgen-match isolation probes. */
if (n->lhs && n->lhs->kind == N_DOT && n->str) {
* on the cgen-match isolation probes. C2 (F4): gated to
* UNTYPED chains only — pre-C2 it swallowed every unmatched
* dot-over-dot chain, turning a TYPED depth-2 read behind an
* index/deref spine (`threads[0].cap.end`) into a silent
* global read of a colliding leaf symbol (p6min10 exit 66). */
if (n->lhs && n->lhs->kind == N_DOT && n->str
&& (n->lhs->type == NULL || n->lhs->type == ty_err)) {
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
break;
}
@@ -9524,10 +9551,69 @@ cgexpr(Cg *c, Node *n, Local *locals)
* TY_STR, so it misses the typed slice/str gate above and
* lands here. cgexpr leaves (AX=ptr, BX=len); `.ptr` keeps AX,
* `.len` shuffles BX→AX. Mirrors wwstage cgdot's catch-all
* (selfhost/cmd/wcc/cgenexpr.ww). #14. */
cgexpr(c, n->lhs, locals);
if (lenfld)
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
* (selfhost/cmd/wcc/cgenexpr.ww). #14. C2 (F4/FA3): gated to
* TY_UNTYPED_STR — pre-C2 this was the offset- and header-
* blind catch-all every unmatched typed dot fell into, so a
* nonzero-offset field behind a deref-index spine read the
* element's word 0 (`(*p)[i].slicefield` → 1-word wrong-
* offset read; offset-0 scalars worked by COINCIDENCE). */
{
Type *cu = type_chase_named(
n->lhs ? n->lhs->type : NULL);
if (cu && cu->kind == TY_UNTYPED_STR) {
cgexpr(c, n->lhs, locals);
if (lenfld)
ins2(c, A_MOVQ, areg(D_BX),
areg(D_AX));
break;
}
}
/* C2 read-resolver (F4 + FA3-cstage): a TYPED N_DOT read no
* enumerated arm matched — depth-2+ chains and slice/str/
* scalar fields behind index/deref spines. Address via
* cgplaceaddr (the C1 resolver), leaf load emitted here by
* kind. Leaf kinds with no canonical register convention in
* expr position stay LOUD; any shape the resolver can't
* address dies LOUD (rule 7) — the pre-C2 tails guessed. */
{
Type *rt = n->type;
Type *ru = type_chase_named(rt);
if (ru && ru->kind == TY_TAGGED)
fatal("read-resolver: tagged field read not "
"wired (rule-7)");
if (ru && (ru->kind == TY_STRUCT
|| ru->kind == TY_TUPLE))
fatal("read-resolver: aggregate field read "
"not wired (rule-7)");
if (!cgplaceaddr(c, n, D_BX, locals))
fatal("unsupported field-read shape");
int rd_isf32 = 0;
if (fld_isfloat(rt, &rd_isf32)) {
ins2(c, rd_isf32 ? A_MOVSS : A_MOVSD,
amem(D_BX, 0), areg(D_X0));
goto dot_done;
}
if (ru && ru->kind == TY_ARRAY) {
/* `[N]T` leaf: leave the field ADDRESS — a
* base for an outer index, never a value
* (#270-1a semantics). */
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
goto dot_done;
}
if (ru && (ru->kind == TY_STR
|| ru->kind == TY_SLICE)) {
/* str IS []u8 — 3-word {ptr,len,cap} into
* (AX, BX, CX). BX is the place base, so
* load .len (which targets BX) LAST. */
ins2(c, A_MOVQ, amem(D_BX, 0), areg(D_AX));
ins2(c, A_MOVQ, amem(D_BX, 16), areg(D_CX));
ins2(c, A_MOVQ, amem(D_BX, 8), areg(D_BX));
goto dot_done;
}
int rdsz = (int)(rt ? rt->size : 8);
ins2(c, fldloadop(rt, rdsz), amem(D_BX, 0),
areg(D_AX));
}
dot_done:
break;
}

View File

@@ -21381,18 +21381,49 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
// expression into dstreg; returns true when the shape is wired, false
// otherwise (the caller loud-stops — rule 7, never a silent drop).
// F6 resolver, commit C1 — mirror of cstage cmd/w6c/cgen.c
// cgplaceaddr: only the deref-rooted spine is wired (`(*p)[i].f` as
// N_UN(STAR) root, N_INDEX hop over a slice/array place, N_DOT
// struct-field hop with one deref for a *struct base). All type keys
// come off the checker-STAMPED tinfo (.type_), never tnode names —
// the #209/#211 discipline. Ident-rooted spines stay with the
// enumerated cgassign arms so this resolver never perturbs their
// asm. ADDRESS COMPUTATION ONLY — every call-site keeps its own
// load/store/copy emission. Clobbers AX/CX (cgexpr on index /
// pointer operands) and balances its own PUSHQ/POPQ; dstreg must
// not be AX or CX.
// cgplaceaddr: `(*p)[i].f` as N_UN(STAR) root, N_INDEX hop over a
// slice/array place, N_DOT struct-field hop with one deref for a
// *struct base. C2 (F4 read-walker) adds the N_IDENT root (local /
// let / DATA-backed def) so indexed-ident spines resolve too. All
// type keys come off the checker-STAMPED tinfo (.type_), never tnode
// names — the #209/#211 discipline. Enumerated arms still win at
// every dispatch site (they are checked first), so shapes that worked
// pre-C1 keep their asm. ADDRESS COMPUTATION ONLY — every call-site
// keeps its own load/store/copy emission. Clobbers AX/CX (cgexpr on
// index / pointer operands) and balances its own PUSHQ/POPQ; dstreg
// must not be AX or CX.
fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
if (n == nil) { return false; };
if (n.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, n.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), ");
emitline(dstreg);
emitline("\n");
return true;
};
let gok: bool = isletvar(c, n.str);
if (!gok) {
if (defvarstructinfo(c, n.str) != nil) { gok = true; };
};
if (!gok) {
let dtn: *node = defvartnode(c, n.str);
if (dtn != nil) {
if (dtn.kind == nkind.N_TARRAY) { gok = true; };
};
};
if (gok) {
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
return false;
};
if (n.kind == nkind.N_UN) {
if (n.op != tkind.TK_STAR) { return false; };
// &(*e) is e's value — no load.
@@ -21406,11 +21437,10 @@ fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
let base: *node = n.lhs;
let idx: *node = n.rhs;
if (base == nil || idx == nil) { return false; };
// Deref base only: ident/dot index bases all have
// enumerated arms; routing them here would change
// their asm.
if (base.kind != nkind.N_UN) { return false; };
if (base.op != tkind.TK_STAR) { return false; };
// C2: any addressable base — recursion decides (deref /
// ident / dot / index spine). Ident-rooted shapes with
// enumerated arms never reach the resolver (those arms
// dispatch first), so their asm is untouched.
let bu: *tinfo = base.type_: *tinfo;
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
if (bu == nil) { return false; };
@@ -23318,35 +23348,57 @@ fn cgdot(c: *cgen, n: *node) void = {
// expression — that leaves (AX=ptr, BX=len). Then `.ptr` returns
// AX as is; `.len` shuffles BX→AX. cstage cgen.c was aligned UP
// to this shuffle in #14 (it had returned the ptr for `.len`).
if (streq(fld, "ptr")) { cgexpr(c, lhs); return; };
if (streq(fld, "len")) {
cgexpr(c, lhs);
emitline("\tMOVQ\tBX, AX\n");
return;
};
// .cap on a non-ident base (indexed element `t[i].cap`, call,
// dot-slice): cgexpr leaves the full {ptr,len,cap} header via
// cgslicehdr — shuffle CX→AX. The shuffle fires ONLY for a TYPED
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase) that is NOT
// a bare string literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
// (cgen.ww), never a CX cap, so `"abc".cap` must return AX unshuffled.
// cstage reaches that outcome by typing N_STRLIT as untyped_str
// (cgen.c:8456 catch-all, no cap shuffle); the wwstage checker types
// N_STRLIT as `str` instead (check.ww:2322 vs cstage check.c:1079 —
// divergence filed separately), so the TY_STR kind-gate alone would
// wrongly fire. The N_STRLIT exclusion keeps this byte-identical with
// cstage. #13 read-fix, sibling of the #20 store.
if (streq(fld, "cap")) {
cgexpr(c, lhs);
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
let lu: *tinfo = lhs.type_: *tinfo;
for (lu != nil && lu.kind == tykind.TY_NAMED) { lu = lu.under; };
if (lu != nil && (lu.kind == tykind.TY_SLICE
|| lu.kind == tykind.TY_STR)) {
emitline("\tMOVQ\tCX, AX\n");
// C2 (F4): gated to a slice/str/untyped-str STAMPED base (or an
// N_STRLIT, which ww types as `str` anyway) — pre-C2 this arm was
// shape-blind, so a STRUCT field that merely shares a pseudo-field
// NAME behind a non-ident spine took the offset-blind cgexpr path
// while cstage (type-gated) routes it to the read-resolver.
{
let pbu: *tinfo = nil;
if (lhs != nil) { pbu = lhs.type_: *tinfo; };
for (pbu != nil && pbu.kind == tykind.TY_NAMED) { pbu = pbu.under; };
let pbok: bool = false;
if (pbu != nil) {
if (pbu.kind == tykind.TY_SLICE
|| pbu.kind == tykind.TY_STR
|| pbu.kind == tykind.TY_UNTYPED_STR) { pbok = true; };
};
if (lhs != nil) {
if (lhs.kind == nkind.N_STRLIT) { pbok = true; };
};
if (pbok) {
if (streq(fld, "ptr")) { cgexpr(c, lhs); return; };
if (streq(fld, "len")) {
cgexpr(c, lhs);
emitline("\tMOVQ\tBX, AX\n");
return;
};
// .cap on a non-ident base (indexed element `t[i].cap`,
// call, dot-slice): cgexpr leaves the full {ptr,len,cap}
// header via cgslicehdr — shuffle CX→AX. The shuffle
// fires ONLY for a TYPED slice/str base (kind TY_SLICE/
// TY_STR after NAMED-chase) that is NOT a bare string
// literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
// (cgen.ww), never a CX cap, so `"abc".cap` must return
// AX unshuffled. cstage reaches that outcome by typing
// N_STRLIT as untyped_str (cgen.c catch-all, no cap
// shuffle); the wwstage checker types N_STRLIT as `str`
// instead (check.ww:2322 vs cstage check.c:1079 —
// divergence filed separately), so the TY_STR kind-gate
// alone would wrongly fire. The N_STRLIT exclusion keeps
// this byte-identical with cstage. #13 read-fix, sibling
// of the #20 store.
if (streq(fld, "cap")) {
cgexpr(c, lhs);
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
if (pbu != nil && (pbu.kind == tykind.TY_SLICE
|| pbu.kind == tykind.TY_STR)) {
emitline("\tMOVQ\tCX, AX\n");
};
};
return;
};
};
return;
};
// Chained struct-field-via-ptr-via-ptr access:
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
@@ -23564,16 +23616,126 @@ fn cgdot(c: *cgen, n: *node) void = {
// driver concatenation — the inner enum / struct hasn't been
// seen). Emit `MOVQ <leaf>(SB), AX` so the linker surfaces a
// clean undefined-symbol error on the leaf. Mirror of
// cmd/w6c/cgen.c N_DOT nested fallback.
// cmd/w6c/cgen.c N_DOT nested fallback. C2 (F4): gated to UNTYPED
// chains only — pre-C2 it swallowed every unmatched dot-over-dot
// chain, turning a TYPED depth-2 read behind an index/deref spine
// into a silent global read of a colliding leaf symbol.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
emitline("\tMOVQ\t");
emitsymname(c, fld);
emitline("(SB), AX\n");
return;
let sbt: *tinfo = lhs.type_: *tinfo;
if (sbt == nil || sbt.kind == tykind.TY_ERR) {
emitline("\tMOVQ\t");
emitsymname(c, fld);
emitline("(SB), AX\n");
return;
};
};
};
return;
// 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)
// must not take the resolver (its sequence differs from cstage's
// arm → cs≠ww). Loud; the wwstage alignment is filed as task
// #37.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
let pgu: *tinfo = lhs.type_: *tinfo;
for (pgu != nil && pgu.kind == tykind.TY_NAMED) { pgu = pgu.under; };
if (pgu != nil) {
if (pgu.kind == tykind.TY_PTR) {
let mp: str = "cgdot: ptr-chained field read unwired in wwstage (task #37)\n";
os.write(2, mp.ptr, mp.len: u64);
os.exit(1);
};
};
};
};
// C2 read-resolver (F4 + FA3): a TYPED N_DOT read no enumerated
// arm matched — depth-2+ chains and slice/str/scalar fields behind
// index/deref spines. Address via cgplaceaddr (the C1 resolver),
// leaf load emitted here by kind. Leaf kinds with no canonical
// register convention in expr position stay LOUD; any shape the
// resolver can't address dies LOUD (rule 7) — the pre-C2 tail
// silently emitted NOTHING. Mirror of cstage cgen.c case N_DOT
// read-resolver tail.
{
let rdt: *tinfo = n.type_: *tinfo;
let rdu: *tinfo = rdt;
for (rdu != nil && rdu.kind == tykind.TY_NAMED) { rdu = rdu.under; };
if (rdu != nil) {
if (rdu.kind == tykind.TY_TAGGED) {
let mt: str = "read-resolver: tagged field read not wired (rule-7)\n";
os.write(2, mt.ptr, mt.len: u64);
os.exit(1);
};
// ww-asymmetric in LET position: cstage's let-init
// consumes `let c = (*ts)[i].cap` BEFORE its N_DOT
// tail and emits NO copy (the F5 bug) — cs-builds/
// ww-louds on that shape until the F5 let-copy
// lands (task #7). Absent from the gate corpus.
if (rdu.kind == tykind.TY_STRUCT
|| rdu.kind == tykind.TY_TUPLE) {
let ma: str = "read-resolver: aggregate field read not wired (rule-7)\n";
os.write(2, ma.ptr, ma.len: u64);
os.exit(1);
};
};
if (!cgplaceaddr(c, n, "BX")) {
let mu: str = "unsupported field-read shape\n";
os.write(2, mu.ptr, mu.len: u64);
os.exit(1);
};
if (typeisfloat(rdt)) {
let mov: str = "MOVSD";
if (typeisf32(rdt)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t(BX), X0\n");
return;
};
if (rdu != nil && rdu.kind == tykind.TY_ARRAY) {
// `[N]T` leaf: leave the field ADDRESS — a base for
// an outer index, never a value (#270-1a semantics).
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (rdu != nil && (rdu.kind == tykind.TY_STR
|| rdu.kind == tykind.TY_SLICE)) {
// str IS []u8 — 3-word {ptr,len,cap} into (AX, BX,
// CX). BX is the place base, so load .len (which
// targets BX) LAST.
emitline("\tMOVQ\t(BX), AX\n");
emitline("\tMOVQ\t16(BX), CX\n");
emitline("\tMOVQ\t8(BX), BX\n");
return;
};
let lop: str = "MOVQ";
if (rdt != nil) {
lop = loadopsz(typeissigned(rdt), rdt.slotsize: i32);
};
emitline("\t");
emitline(lop);
emitline("\t(BX), AX\n");
return;
};
};
fn cgun(c: *cgen, n: *node) void = {
@@ -23882,9 +24044,21 @@ fn cgun(c: *cgen, n: *node) void = {
};
};
};
// Fall through silently (mirrors cstage silent-
// drop fallback at the end of the TK_AMP block).
return;
// C2 (F4 family, reviewer-A route): address-of
// through an indexed/deref dot spine — the
// arms above root only at idents. Route the
// place address through cgplaceaddr (read-twin
// in cgdot). Any remaining shape dies LOUD:
// the pre-C2 silent drop left stale AX as the
// "address" — a gate-blind SEGFAULT at the
// deref. Mirror of cstage TK_AMP tail.
if (cgplaceaddr(c, opnd, "BX")) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
let mam: str = "unsupported address-of shape\n";
os.write(2, mam.ptr, mam.len: u64);
os.exit(1);
};
if (opnd.kind == nkind.N_INDEX) {
// &base[i] = base + i*esz, no dereference.
@@ -23987,6 +24161,17 @@ fn cgun(c: *cgen, n: *node) void = {
emitline("\tADDQ\tBX, AX\n");
return;
};
// C2: deref-rooted (`&(*p)`) and other non-ident
// operands — resolver-or-loud, the same tail as the
// N_DOT arm above (cstage has ONE shared tail for
// both).
if (cgplaceaddr(c, opnd, "BX")) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
let mam2: str = "unsupported address-of shape\n";
os.write(2, mam2.ptr, mam2.len: u64);
os.exit(1);
};
return;
};

View File

@@ -1303,18 +1303,49 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
// expression into dstreg; returns true when the shape is wired, false
// otherwise (the caller loud-stops — rule 7, never a silent drop).
// F6 resolver, commit C1 — mirror of cstage cmd/w6c/cgen.c
// cgplaceaddr: only the deref-rooted spine is wired (`(*p)[i].f` as
// N_UN(STAR) root, N_INDEX hop over a slice/array place, N_DOT
// struct-field hop with one deref for a *struct base). All type keys
// come off the checker-STAMPED tinfo (.type_), never tnode names —
// the #209/#211 discipline. Ident-rooted spines stay with the
// enumerated cgassign arms so this resolver never perturbs their
// asm. ADDRESS COMPUTATION ONLY — every call-site keeps its own
// load/store/copy emission. Clobbers AX/CX (cgexpr on index /
// pointer operands) and balances its own PUSHQ/POPQ; dstreg must
// not be AX or CX.
// cgplaceaddr: `(*p)[i].f` as N_UN(STAR) root, N_INDEX hop over a
// slice/array place, N_DOT struct-field hop with one deref for a
// *struct base. C2 (F4 read-walker) adds the N_IDENT root (local /
// let / DATA-backed def) so indexed-ident spines resolve too. All
// type keys come off the checker-STAMPED tinfo (.type_), never tnode
// names — the #209/#211 discipline. Enumerated arms still win at
// every dispatch site (they are checked first), so shapes that worked
// pre-C1 keep their asm. ADDRESS COMPUTATION ONLY — every call-site
// keeps its own load/store/copy emission. Clobbers AX/CX (cgexpr on
// index / pointer operands) and balances its own PUSHQ/POPQ; dstreg
// must not be AX or CX.
fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
if (n == nil) { return false; };
if (n.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, n.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), ");
emitline(dstreg);
emitline("\n");
return true;
};
let gok: bool = isletvar(c, n.str);
if (!gok) {
if (defvarstructinfo(c, n.str) != nil) { gok = true; };
};
if (!gok) {
let dtn: *node = defvartnode(c, n.str);
if (dtn != nil) {
if (dtn.kind == nkind.N_TARRAY) { gok = true; };
};
};
if (gok) {
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
return false;
};
if (n.kind == nkind.N_UN) {
if (n.op != tkind.TK_STAR) { return false; };
// &(*e) is e's value — no load.
@@ -1328,11 +1359,10 @@ fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
let base: *node = n.lhs;
let idx: *node = n.rhs;
if (base == nil || idx == nil) { return false; };
// Deref base only: ident/dot index bases all have
// enumerated arms; routing them here would change
// their asm.
if (base.kind != nkind.N_UN) { return false; };
if (base.op != tkind.TK_STAR) { return false; };
// C2: any addressable base — recursion decides (deref /
// ident / dot / index spine). Ident-rooted shapes with
// enumerated arms never reach the resolver (those arms
// dispatch first), so their asm is untouched.
let bu: *tinfo = base.type_: *tinfo;
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
if (bu == nil) { return false; };
@@ -3240,35 +3270,57 @@ fn cgdot(c: *cgen, n: *node) void = {
// expression — that leaves (AX=ptr, BX=len). Then `.ptr` returns
// AX as is; `.len` shuffles BX→AX. cstage cgen.c was aligned UP
// to this shuffle in #14 (it had returned the ptr for `.len`).
if (streq(fld, "ptr")) { cgexpr(c, lhs); return; };
if (streq(fld, "len")) {
cgexpr(c, lhs);
emitline("\tMOVQ\tBX, AX\n");
return;
};
// .cap on a non-ident base (indexed element `t[i].cap`, call,
// dot-slice): cgexpr leaves the full {ptr,len,cap} header via
// cgslicehdr — shuffle CX→AX. The shuffle fires ONLY for a TYPED
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase) that is NOT
// a bare string literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
// (cgen.ww), never a CX cap, so `"abc".cap` must return AX unshuffled.
// cstage reaches that outcome by typing N_STRLIT as untyped_str
// (cgen.c:8456 catch-all, no cap shuffle); the wwstage checker types
// N_STRLIT as `str` instead (check.ww:2322 vs cstage check.c:1079 —
// divergence filed separately), so the TY_STR kind-gate alone would
// wrongly fire. The N_STRLIT exclusion keeps this byte-identical with
// cstage. #13 read-fix, sibling of the #20 store.
if (streq(fld, "cap")) {
cgexpr(c, lhs);
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
let lu: *tinfo = lhs.type_: *tinfo;
for (lu != nil && lu.kind == tykind.TY_NAMED) { lu = lu.under; };
if (lu != nil && (lu.kind == tykind.TY_SLICE
|| lu.kind == tykind.TY_STR)) {
emitline("\tMOVQ\tCX, AX\n");
// C2 (F4): gated to a slice/str/untyped-str STAMPED base (or an
// N_STRLIT, which ww types as `str` anyway) — pre-C2 this arm was
// shape-blind, so a STRUCT field that merely shares a pseudo-field
// NAME behind a non-ident spine took the offset-blind cgexpr path
// while cstage (type-gated) routes it to the read-resolver.
{
let pbu: *tinfo = nil;
if (lhs != nil) { pbu = lhs.type_: *tinfo; };
for (pbu != nil && pbu.kind == tykind.TY_NAMED) { pbu = pbu.under; };
let pbok: bool = false;
if (pbu != nil) {
if (pbu.kind == tykind.TY_SLICE
|| pbu.kind == tykind.TY_STR
|| pbu.kind == tykind.TY_UNTYPED_STR) { pbok = true; };
};
if (lhs != nil) {
if (lhs.kind == nkind.N_STRLIT) { pbok = true; };
};
if (pbok) {
if (streq(fld, "ptr")) { cgexpr(c, lhs); return; };
if (streq(fld, "len")) {
cgexpr(c, lhs);
emitline("\tMOVQ\tBX, AX\n");
return;
};
// .cap on a non-ident base (indexed element `t[i].cap`,
// call, dot-slice): cgexpr leaves the full {ptr,len,cap}
// header via cgslicehdr — shuffle CX→AX. The shuffle
// fires ONLY for a TYPED slice/str base (kind TY_SLICE/
// TY_STR after NAMED-chase) that is NOT a bare string
// literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
// (cgen.ww), never a CX cap, so `"abc".cap` must return
// AX unshuffled. cstage reaches that outcome by typing
// N_STRLIT as untyped_str (cgen.c catch-all, no cap
// shuffle); the wwstage checker types N_STRLIT as `str`
// instead (check.ww:2322 vs cstage check.c:1079 —
// divergence filed separately), so the TY_STR kind-gate
// alone would wrongly fire. The N_STRLIT exclusion keeps
// this byte-identical with cstage. #13 read-fix, sibling
// of the #20 store.
if (streq(fld, "cap")) {
cgexpr(c, lhs);
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
if (pbu != nil && (pbu.kind == tykind.TY_SLICE
|| pbu.kind == tykind.TY_STR)) {
emitline("\tMOVQ\tCX, AX\n");
};
};
return;
};
};
return;
};
// Chained struct-field-via-ptr-via-ptr access:
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
@@ -3486,16 +3538,126 @@ fn cgdot(c: *cgen, n: *node) void = {
// driver concatenation — the inner enum / struct hasn't been
// seen). Emit `MOVQ <leaf>(SB), AX` so the linker surfaces a
// clean undefined-symbol error on the leaf. Mirror of
// cmd/w6c/cgen.c N_DOT nested fallback.
// cmd/w6c/cgen.c N_DOT nested fallback. C2 (F4): gated to UNTYPED
// chains only — pre-C2 it swallowed every unmatched dot-over-dot
// chain, turning a TYPED depth-2 read behind an index/deref spine
// into a silent global read of a colliding leaf symbol.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
emitline("\tMOVQ\t");
emitsymname(c, fld);
emitline("(SB), AX\n");
return;
let sbt: *tinfo = lhs.type_: *tinfo;
if (sbt == nil || sbt.kind == tykind.TY_ERR) {
emitline("\tMOVQ\t");
emitsymname(c, fld);
emitline("(SB), AX\n");
return;
};
};
};
return;
// 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)
// must not take the resolver (its sequence differs from cstage's
// arm → cs≠ww). Loud; the wwstage alignment is filed as task
// #37.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
let pgu: *tinfo = lhs.type_: *tinfo;
for (pgu != nil && pgu.kind == tykind.TY_NAMED) { pgu = pgu.under; };
if (pgu != nil) {
if (pgu.kind == tykind.TY_PTR) {
let mp: str = "cgdot: ptr-chained field read unwired in wwstage (task #37)\n";
os.write(2, mp.ptr, mp.len: u64);
os.exit(1);
};
};
};
};
// C2 read-resolver (F4 + FA3): a TYPED N_DOT read no enumerated
// arm matched — depth-2+ chains and slice/str/scalar fields behind
// index/deref spines. Address via cgplaceaddr (the C1 resolver),
// leaf load emitted here by kind. Leaf kinds with no canonical
// register convention in expr position stay LOUD; any shape the
// resolver can't address dies LOUD (rule 7) — the pre-C2 tail
// silently emitted NOTHING. Mirror of cstage cgen.c case N_DOT
// read-resolver tail.
{
let rdt: *tinfo = n.type_: *tinfo;
let rdu: *tinfo = rdt;
for (rdu != nil && rdu.kind == tykind.TY_NAMED) { rdu = rdu.under; };
if (rdu != nil) {
if (rdu.kind == tykind.TY_TAGGED) {
let mt: str = "read-resolver: tagged field read not wired (rule-7)\n";
os.write(2, mt.ptr, mt.len: u64);
os.exit(1);
};
// ww-asymmetric in LET position: cstage's let-init
// consumes `let c = (*ts)[i].cap` BEFORE its N_DOT
// tail and emits NO copy (the F5 bug) — cs-builds/
// ww-louds on that shape until the F5 let-copy
// lands (task #7). Absent from the gate corpus.
if (rdu.kind == tykind.TY_STRUCT
|| rdu.kind == tykind.TY_TUPLE) {
let ma: str = "read-resolver: aggregate field read not wired (rule-7)\n";
os.write(2, ma.ptr, ma.len: u64);
os.exit(1);
};
};
if (!cgplaceaddr(c, n, "BX")) {
let mu: str = "unsupported field-read shape\n";
os.write(2, mu.ptr, mu.len: u64);
os.exit(1);
};
if (typeisfloat(rdt)) {
let mov: str = "MOVSD";
if (typeisf32(rdt)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t(BX), X0\n");
return;
};
if (rdu != nil && rdu.kind == tykind.TY_ARRAY) {
// `[N]T` leaf: leave the field ADDRESS — a base for
// an outer index, never a value (#270-1a semantics).
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (rdu != nil && (rdu.kind == tykind.TY_STR
|| rdu.kind == tykind.TY_SLICE)) {
// str IS []u8 — 3-word {ptr,len,cap} into (AX, BX,
// CX). BX is the place base, so load .len (which
// targets BX) LAST.
emitline("\tMOVQ\t(BX), AX\n");
emitline("\tMOVQ\t16(BX), CX\n");
emitline("\tMOVQ\t8(BX), BX\n");
return;
};
let lop: str = "MOVQ";
if (rdt != nil) {
lop = loadopsz(typeissigned(rdt), rdt.slotsize: i32);
};
emitline("\t");
emitline(lop);
emitline("\t(BX), AX\n");
return;
};
};
fn cgun(c: *cgen, n: *node) void = {
@@ -3804,9 +3966,21 @@ fn cgun(c: *cgen, n: *node) void = {
};
};
};
// Fall through silently (mirrors cstage silent-
// drop fallback at the end of the TK_AMP block).
return;
// C2 (F4 family, reviewer-A route): address-of
// through an indexed/deref dot spine — the
// arms above root only at idents. Route the
// place address through cgplaceaddr (read-twin
// in cgdot). Any remaining shape dies LOUD:
// the pre-C2 silent drop left stale AX as the
// "address" — a gate-blind SEGFAULT at the
// deref. Mirror of cstage TK_AMP tail.
if (cgplaceaddr(c, opnd, "BX")) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
let mam: str = "unsupported address-of shape\n";
os.write(2, mam.ptr, mam.len: u64);
os.exit(1);
};
if (opnd.kind == nkind.N_INDEX) {
// &base[i] = base + i*esz, no dereference.
@@ -3909,6 +4083,17 @@ fn cgun(c: *cgen, n: *node) void = {
emitline("\tADDQ\tBX, AX\n");
return;
};
// C2: deref-rooted (`&(*p)`) and other non-ident
// operands — resolver-or-loud, the same tail as the
// N_DOT arm above (cstage has ONE shared tail for
// both).
if (cgplaceaddr(c, opnd, "BX")) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
let mam2: str = "unsupported address-of shape\n";
os.write(2, mam2.ptr, mam2.len: u64);
os.exit(1);
};
return;
};

View File

@@ -21381,18 +21381,49 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
// expression into dstreg; returns true when the shape is wired, false
// otherwise (the caller loud-stops — rule 7, never a silent drop).
// F6 resolver, commit C1 — mirror of cstage cmd/w6c/cgen.c
// cgplaceaddr: only the deref-rooted spine is wired (`(*p)[i].f` as
// N_UN(STAR) root, N_INDEX hop over a slice/array place, N_DOT
// struct-field hop with one deref for a *struct base). All type keys
// come off the checker-STAMPED tinfo (.type_), never tnode names —
// the #209/#211 discipline. Ident-rooted spines stay with the
// enumerated cgassign arms so this resolver never perturbs their
// asm. ADDRESS COMPUTATION ONLY — every call-site keeps its own
// load/store/copy emission. Clobbers AX/CX (cgexpr on index /
// pointer operands) and balances its own PUSHQ/POPQ; dstreg must
// not be AX or CX.
// cgplaceaddr: `(*p)[i].f` as N_UN(STAR) root, N_INDEX hop over a
// slice/array place, N_DOT struct-field hop with one deref for a
// *struct base. C2 (F4 read-walker) adds the N_IDENT root (local /
// let / DATA-backed def) so indexed-ident spines resolve too. All
// type keys come off the checker-STAMPED tinfo (.type_), never tnode
// names — the #209/#211 discipline. Enumerated arms still win at
// every dispatch site (they are checked first), so shapes that worked
// pre-C1 keep their asm. ADDRESS COMPUTATION ONLY — every call-site
// keeps its own load/store/copy emission. Clobbers AX/CX (cgexpr on
// index / pointer operands) and balances its own PUSHQ/POPQ; dstreg
// must not be AX or CX.
fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
if (n == nil) { return false; };
if (n.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, n.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), ");
emitline(dstreg);
emitline("\n");
return true;
};
let gok: bool = isletvar(c, n.str);
if (!gok) {
if (defvarstructinfo(c, n.str) != nil) { gok = true; };
};
if (!gok) {
let dtn: *node = defvartnode(c, n.str);
if (dtn != nil) {
if (dtn.kind == nkind.N_TARRAY) { gok = true; };
};
};
if (gok) {
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
return false;
};
if (n.kind == nkind.N_UN) {
if (n.op != tkind.TK_STAR) { return false; };
// &(*e) is e's value — no load.
@@ -21406,11 +21437,10 @@ fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
let base: *node = n.lhs;
let idx: *node = n.rhs;
if (base == nil || idx == nil) { return false; };
// Deref base only: ident/dot index bases all have
// enumerated arms; routing them here would change
// their asm.
if (base.kind != nkind.N_UN) { return false; };
if (base.op != tkind.TK_STAR) { return false; };
// C2: any addressable base — recursion decides (deref /
// ident / dot / index spine). Ident-rooted shapes with
// enumerated arms never reach the resolver (those arms
// dispatch first), so their asm is untouched.
let bu: *tinfo = base.type_: *tinfo;
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
if (bu == nil) { return false; };
@@ -23318,35 +23348,57 @@ fn cgdot(c: *cgen, n: *node) void = {
// expression — that leaves (AX=ptr, BX=len). Then `.ptr` returns
// AX as is; `.len` shuffles BX→AX. cstage cgen.c was aligned UP
// to this shuffle in #14 (it had returned the ptr for `.len`).
if (streq(fld, "ptr")) { cgexpr(c, lhs); return; };
if (streq(fld, "len")) {
cgexpr(c, lhs);
emitline("\tMOVQ\tBX, AX\n");
return;
};
// .cap on a non-ident base (indexed element `t[i].cap`, call,
// dot-slice): cgexpr leaves the full {ptr,len,cap} header via
// cgslicehdr — shuffle CX→AX. The shuffle fires ONLY for a TYPED
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase) that is NOT
// a bare string literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
// (cgen.ww), never a CX cap, so `"abc".cap` must return AX unshuffled.
// cstage reaches that outcome by typing N_STRLIT as untyped_str
// (cgen.c:8456 catch-all, no cap shuffle); the wwstage checker types
// N_STRLIT as `str` instead (check.ww:2322 vs cstage check.c:1079 —
// divergence filed separately), so the TY_STR kind-gate alone would
// wrongly fire. The N_STRLIT exclusion keeps this byte-identical with
// cstage. #13 read-fix, sibling of the #20 store.
if (streq(fld, "cap")) {
cgexpr(c, lhs);
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
let lu: *tinfo = lhs.type_: *tinfo;
for (lu != nil && lu.kind == tykind.TY_NAMED) { lu = lu.under; };
if (lu != nil && (lu.kind == tykind.TY_SLICE
|| lu.kind == tykind.TY_STR)) {
emitline("\tMOVQ\tCX, AX\n");
// C2 (F4): gated to a slice/str/untyped-str STAMPED base (or an
// N_STRLIT, which ww types as `str` anyway) — pre-C2 this arm was
// shape-blind, so a STRUCT field that merely shares a pseudo-field
// NAME behind a non-ident spine took the offset-blind cgexpr path
// while cstage (type-gated) routes it to the read-resolver.
{
let pbu: *tinfo = nil;
if (lhs != nil) { pbu = lhs.type_: *tinfo; };
for (pbu != nil && pbu.kind == tykind.TY_NAMED) { pbu = pbu.under; };
let pbok: bool = false;
if (pbu != nil) {
if (pbu.kind == tykind.TY_SLICE
|| pbu.kind == tykind.TY_STR
|| pbu.kind == tykind.TY_UNTYPED_STR) { pbok = true; };
};
if (lhs != nil) {
if (lhs.kind == nkind.N_STRLIT) { pbok = true; };
};
if (pbok) {
if (streq(fld, "ptr")) { cgexpr(c, lhs); return; };
if (streq(fld, "len")) {
cgexpr(c, lhs);
emitline("\tMOVQ\tBX, AX\n");
return;
};
// .cap on a non-ident base (indexed element `t[i].cap`,
// call, dot-slice): cgexpr leaves the full {ptr,len,cap}
// header via cgslicehdr — shuffle CX→AX. The shuffle
// fires ONLY for a TYPED slice/str base (kind TY_SLICE/
// TY_STR after NAMED-chase) that is NOT a bare string
// literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
// (cgen.ww), never a CX cap, so `"abc".cap` must return
// AX unshuffled. cstage reaches that outcome by typing
// N_STRLIT as untyped_str (cgen.c catch-all, no cap
// shuffle); the wwstage checker types N_STRLIT as `str`
// instead (check.ww:2322 vs cstage check.c:1079 —
// divergence filed separately), so the TY_STR kind-gate
// alone would wrongly fire. The N_STRLIT exclusion keeps
// this byte-identical with cstage. #13 read-fix, sibling
// of the #20 store.
if (streq(fld, "cap")) {
cgexpr(c, lhs);
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
if (pbu != nil && (pbu.kind == tykind.TY_SLICE
|| pbu.kind == tykind.TY_STR)) {
emitline("\tMOVQ\tCX, AX\n");
};
};
return;
};
};
return;
};
// Chained struct-field-via-ptr-via-ptr access:
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
@@ -23564,16 +23616,126 @@ fn cgdot(c: *cgen, n: *node) void = {
// driver concatenation — the inner enum / struct hasn't been
// seen). Emit `MOVQ <leaf>(SB), AX` so the linker surfaces a
// clean undefined-symbol error on the leaf. Mirror of
// cmd/w6c/cgen.c N_DOT nested fallback.
// cmd/w6c/cgen.c N_DOT nested fallback. C2 (F4): gated to UNTYPED
// chains only — pre-C2 it swallowed every unmatched dot-over-dot
// chain, turning a TYPED depth-2 read behind an index/deref spine
// into a silent global read of a colliding leaf symbol.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
emitline("\tMOVQ\t");
emitsymname(c, fld);
emitline("(SB), AX\n");
return;
let sbt: *tinfo = lhs.type_: *tinfo;
if (sbt == nil || sbt.kind == tykind.TY_ERR) {
emitline("\tMOVQ\t");
emitsymname(c, fld);
emitline("(SB), AX\n");
return;
};
};
};
return;
// 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)
// must not take the resolver (its sequence differs from cstage's
// arm → cs≠ww). Loud; the wwstage alignment is filed as task
// #37.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
let pgu: *tinfo = lhs.type_: *tinfo;
for (pgu != nil && pgu.kind == tykind.TY_NAMED) { pgu = pgu.under; };
if (pgu != nil) {
if (pgu.kind == tykind.TY_PTR) {
let mp: str = "cgdot: ptr-chained field read unwired in wwstage (task #37)\n";
os.write(2, mp.ptr, mp.len: u64);
os.exit(1);
};
};
};
};
// C2 read-resolver (F4 + FA3): a TYPED N_DOT read no enumerated
// arm matched — depth-2+ chains and slice/str/scalar fields behind
// index/deref spines. Address via cgplaceaddr (the C1 resolver),
// leaf load emitted here by kind. Leaf kinds with no canonical
// register convention in expr position stay LOUD; any shape the
// resolver can't address dies LOUD (rule 7) — the pre-C2 tail
// silently emitted NOTHING. Mirror of cstage cgen.c case N_DOT
// read-resolver tail.
{
let rdt: *tinfo = n.type_: *tinfo;
let rdu: *tinfo = rdt;
for (rdu != nil && rdu.kind == tykind.TY_NAMED) { rdu = rdu.under; };
if (rdu != nil) {
if (rdu.kind == tykind.TY_TAGGED) {
let mt: str = "read-resolver: tagged field read not wired (rule-7)\n";
os.write(2, mt.ptr, mt.len: u64);
os.exit(1);
};
// ww-asymmetric in LET position: cstage's let-init
// consumes `let c = (*ts)[i].cap` BEFORE its N_DOT
// tail and emits NO copy (the F5 bug) — cs-builds/
// ww-louds on that shape until the F5 let-copy
// lands (task #7). Absent from the gate corpus.
if (rdu.kind == tykind.TY_STRUCT
|| rdu.kind == tykind.TY_TUPLE) {
let ma: str = "read-resolver: aggregate field read not wired (rule-7)\n";
os.write(2, ma.ptr, ma.len: u64);
os.exit(1);
};
};
if (!cgplaceaddr(c, n, "BX")) {
let mu: str = "unsupported field-read shape\n";
os.write(2, mu.ptr, mu.len: u64);
os.exit(1);
};
if (typeisfloat(rdt)) {
let mov: str = "MOVSD";
if (typeisf32(rdt)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t(BX), X0\n");
return;
};
if (rdu != nil && rdu.kind == tykind.TY_ARRAY) {
// `[N]T` leaf: leave the field ADDRESS — a base for
// an outer index, never a value (#270-1a semantics).
emitline("\tMOVQ\tBX, AX\n");
return;
};
if (rdu != nil && (rdu.kind == tykind.TY_STR
|| rdu.kind == tykind.TY_SLICE)) {
// str IS []u8 — 3-word {ptr,len,cap} into (AX, BX,
// CX). BX is the place base, so load .len (which
// targets BX) LAST.
emitline("\tMOVQ\t(BX), AX\n");
emitline("\tMOVQ\t16(BX), CX\n");
emitline("\tMOVQ\t8(BX), BX\n");
return;
};
let lop: str = "MOVQ";
if (rdt != nil) {
lop = loadopsz(typeissigned(rdt), rdt.slotsize: i32);
};
emitline("\t");
emitline(lop);
emitline("\t(BX), AX\n");
return;
};
};
fn cgun(c: *cgen, n: *node) void = {
@@ -23882,9 +24044,21 @@ fn cgun(c: *cgen, n: *node) void = {
};
};
};
// Fall through silently (mirrors cstage silent-
// drop fallback at the end of the TK_AMP block).
return;
// C2 (F4 family, reviewer-A route): address-of
// through an indexed/deref dot spine — the
// arms above root only at idents. Route the
// place address through cgplaceaddr (read-twin
// in cgdot). Any remaining shape dies LOUD:
// the pre-C2 silent drop left stale AX as the
// "address" — a gate-blind SEGFAULT at the
// deref. Mirror of cstage TK_AMP tail.
if (cgplaceaddr(c, opnd, "BX")) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
let mam: str = "unsupported address-of shape\n";
os.write(2, mam.ptr, mam.len: u64);
os.exit(1);
};
if (opnd.kind == nkind.N_INDEX) {
// &base[i] = base + i*esz, no dereference.
@@ -23987,6 +24161,17 @@ fn cgun(c: *cgen, n: *node) void = {
emitline("\tADDQ\tBX, AX\n");
return;
};
// C2: deref-rooted (`&(*p)`) and other non-ident
// operands — resolver-or-loud, the same tail as the
// N_DOT arm above (cstage has ONE shared tail for
// both).
if (cgplaceaddr(c, opnd, "BX")) {
emitline("\tMOVQ\tBX, AX\n");
return;
};
let mam2: str = "unsupported address-of shape\n";
os.write(2, mam2.ptr, mam2.len: u64);
os.exit(1);
};
return;
};

View File

@@ -24,6 +24,21 @@
* diagnostics — the reject rows pin the exact text on BOTH stages.
* Non-DOT lvalue tail residue is task #22.
*
* C2 (F4 + FA3-cstage, task #6): the READ side. cgplaceaddr grew an
* N_IDENT root and recursion-driven N_INDEX bases; case N_DOT routes
* every TYPED read no enumerated arm matched through the resolver, and
* BOTH silent fallbacks died: the module-leaf `MOVQ <leaf>(SB)` is
* gated to UNTYPED chains (pre-C2 it swallowed any unmatched dot chain
* — a silent global read of a colliding symbol), and the offset-blind
* cgexpr catch-all is gated to untyped-str pseudo-fields (pre-C2 a
* nonzero-offset field behind a deref-index spine read element word 0;
* 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).
*
* row | shape | want
* --------------------+----------------------------------------+------
* store_size_neighbor | (*ts)[1].pc = 9, elem 0 intact | 19
@@ -55,7 +70,22 @@
* reject_float | f64 field store | BUILD_FAIL
* reject_tagged | tagged-union field store | BUILD_FAIL
* reject_str_compound | (*ts)[i].name += — str/slice compound | BUILD_FAIL
* reject_tail | h.arr[1].pc (resolver-unwired N_DOT) | BUILD_FAIL
* arr_field_idx_store | h.arr[1].pc = / += (C1's reject_tail, | 58
* | graduated by the C2 resolver arms) |
* read_depth2_idx | ts[i].cap.start/.end/.content.len — | 59
* | typed depth-2 behind an IDENT index |
* read_depth2_deref | (*ts)[i].cap.start + offset-0 .pc | 60
* read_widths_spine | i8..u32/f64/f32/[3]u8 behind (*p)[i] | 61
* read_slicefield | let h = (*p)[i].xs (FA3/pA5) + .cap | 62
* amp_spine | &ts[1].cap / &(*p)[0].cap — deref, | 63
* | compound through the pointer |
* neutral_reads | ident-root chain, "abc".len, (*p).f — | 64
* | untouched-arm asm-neutrality pins |
* reject_read_tagged | (*ts)[i].tg read behind spine | BUILD_FAIL
* reject_read_callbase| mk()[0].pc read (unaddressable base) | BUILD_FAIL
* 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
*
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
* both stages) — a build that fails for any other reason (parse error,
@@ -383,14 +413,10 @@ static const struct row rows[] = {
* through a FRESH per-use @placescr materialise + word-copy; the
* `...` setter pins the autofill zero loop; neighbour element +
* sibling field readbacks pin the stride and copy length. */
/* Readback is RAW bytes over ts.ptr (the 804 tagged_56b
* precedent): a depth-2 read behind an index (ts[1].cap.start)
* is the F4 walker gap (task #6) and link-fails today on the
* cgen.c:9038 global-leaf fallback — typed readbacks graduate
* with the C2 read-walker. Layout: t={pc@0,cap@8}, size 48;
* capture={content(str ptr@0,len@8,cap@16),start@24,end@32}.
* Elem1 base 48 → content.len byte 64, start 80, end 88;
* elem0 → 16/32/40. Stored values fit one byte. */
/* Readback was RAW bytes over ts.ptr until C2 — a depth-2 read
* behind an index (ts[1].cap.start) was the F4 walker gap (task
* #6) and link-failed on the cgen.c global-leaf fallback. C2
* graduation: typed depth-2 readbacks (the documented note). */
{ "agg_structlit_40b",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
@@ -407,24 +433,23 @@ static const struct row rows[] = {
"\tappend(ts, t { pc = 7, cap = z });\n"
"\tappend(ts, t { pc = 8, cap = z });\n"
"\tsetcap(&ts, 1);\n"
"\tlet bp: *u8 = ts.ptr: *u8;\n"
"\tif (bp[64] != 3u8) { return 1; };\n"
"\tif (bp[80] != 2u8) { return 2; };\n"
"\tif (bp[88] != 4u8) { return 3; };\n"
"\tif (ts[1].cap.content.len != 3) { return 1; };\n"
"\tif (ts[1].cap.start != 2) { return 2; };\n"
"\tif (ts[1].cap.end != 4) { return 3; };\n"
"\tif (ts[1].pc != 8) { return 4; };\n"
"\tif (bp[16] != 0u8) { return 5; };\n"
"\tif (ts[0].cap.content.len != 0) { return 5; };\n"
"\tif (ts[0].pc != 7) { return 6; };\n"
"\tclearcap(&ts, 1);\n"
"\tif (bp[64] != 1u8) { return 7; };\n"
"\tif (bp[80] != 0u8) { return 8; };\n"
"\tif (bp[88] != 0u8) { return 9; };\n"
"\tif (ts[1].cap.content.len != 1) { return 7; };\n"
"\tif (ts[1].cap.start != 0) { return 8; };\n"
"\tif (ts[1].cap.end != 0) { return 9; };\n"
"\treturn 52;\n"
"};\n",
52, NULL },
/* Addressable aggregate sources via the #265/#268 dispatch:
* local ident (LEAQ slot) and deref (*pc). Raw-byte readback per
* the agg_structlit_40b layout note (F4 blocks typed depth-2). */
* local ident (LEAQ slot) and deref (*pc). Typed depth-2
* readbacks since C2 (were raw bytes over ts.ptr). */
{ "agg_from_ident_deref",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
@@ -442,16 +467,15 @@ static const struct row rows[] = {
"\tappend(ts, t { pc = 8, cap = z });\n"
"\tlet a: capture = capture { content = \"ab\", start = 1, end = 3 };\n"
"\tcopycap(&ts, 1, a);\n"
"\tlet bp: *u8 = ts.ptr: *u8;\n"
"\tif (bp[64] != 2u8) { return 1; };\n"
"\tif (bp[80] != 1u8) { return 2; };\n"
"\tif (bp[88] != 3u8) { return 3; };\n"
"\tif (ts[1].cap.content.len != 2) { return 1; };\n"
"\tif (ts[1].cap.start != 1) { return 2; };\n"
"\tif (ts[1].cap.end != 3) { return 3; };\n"
"\tlet b: capture = capture { content = \"wxyz\", start = 5, end = 9 };\n"
"\tderefcap(&ts, 0, &b);\n"
"\tif (bp[16] != 4u8) { return 4; };\n"
"\tif (bp[32] != 5u8) { return 5; };\n"
"\tif (bp[40] != 9u8) { return 6; };\n"
"\tif (bp[80] != 1u8) { return 7; };\n"
"\tif (ts[0].cap.content.len != 4) { return 4; };\n"
"\tif (ts[0].cap.start != 5) { return 5; };\n"
"\tif (ts[0].cap.end != 9) { return 6; };\n"
"\tif (ts[1].cap.start != 1) { return 7; };\n"
"\treturn 53;\n"
"};\n",
53, NULL },
@@ -524,13 +548,12 @@ static const struct row rows[] = {
"\tappend(ts, t { pc = 7, cap = z });\n"
"\tappend(ts, t { pc = 8, cap = z });\n"
"\tsettwo(&ts);\n"
"\tlet bp: *u8 = ts.ptr: *u8;\n"
"\tif (bp[16] != 2u8) { return 1; };\n"
"\tif (bp[32] != 1u8) { return 2; };\n"
"\tif (bp[40] != 2u8) { return 3; };\n"
"\tif (bp[64] != 3u8) { return 4; };\n"
"\tif (bp[80] != 3u8) { return 5; };\n"
"\tif (bp[88] != 4u8) { return 6; };\n"
"\tif (ts[0].cap.content.len != 2) { return 1; };\n"
"\tif (ts[0].cap.start != 1) { return 2; };\n"
"\tif (ts[0].cap.end != 2) { return 3; };\n"
"\tif (ts[1].cap.content.len != 3) { return 4; };\n"
"\tif (ts[1].cap.start != 3) { return 5; };\n"
"\tif (ts[1].cap.end != 4) { return 6; };\n"
"\treturn 56;\n"
"};\n",
56, NULL },
@@ -655,19 +678,239 @@ static const struct row rows[] = {
BUILD_FAIL,
"assign-resolver: compound on str/slice field not wired (rule-7)" },
/* An N_DOT lvalue the C1 resolver does not wire (index base is a
* dot chain, not a deref) — pre-C1 this was a SILENT no-op store;
* the loud dispatch tail is the close-by-construction net. */
{ "reject_tail",
/* C1's reject_tail (index base is a dot chain), graduated by the
* C2 resolver arms (N_IDENT root + recursive N_INDEX bases) —
* store, compound, readback, and neighbour-intact pins. */
{ "arr_field_idx_store",
"package main;\n"
"type t = struct { pc: size, matched: bool };\n"
"type holder = struct { arr: [3]t, n: i64 };\n"
"export fn main() i32 = {\n"
"\tlet h: holder = holder { n = 0, ... };\n"
"\th.arr[1].pc = 5;\n"
"\th.arr[1].pc += 2;\n"
"\tif (h.arr[1].pc != 7) { return 1; };\n"
"\tif (h.arr[0].pc != 0) { return 2; };\n"
"\tif (h.arr[2].pc != 0) { return 3; };\n"
"\treturn 58;\n"
"};\n",
58, NULL },
/* C2 (F4): typed depth-2 reads behind an IDENT-indexed root —
* the p6min9/p6min10 read shape (nonzero offsets, both elements,
* pseudo .len behind the spine, and the global-collision check:
* a module global named `end` must NOT be read). Elements are
* built with NESTED literals, not `cap = rc1` ident rhs — the
* probes' own construction shape is the #36 under-copy (struct-
* ident field rhs copies 8B), which keeps p6min9/p6min10
* themselves blocked on #36 with their read-half fixed here. */
{ "read_depth2_idx",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"let end: size = 777;\n"
"export fn main() i32 = {\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 5, cap = capture { content = \"abc\", start = 11, end = 13 } });\n"
"\tappend(ts, t { pc = 6, cap = capture { content = \"wy\", start = 21, end = 23 } });\n"
"\tif (ts[0].cap.end == 777) { return 66; };\n"
"\tif (ts[0].cap.end != 13) { return 1; };\n"
"\tif (ts[0].cap.start != 11) { return 2; };\n"
"\tif (ts[1].cap.end != 23) { return 3; };\n"
"\tif (ts[1].cap.content.len != 2) { return 4; };\n"
"\tif (ts[1].pc != 6) { return 5; };\n"
"\treturn 59;\n"
"};\n",
59, NULL },
/* C2 (F4): the same chain behind a DEREF-rooted index — plus the
* offset-0 scalar (.pc) that pre-C2 cstage read correctly only by
* COINCIDENCE through the offset-blind catch-all (ken's FA3
* correction); now it routes through the resolver in both
* stages. */
{ "read_depth2_deref",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"fn rd(ts: *[]t) i32 = {\n"
"\tif ((*ts)[0].pc != 5) { return 1; };\n"
"\tif ((*ts)[0].cap.start != 11) { return 2; };\n"
"\tif ((*ts)[0].cap.end != 13) { return 3; };\n"
"\tif ((*ts)[1].cap.end != 23) { return 4; };\n"
"\treturn 0;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 5, cap = capture { content = \"abc\", start = 11, end = 13 } });\n"
"\tappend(ts, t { pc = 6, cap = capture { content = \"wy\", start = 21, end = 23 } });\n"
"\tlet rc: i32 = rd(&ts);\n"
"\tif (rc != 0) { return rc; };\n"
"\treturn 60;\n"
"};\n",
60, NULL },
/* C2 (F4): leaf-kind matrix behind the deref-index spine — narrow
* signed/unsigned (fldloadop sign/zero extension), f64/f32 via
* X0, and an `[N]u8` field leaf (address semantics, outer index
* applies). */
{ "read_widths_spine",
"package main;\n"
"type inner = struct { b: i8, ub: u8, w: i16, uw: u16, d: i32, ud: u32, f: f64, g: f32, arr: [3]u8 };\n"
"type box = struct { pad: size, it: inner };\n"
"export fn main() i32 = {\n"
"\tlet bs: []box = [];\n"
"\tappend(bs, box { pad = 1, it = inner { b = -5i8, ub = 200u8, w = -300i16, uw = 60000u16, d = -70000, ud = 4000000000u32, f = 1.5, g = 2.5f32, arr = [7u8, 8u8, 9u8] } });\n"
"\tlet p: *[]box = &bs;\n"
"\tif ((*p)[0].it.b != -5i8) { return 1; };\n"
"\tif ((*p)[0].it.ub != 200u8) { return 2; };\n"
"\tif ((*p)[0].it.w != -300i16) { return 3; };\n"
"\tif ((*p)[0].it.uw != 60000u16) { return 4; };\n"
"\tif ((*p)[0].it.d != -70000) { return 5; };\n"
"\tif ((*p)[0].it.ud != 4000000000u32) { return 6; };\n"
"\tif ((*p)[0].it.f != 1.5) { return 7; };\n"
"\tif ((*p)[0].it.g != 2.5f32) { return 8; };\n"
"\tif ((*p)[0].it.arr[1] != 8u8) { return 9; };\n"
"\treturn 61;\n"
"};\n",
61, NULL },
/* FA3/pA5 graduated: 24B slice-header field read behind a deref-
* index spine — pre-C2 cstage took the offset-blind catch-all
* (one wrong-offset MOVQ + stale BX/CX as the header), wwstage
* silently emitted NOTHING. Plus the .cap pseudo behind the same
* spine (recurses through the resolver). */
{ "read_slicefield",
"package main;\n"
"type box = struct { pc: size, xs: []size };\n"
"fn grab(p: *[]box, i: size) i32 = {\n"
"\tlet h: []size = (*p)[i].xs;\n"
"\tif (len(h) != 2) { return 1; };\n"
"\tif (h[0] != (7: size)) { return 2; };\n"
"\tif (h[1] != (8: size)) { return 3; };\n"
"\tif ((*p)[i].xs.cap < 2) { return 4; };\n"
"\treturn 0;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet src: []size = [];\n"
"\tappend(src, (7: size));\n"
"\tappend(src, (8: size));\n"
"\tlet bs: []box = [];\n"
"\tappend(bs, box { pc = 1, xs = src });\n"
"\tlet rc: i32 = grab(&bs, 0);\n"
"\tif (rc != 0) { return rc; };\n"
"\treturn 62;\n"
"};\n",
62, NULL },
/* Reviewer-A route: address-of a field through an indexed /
* deref-indexed spine, then deref + compound through the pointer.
* Pre-C2 BOTH stages silently dropped the & (stale AX as the
* address — gate-blind SEGFAULT). */
{ "amp_spine",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"fn bump(cp: *capture) void = { cp.end += 100; };\n"
"export fn main() i32 = {\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 5, cap = capture { content = \"abc\", start = 11, end = 13 } });\n"
"\tappend(ts, t { pc = 6, cap = capture { content = \"wy\", start = 21, end = 23 } });\n"
"\tlet cp: *capture = &ts[1].cap;\n"
"\tif (cp.start != 21) { return 1; };\n"
"\tbump(cp);\n"
"\tif (ts[1].cap.end != 123) { return 2; };\n"
"\tlet pp: *[]t = &ts;\n"
"\tlet dp: *capture = &(*pp)[0].cap;\n"
"\tif (dp.end != 13) { return 3; };\n"
"\treturn 63;\n"
"};\n",
63, NULL },
/* Asm-neutrality pins for the arms the C2 gates must NOT disturb:
* ident-rooted value chain (chain walker), `(*p).f` retarget,
* untyped-str literal pseudo-fields (the gated catch-all's only
* legitimate tenant). A tuple-positional pin is blocked by the
* pre-existing #33 wwstage tuple-let word-2 drop (filed). */
{ "neutral_reads",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"type t = struct { pc: size, cap: capture };\n"
"fn pcof(p: *t) size = { return (*p).pc; };\n"
"export fn main() i32 = {\n"
"\tlet x: t = t { pc = 9, cap = capture { content = \"hi\", start = 1, end = 2 } };\n"
"\tif (x.cap.start != 1) { return 1; };\n"
"\tif (x.cap.content.len != 2) { return 2; };\n"
"\tif (pcof(&x) != 9) { return 3; };\n"
"\tif (\"abc\".len != 3) { return 4; };\n"
"\treturn 64;\n"
"};\n",
64, NULL },
/* Tagged leaf behind a spine: no canonical register convention
* is emitted by the read-resolver yet — must die LOUD, not load
* 8 of 16+ bytes (rule 7). */
{ "reject_read_tagged",
"package main;\n"
"type v = (i64 | bool);\n"
"type t = struct { pc: size, tg: v };\n"
"fn rd(ts: *[]t, i: size) i64 = {\n"
"\tlet x: v = (*ts)[i].tg;\n"
"\treturn 1;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet ts: []t = [];\n"
"\treturn rd(&ts, 0): i32;\n"
"};\n",
BUILD_FAIL, "read-resolver: tagged field read not wired (rule-7)" },
/* A call-result index base has no storage address — the resolver
* must refuse and the read tail must be LOUD (pre-C2: cstage
* emitted an offset-blind garbage read, wwstage nothing). */
{ "reject_read_callbase",
"package main;\n"
"type t = struct { pc: size, matched: bool };\n"
"fn mk() []t = {\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 3, matched = true });\n"
"\treturn ts;\n"
"};\n"
"export fn main() i32 = {\n"
"\tif (mk()[0].pc != 3) { return 1; };\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "unsupported field-read shape" },
/* The assign-tail diagnostic keeps a carrier now that C1's
* reject_tail shape (h.arr[1].pc) graduated: a call-base lvalue
* is unaddressable. */
{ "reject_assign_callbase",
"package main;\n"
"type t = struct { pc: size, matched: bool };\n"
"fn mk() []t = {\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 3, matched = true });\n"
"\treturn ts;\n"
"};\n"
"export fn main() i32 = {\n"
"\tmk()[0].pc = 2;\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "unsupported assign target shape" },
/* TK_AMP loud tail (the C2 replacement for the silent drop). */
{ "reject_addrof_callbase",
"package main;\n"
"type t = struct { pc: size, matched: bool };\n"
"fn mk() []t = {\n"
"\tlet ts: []t = [];\n"
"\tappend(ts, t { pc = 3, matched = true });\n"
"\treturn ts;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet p: *size = &mk()[0].pc;\n"
"\treturn (*p): i32;\n"
"};\n",
BUILD_FAIL, "unsupported address-of shape" },
};
/* errlog_has — the build-failure stderr must carry the row's expected

View File

@@ -42,8 +42,8 @@
* | — nested indirect append inside the |
* | outer's value expr; pins the per-SITE |
* | @apphdrscr slot (a shared slot clobbers)|
* reject_identroot_dot | h.xs via *holder param (resolver ident | BUILD_FAIL
* | root unwired — was silent corruption) |
* identroot_dot | h.xs via *holder param — C1.5's reject, | 73
* | graduated by C2's resolver ident root |
* reject_spread_src | non-ident spread SOURCE through a deref | BUILD_FAIL
* | target (the #35 designed boundary) |
*
@@ -372,20 +372,28 @@ static const struct row rows[] = {
"};\n",
33, NULL },
/* Ident-rooted dot target (h.xs through *holder): the resolver
* does not wire ident roots (they belong to the enumerated arms;
* none exists for append yet) — pre-fix this shape silently
* corrupted the frame in cstage; now it must die LOUD. */
{ "reject_identroot_dot",
/* Ident-rooted dot target (h.xs through *holder): C1.5's loud
* boundary (the resolver had no ident root then; pre-FA1 this
* shape silently corrupted the frame in cstage), graduated by
* C2's cgplaceaddr N_IDENT root — the *holder base derefs once
* inside the N_DOT hop and the header place lands on &h.xs.
* Readbacks use the .len pseudo, not len(): len() of a non-tuple
* N_DOT is the pre-existing F2 enumeration gap (task #10). */
{ "identroot_dot",
"package main;\n"
"type holder = struct { tag: i64, xs: []i64 };\n"
"fn addfield(h: *holder, v: i64) void = { append(h.xs, v); };\n"
"export fn main() i32 = {\n"
"\tlet hl: holder = holder { tag = 2, xs = [] };\n"
"\taddfield(&hl, 9);\n"
"\treturn 0;\n"
"\taddfield(&hl, 11);\n"
"\tif (hl.xs[0] != 9) { return 1; };\n"
"\tif (hl.xs[1] != 11) { return 2; };\n"
"\tif (hl.xs.len != 2) { return 3; };\n"
"\tif (hl.tag != 2) { return 4; };\n"
"\treturn 73;\n"
"};\n",
BUILD_FAIL, "#15: append() target place unsupported (rule-7)" },
73, NULL },
/* The FA4 designed boundary (task #35, old #37): a spread SOURCE that is
* not an ident local must stay loud even now that the deref