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:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user