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:
126
cmd/w6c/cgen.c
126
cmd/w6c/cgen.c
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user