wcc+w6c_ww: aggregate let/range element copies via cgplaceaddr (F5)
F5 (task #7): the N_LET aggregate-copy arm's source-addr enumeration
(cgen.c #265/#268) had TY_ARRAY-ident/N_DOT/N_INDEX bases but no
TY_SLICE base and no deref-spine shapes, so `let e: th = xs[0]` fell
out with havesrc=0 — cstage emitted NOTHING (slot uninitialised),
wwstage fell to its scalar default (8B truncation): gate-blind cs≠ww
(p6min13). Every remaining ADDRESSABLE rhs now resolves through
cgplaceaddr (the C1 resolver; enumerated arms dispatch first, their
asm untouched), and the arm closes by construction with a loud tail —
nothing below it can initialise a >8B struct/array slot, so any
unhandled rhs shape dies loud instead of silently. A pre-tail #38b
guard keeps the established `?`/`!`-on-sret loud-stop marker in
wwstage (mirror of cstage's pre-arm fatal; pre-fix that shape reached
the cgtryunw/cgtryprop gates which the tail now pre-empts in let
position).
Reviewer-C2 inheritance: `let c: capture = (*ts)[i].cap` (aggregate
leaf behind a deref spine) — wwstage's documented cgdot aggregate-leaf
loud is retired for let position (cglet routes the copy through the
resolver before cgexpr sees the leaf; the loud stays as the guard for
non-let expr positions), and cstage's silent no-copy on the same shape
is fixed by the same resolver fallback.
By-value RANGE payloads ride the same class: N_FORRANGE's single-bind
load truncated every aggregate element to one fldloadop word. Both
stages now word-copy the full element extent (MOVQ run + sized
MOVL/MOVW/MOVB tail, the #270-1b idiom) for esz > 8. wwstage esz is
re-keyed elemsizeof→elemsizeofc (the 8-sentinel hid struct elements
from the copy gate — the #8 named-narrow precedent), with a
stamped-slc.type_ fallback + element-tnode synthesis for non-ident
scrutinees (tinfo SSoT, #209/#211). The wwstage checker now binds the
ELEMENT type on single-bind ranges via a synthetic N_LET binder node
(mirror of cstage check.c N_FORRANGE scope_define(..., elem, ...));
pre-fix the binding's decl was the N_FORRANGE node itself, so any
field read off a by-value binding asserttyped-bailed. The checker
half folds in under rule 11 because the split is unsound in either
order: cgen-first is untestable (every field read off the binding
still bails), checker-first converts that loud bail into the 8B
SILENT truncation — only the pair closes the class.
FC0 graduates: regex.finish's by-value range over 24B charset elems
(non-ident scrutinee re.charsets) was the lib/regex byte-cmp's ONLY
hunk since fold-1 — cstage 8-of-24-byte copy + IMULQ $24 vs wwstage
1-byte MOVZBQ, runtime-masked by the no-op loop body. The byte-cmp is
now ZERO hunks (regex_test.combined.ww, w6c vs w6c_ww).
#36 disposition: NOT folded. p6min9/p6min10's remaining failure is the
struct-ident field rhs inside a struct LITERAL (cg_structlit_fill
under-copy) — a different choke-point from the let-copy source-addr
machinery; they still exit 4 here and stay blocked on #36 (read half
landed in C2).
Residual filed as task #43: an UNANNOTATED aggregate let
(`let e = xs[0]`) still skips the wwstage arm (aggn/letslotsize are
annotation-keyed; cstage keys the stamped n->type and now full-copies)
— cs≠ww on that shape remains, #38-family. A landmine comment in
test 805 marks the gap.
test 805: +6 rows — let-from-slice-elem 16B (p6min13 verbatim) /
24B/40B/12B(MOVQ+MOVL tail) matrix / deref-spine leaf / by-value range
([]struct both-fields sum, []capture 40B, []str 24B header) / range
edges (empty slice, by-VALUE binder-mutation pin, 12B elem MOVL tail)
/ reject row pinning the loud-tail text on both stages. All six fail
at the pristine parent 403625e (re-verified post-rebase; 121 prior
fixtures stay green there).
This commit is contained in:
@@ -10740,7 +10740,33 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len > 0) {
|
||||
checkmoduleshadow(c, bnm, "binding");
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
// C4 (task #7): bind the ELEMENT type so field
|
||||
// reads off a by-value aggregate binding
|
||||
// (`for (let t .. threads) { t.pc }`) resolve —
|
||||
// pre-C4 the binding's decl was the N_FORRANGE
|
||||
// node itself, whose .lhs is the SCRUTINEE expr,
|
||||
// so exprtype's decl.lhs read handed the dot a
|
||||
// non-type node and asserttyped bailed (cstage
|
||||
// types it: check.c N_FORRANGE scope_define(...,
|
||||
// elem, ...)). Synthetic N_LET binder whose .lhs
|
||||
// is the element tnode — the stamptuplebinds
|
||||
// `b.lhs = et` idiom. A str scrutinee keeps the
|
||||
// old decl: cgen synthesises the u8 elem there
|
||||
// and no dot applies to a u8 binding.
|
||||
let et: *node = nil;
|
||||
let it: *node = exprtype(c, n.lhs, nil);
|
||||
if (it != nil) {
|
||||
if (it.kind == nkind.N_TSLICE) { et = it.lhs; };
|
||||
if (it.kind == nkind.N_TARRAY) { et = it.lhs; };
|
||||
};
|
||||
if (et != nil) {
|
||||
let bn: *node = newnode(nkind.N_LET, n.file, n.line, n.col);
|
||||
bn.str = bnm;
|
||||
bn.lhs = et;
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, bn);
|
||||
} else {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
@@ -23700,11 +23726,11 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
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.
|
||||
// LET-position aggregate leaves route through cglet's
|
||||
// resolver copy (C4, task #7) before cgexpr ever sees
|
||||
// them; this loud guards the remaining non-let expr
|
||||
// positions (no register convention for a >8B leaf),
|
||||
// symmetric with cstage's read-resolver tail.
|
||||
if (rdu.kind == tykind.TY_STRUCT
|
||||
|| rdu.kind == tykind.TY_TUPLE) {
|
||||
let ma: str = "read-resolver: aggregate field read not wired (rule-7)\n";
|
||||
@@ -32116,6 +32142,18 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
}; };
|
||||
// C4 (F5, task #7): the remaining ADDRESSABLE rhs
|
||||
// shapes — a slice-base element (`= xs[0]`; the arms
|
||||
// above have TY_ARRAY/N_DOT/N_INDEX bases but no
|
||||
// TY_SLICE base) and deref-spine leaves
|
||||
// (`= (*ts)[i].cap`) — resolve through cgplaceaddr
|
||||
// (the C1 resolver; enumerated arms dispatch first so
|
||||
// their asm is untouched). Pre-C4 these fell through
|
||||
// to the scalar default's 8B truncation while cstage
|
||||
// emitted NOTHING — gate-blind cs≠ww.
|
||||
if (!havesrc) {
|
||||
if (cgplaceaddr(c, rhs, "SI")) { havesrc = true; };
|
||||
};
|
||||
if (havesrc) {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= aggn) {
|
||||
@@ -32157,6 +32195,30 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// #38b (rule 7): `?`/`!` over an sret-class call into
|
||||
// an aggregate let — keep the established #38b/#40
|
||||
// loud-stop marker (mirror of cstage's pre-arm fatal,
|
||||
// cgen.c N_LET; pre-C4 this shape fell through to the
|
||||
// cgtryunw/cgtryprop gates, which the C4 tail below
|
||||
// now pre-empts in let position).
|
||||
if (rhs.kind == nkind.N_TRYUNW
|
||||
|| rhs.kind == nkind.N_TRYPROP) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, rhs.lhs) > 0) {
|
||||
let m38f: str = "#38b: `?`/`!` on an sret-class call result unwired (mem-based unwrap is a #40-family follow-up)\n";
|
||||
os.write(2, m38f.ptr, m38f.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// C4: nothing below this arm can initialise a >8B
|
||||
// struct/array slot — the scalar default's 8B store
|
||||
// was a silent truncation (rule 7).
|
||||
let mf5: str = "let: aggregate init from unhandled rhs shape (task #7/rule-7)\n";
|
||||
os.write(2, mf5.ptr, mf5.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
@@ -32807,7 +32869,12 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
// no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8.
|
||||
// elemsizeof returns 8 for non-primitive elem, which would be
|
||||
// wrong here — compute from the tuple param walk instead.
|
||||
let esz: i32 = elemsizeof(slctn);
|
||||
// C4 (task #7): elemsizeofc, not elemsizeof — a struct element
|
||||
// (`[]thread`, 16B) hit elemsizeof's 8-sentinel while cstage reads
|
||||
// the stamped slc->type sub size (IMULQ $8 vs $16, gate-blind
|
||||
// cs≠ww). elemsizeofc recovers the width from the stamped tinfo
|
||||
// (the #8 named-narrow precedent).
|
||||
let esz: i32 = elemsizeofc(c, slctn);
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) {
|
||||
let total: i32 = 0;
|
||||
@@ -32819,6 +32886,31 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
esz = total;
|
||||
};
|
||||
};
|
||||
// C4 (FC0, task #7): a non-ident scrutinee (`re.charsets`) has no
|
||||
// local tnode — slctn is nil, so esz fell to 1 and the binding
|
||||
// registered typeless (cstage reads the stamped slc->type: esz 24,
|
||||
// slice-header readbacks → cs≠ww). Derive both from the checker-
|
||||
// stamped slc.type_ (tinfo SSoT, the #209/#211 discipline); the
|
||||
// synthesised N_TNAME carries the element tinfo so cgident's
|
||||
// str/slice/float keys read it like a declared local (the str→u8
|
||||
// synthesis precedent above).
|
||||
if (slctn == nil && slc != nil) {
|
||||
let sti2: *tinfo = slc.type_: *tinfo;
|
||||
for (sti2 != nil && sti2.kind == tykind.TY_NAMED) { sti2 = sti2.under; };
|
||||
if (sti2 != nil) {
|
||||
if (sti2.kind == tykind.TY_SLICE
|
||||
|| sti2.kind == tykind.TY_STR
|
||||
|| sti2.kind == tykind.TY_ARRAY) {
|
||||
if (sti2.sub != nil) {
|
||||
esz = sti2.sub.size: i32;
|
||||
let en: *node = newnode(nkind.N_TNAME, slc.file, slc.line, slc.col);
|
||||
en.str = sti2.sub.name;
|
||||
en.type_ = sti2.sub: *void;
|
||||
elemt = en;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let destruct: bool = (n.list != nil);
|
||||
|
||||
// .rgi (counter) + .rgl (length) scratch slots.
|
||||
@@ -32987,18 +33079,64 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
// Per-binding load from BX+foff. Signedness comes from bind_signed
|
||||
// (set via paramissigned → fieldissignedc), so enum-aliased narrows
|
||||
// pick the right MOVS*Q without a literal-name gate.
|
||||
let b: i32 = 0;
|
||||
for (b < nbinds) {
|
||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]: i64);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
// C4 (F5/FC0, task #7): a by-value AGGREGATE element (struct /
|
||||
// tuple / str/slice header, esz > 8) copies its FULL extent — the
|
||||
// single load word truncated it to 8B, so every field past word 0
|
||||
// (str/slice .len/.cap included) read stale slot bytes
|
||||
// (regex.finish's 24B charset binding, gate-blind cs≠ww). Same
|
||||
// word-run + sized-tail idiom as the cglet aggregate copy.
|
||||
if (!destruct && esz > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 8;
|
||||
};
|
||||
if (k + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 4;
|
||||
};
|
||||
if (k + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 2;
|
||||
};
|
||||
if (k + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 1;
|
||||
};
|
||||
} else {
|
||||
let b: i32 = 0;
|
||||
for (b < nbinds) {
|
||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]: i64);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
};
|
||||
};
|
||||
|
||||
if (n.body != nil) { cgstmt(c, n.body); };
|
||||
|
||||
@@ -3606,11 +3606,11 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
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.
|
||||
// LET-position aggregate leaves route through cglet's
|
||||
// resolver copy (C4, task #7) before cgexpr ever sees
|
||||
// them; this loud guards the remaining non-let expr
|
||||
// positions (no register convention for a >8B leaf),
|
||||
// symmetric with cstage's read-resolver tail.
|
||||
if (rdu.kind == tykind.TY_STRUCT
|
||||
|| rdu.kind == tykind.TY_TUPLE) {
|
||||
let ma: str = "read-resolver: aggregate field read not wired (rule-7)\n";
|
||||
|
||||
@@ -2543,6 +2543,18 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
}; };
|
||||
// C4 (F5, task #7): the remaining ADDRESSABLE rhs
|
||||
// shapes — a slice-base element (`= xs[0]`; the arms
|
||||
// above have TY_ARRAY/N_DOT/N_INDEX bases but no
|
||||
// TY_SLICE base) and deref-spine leaves
|
||||
// (`= (*ts)[i].cap`) — resolve through cgplaceaddr
|
||||
// (the C1 resolver; enumerated arms dispatch first so
|
||||
// their asm is untouched). Pre-C4 these fell through
|
||||
// to the scalar default's 8B truncation while cstage
|
||||
// emitted NOTHING — gate-blind cs≠ww.
|
||||
if (!havesrc) {
|
||||
if (cgplaceaddr(c, rhs, "SI")) { havesrc = true; };
|
||||
};
|
||||
if (havesrc) {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= aggn) {
|
||||
@@ -2584,6 +2596,30 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// #38b (rule 7): `?`/`!` over an sret-class call into
|
||||
// an aggregate let — keep the established #38b/#40
|
||||
// loud-stop marker (mirror of cstage's pre-arm fatal,
|
||||
// cgen.c N_LET; pre-C4 this shape fell through to the
|
||||
// cgtryunw/cgtryprop gates, which the C4 tail below
|
||||
// now pre-empts in let position).
|
||||
if (rhs.kind == nkind.N_TRYUNW
|
||||
|| rhs.kind == nkind.N_TRYPROP) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, rhs.lhs) > 0) {
|
||||
let m38f: str = "#38b: `?`/`!` on an sret-class call result unwired (mem-based unwrap is a #40-family follow-up)\n";
|
||||
os.write(2, m38f.ptr, m38f.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// C4: nothing below this arm can initialise a >8B
|
||||
// struct/array slot — the scalar default's 8B store
|
||||
// was a silent truncation (rule 7).
|
||||
let mf5: str = "let: aggregate init from unhandled rhs shape (task #7/rule-7)\n";
|
||||
os.write(2, mf5.ptr, mf5.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
@@ -3234,7 +3270,12 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
// no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8.
|
||||
// elemsizeof returns 8 for non-primitive elem, which would be
|
||||
// wrong here — compute from the tuple param walk instead.
|
||||
let esz: i32 = elemsizeof(slctn);
|
||||
// C4 (task #7): elemsizeofc, not elemsizeof — a struct element
|
||||
// (`[]thread`, 16B) hit elemsizeof's 8-sentinel while cstage reads
|
||||
// the stamped slc->type sub size (IMULQ $8 vs $16, gate-blind
|
||||
// cs≠ww). elemsizeofc recovers the width from the stamped tinfo
|
||||
// (the #8 named-narrow precedent).
|
||||
let esz: i32 = elemsizeofc(c, slctn);
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) {
|
||||
let total: i32 = 0;
|
||||
@@ -3246,6 +3287,31 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
esz = total;
|
||||
};
|
||||
};
|
||||
// C4 (FC0, task #7): a non-ident scrutinee (`re.charsets`) has no
|
||||
// local tnode — slctn is nil, so esz fell to 1 and the binding
|
||||
// registered typeless (cstage reads the stamped slc->type: esz 24,
|
||||
// slice-header readbacks → cs≠ww). Derive both from the checker-
|
||||
// stamped slc.type_ (tinfo SSoT, the #209/#211 discipline); the
|
||||
// synthesised N_TNAME carries the element tinfo so cgident's
|
||||
// str/slice/float keys read it like a declared local (the str→u8
|
||||
// synthesis precedent above).
|
||||
if (slctn == nil && slc != nil) {
|
||||
let sti2: *tinfo = slc.type_: *tinfo;
|
||||
for (sti2 != nil && sti2.kind == tykind.TY_NAMED) { sti2 = sti2.under; };
|
||||
if (sti2 != nil) {
|
||||
if (sti2.kind == tykind.TY_SLICE
|
||||
|| sti2.kind == tykind.TY_STR
|
||||
|| sti2.kind == tykind.TY_ARRAY) {
|
||||
if (sti2.sub != nil) {
|
||||
esz = sti2.sub.size: i32;
|
||||
let en: *node = newnode(nkind.N_TNAME, slc.file, slc.line, slc.col);
|
||||
en.str = sti2.sub.name;
|
||||
en.type_ = sti2.sub: *void;
|
||||
elemt = en;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let destruct: bool = (n.list != nil);
|
||||
|
||||
// .rgi (counter) + .rgl (length) scratch slots.
|
||||
@@ -3414,18 +3480,64 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
// Per-binding load from BX+foff. Signedness comes from bind_signed
|
||||
// (set via paramissigned → fieldissignedc), so enum-aliased narrows
|
||||
// pick the right MOVS*Q without a literal-name gate.
|
||||
let b: i32 = 0;
|
||||
for (b < nbinds) {
|
||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]: i64);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
// C4 (F5/FC0, task #7): a by-value AGGREGATE element (struct /
|
||||
// tuple / str/slice header, esz > 8) copies its FULL extent — the
|
||||
// single load word truncated it to 8B, so every field past word 0
|
||||
// (str/slice .len/.cap included) read stale slot bytes
|
||||
// (regex.finish's 24B charset binding, gate-blind cs≠ww). Same
|
||||
// word-run + sized-tail idiom as the cglet aggregate copy.
|
||||
if (!destruct && esz > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 8;
|
||||
};
|
||||
if (k + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 4;
|
||||
};
|
||||
if (k + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 2;
|
||||
};
|
||||
if (k + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 1;
|
||||
};
|
||||
} else {
|
||||
let b: i32 = 0;
|
||||
for (b < nbinds) {
|
||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]: i64);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
};
|
||||
};
|
||||
|
||||
if (n.body != nil) { cgstmt(c, n.body); };
|
||||
|
||||
@@ -413,7 +413,33 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len > 0) {
|
||||
checkmoduleshadow(c, bnm, "binding");
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
// C4 (task #7): bind the ELEMENT type so field
|
||||
// reads off a by-value aggregate binding
|
||||
// (`for (let t .. threads) { t.pc }`) resolve —
|
||||
// pre-C4 the binding's decl was the N_FORRANGE
|
||||
// node itself, whose .lhs is the SCRUTINEE expr,
|
||||
// so exprtype's decl.lhs read handed the dot a
|
||||
// non-type node and asserttyped bailed (cstage
|
||||
// types it: check.c N_FORRANGE scope_define(...,
|
||||
// elem, ...)). Synthetic N_LET binder whose .lhs
|
||||
// is the element tnode — the stamptuplebinds
|
||||
// `b.lhs = et` idiom. A str scrutinee keeps the
|
||||
// old decl: cgen synthesises the u8 elem there
|
||||
// and no dot applies to a u8 binding.
|
||||
let et: *node = nil;
|
||||
let it: *node = exprtype(c, n.lhs, nil);
|
||||
if (it != nil) {
|
||||
if (it.kind == nkind.N_TSLICE) { et = it.lhs; };
|
||||
if (it.kind == nkind.N_TARRAY) { et = it.lhs; };
|
||||
};
|
||||
if (et != nil) {
|
||||
let bn: *node = newnode(nkind.N_LET, n.file, n.line, n.col);
|
||||
bn.str = bnm;
|
||||
bn.lhs = et;
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, bn);
|
||||
} else {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
|
||||
@@ -10740,7 +10740,33 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len > 0) {
|
||||
checkmoduleshadow(c, bnm, "binding");
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
// C4 (task #7): bind the ELEMENT type so field
|
||||
// reads off a by-value aggregate binding
|
||||
// (`for (let t .. threads) { t.pc }`) resolve —
|
||||
// pre-C4 the binding's decl was the N_FORRANGE
|
||||
// node itself, whose .lhs is the SCRUTINEE expr,
|
||||
// so exprtype's decl.lhs read handed the dot a
|
||||
// non-type node and asserttyped bailed (cstage
|
||||
// types it: check.c N_FORRANGE scope_define(...,
|
||||
// elem, ...)). Synthetic N_LET binder whose .lhs
|
||||
// is the element tnode — the stamptuplebinds
|
||||
// `b.lhs = et` idiom. A str scrutinee keeps the
|
||||
// old decl: cgen synthesises the u8 elem there
|
||||
// and no dot applies to a u8 binding.
|
||||
let et: *node = nil;
|
||||
let it: *node = exprtype(c, n.lhs, nil);
|
||||
if (it != nil) {
|
||||
if (it.kind == nkind.N_TSLICE) { et = it.lhs; };
|
||||
if (it.kind == nkind.N_TARRAY) { et = it.lhs; };
|
||||
};
|
||||
if (et != nil) {
|
||||
let bn: *node = newnode(nkind.N_LET, n.file, n.line, n.col);
|
||||
bn.str = bnm;
|
||||
bn.lhs = et;
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, bn);
|
||||
} else {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
@@ -23700,11 +23726,11 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
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.
|
||||
// LET-position aggregate leaves route through cglet's
|
||||
// resolver copy (C4, task #7) before cgexpr ever sees
|
||||
// them; this loud guards the remaining non-let expr
|
||||
// positions (no register convention for a >8B leaf),
|
||||
// symmetric with cstage's read-resolver tail.
|
||||
if (rdu.kind == tykind.TY_STRUCT
|
||||
|| rdu.kind == tykind.TY_TUPLE) {
|
||||
let ma: str = "read-resolver: aggregate field read not wired (rule-7)\n";
|
||||
@@ -32116,6 +32142,18 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
}; };
|
||||
// C4 (F5, task #7): the remaining ADDRESSABLE rhs
|
||||
// shapes — a slice-base element (`= xs[0]`; the arms
|
||||
// above have TY_ARRAY/N_DOT/N_INDEX bases but no
|
||||
// TY_SLICE base) and deref-spine leaves
|
||||
// (`= (*ts)[i].cap`) — resolve through cgplaceaddr
|
||||
// (the C1 resolver; enumerated arms dispatch first so
|
||||
// their asm is untouched). Pre-C4 these fell through
|
||||
// to the scalar default's 8B truncation while cstage
|
||||
// emitted NOTHING — gate-blind cs≠ww.
|
||||
if (!havesrc) {
|
||||
if (cgplaceaddr(c, rhs, "SI")) { havesrc = true; };
|
||||
};
|
||||
if (havesrc) {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= aggn) {
|
||||
@@ -32157,6 +32195,30 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// #38b (rule 7): `?`/`!` over an sret-class call into
|
||||
// an aggregate let — keep the established #38b/#40
|
||||
// loud-stop marker (mirror of cstage's pre-arm fatal,
|
||||
// cgen.c N_LET; pre-C4 this shape fell through to the
|
||||
// cgtryunw/cgtryprop gates, which the C4 tail below
|
||||
// now pre-empts in let position).
|
||||
if (rhs.kind == nkind.N_TRYUNW
|
||||
|| rhs.kind == nkind.N_TRYPROP) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, rhs.lhs) > 0) {
|
||||
let m38f: str = "#38b: `?`/`!` on an sret-class call result unwired (mem-based unwrap is a #40-family follow-up)\n";
|
||||
os.write(2, m38f.ptr, m38f.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// C4: nothing below this arm can initialise a >8B
|
||||
// struct/array slot — the scalar default's 8B store
|
||||
// was a silent truncation (rule 7).
|
||||
let mf5: str = "let: aggregate init from unhandled rhs shape (task #7/rule-7)\n";
|
||||
os.write(2, mf5.ptr, mf5.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
@@ -32807,7 +32869,12 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
// no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8.
|
||||
// elemsizeof returns 8 for non-primitive elem, which would be
|
||||
// wrong here — compute from the tuple param walk instead.
|
||||
let esz: i32 = elemsizeof(slctn);
|
||||
// C4 (task #7): elemsizeofc, not elemsizeof — a struct element
|
||||
// (`[]thread`, 16B) hit elemsizeof's 8-sentinel while cstage reads
|
||||
// the stamped slc->type sub size (IMULQ $8 vs $16, gate-blind
|
||||
// cs≠ww). elemsizeofc recovers the width from the stamped tinfo
|
||||
// (the #8 named-narrow precedent).
|
||||
let esz: i32 = elemsizeofc(c, slctn);
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) {
|
||||
let total: i32 = 0;
|
||||
@@ -32819,6 +32886,31 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
esz = total;
|
||||
};
|
||||
};
|
||||
// C4 (FC0, task #7): a non-ident scrutinee (`re.charsets`) has no
|
||||
// local tnode — slctn is nil, so esz fell to 1 and the binding
|
||||
// registered typeless (cstage reads the stamped slc->type: esz 24,
|
||||
// slice-header readbacks → cs≠ww). Derive both from the checker-
|
||||
// stamped slc.type_ (tinfo SSoT, the #209/#211 discipline); the
|
||||
// synthesised N_TNAME carries the element tinfo so cgident's
|
||||
// str/slice/float keys read it like a declared local (the str→u8
|
||||
// synthesis precedent above).
|
||||
if (slctn == nil && slc != nil) {
|
||||
let sti2: *tinfo = slc.type_: *tinfo;
|
||||
for (sti2 != nil && sti2.kind == tykind.TY_NAMED) { sti2 = sti2.under; };
|
||||
if (sti2 != nil) {
|
||||
if (sti2.kind == tykind.TY_SLICE
|
||||
|| sti2.kind == tykind.TY_STR
|
||||
|| sti2.kind == tykind.TY_ARRAY) {
|
||||
if (sti2.sub != nil) {
|
||||
esz = sti2.sub.size: i32;
|
||||
let en: *node = newnode(nkind.N_TNAME, slc.file, slc.line, slc.col);
|
||||
en.str = sti2.sub.name;
|
||||
en.type_ = sti2.sub: *void;
|
||||
elemt = en;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let destruct: bool = (n.list != nil);
|
||||
|
||||
// .rgi (counter) + .rgl (length) scratch slots.
|
||||
@@ -32987,18 +33079,64 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
// Per-binding load from BX+foff. Signedness comes from bind_signed
|
||||
// (set via paramissigned → fieldissignedc), so enum-aliased narrows
|
||||
// pick the right MOVS*Q without a literal-name gate.
|
||||
let b: i32 = 0;
|
||||
for (b < nbinds) {
|
||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]: i64);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
// C4 (F5/FC0, task #7): a by-value AGGREGATE element (struct /
|
||||
// tuple / str/slice header, esz > 8) copies its FULL extent — the
|
||||
// single load word truncated it to 8B, so every field past word 0
|
||||
// (str/slice .len/.cap included) read stale slot bytes
|
||||
// (regex.finish's 24B charset binding, gate-blind cs≠ww). Same
|
||||
// word-run + sized-tail idiom as the cglet aggregate copy.
|
||||
if (!destruct && esz > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 8;
|
||||
};
|
||||
if (k + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 4;
|
||||
};
|
||||
if (k + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 2;
|
||||
};
|
||||
if (k + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((bind_off[0] + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 1;
|
||||
};
|
||||
} else {
|
||||
let b: i32 = 0;
|
||||
for (b < nbinds) {
|
||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]: i64);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]: i64);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
};
|
||||
};
|
||||
|
||||
if (n.body != nil) { cgstmt(c, n.body); };
|
||||
|
||||
Reference in New Issue
Block a user