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

@@ -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;
};