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:
@@ -10472,6 +10472,18 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 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 out with
|
||||
* havesrc=0: cstage emitted NOTHING (slot
|
||||
* uninitialised), wwstage's scalar default truncated
|
||||
* to 8B — gate-blind cs≠ww. */
|
||||
if (!havesrc && cgplaceaddr(c, n->rhs, D_SI, *locals))
|
||||
havesrc = 1;
|
||||
if (havesrc) {
|
||||
int k = 0;
|
||||
for (; k + 8 <= sz; k += 8) {
|
||||
@@ -10503,6 +10515,11 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* C4: nothing below this arm can initialise a >8B
|
||||
* struct/array slot — every fall-through was a silent
|
||||
* miscompile (rule 7). */
|
||||
fatal("let: aggregate init from unhandled rhs shape "
|
||||
"(task #7/rule-7)");
|
||||
}
|
||||
if (n->rhs && sz == 8) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
@@ -11513,11 +11530,47 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
||||
}
|
||||
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
||||
/* load each binding from BX + foff into its slot */
|
||||
for (int b = 0; b < nbinds; b++) {
|
||||
int op = fldloadop(binds[b].ftype, binds[b].sz);
|
||||
ins2(c, op, amem(D_BX, binds[b].foff), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, binds[b].off));
|
||||
/* load each binding from BX + foff into its slot. C4 (F5/FC0,
|
||||
* task #7): a by-value AGGREGATE element (struct / tuple /
|
||||
* str/slice header, esz > 8) copies its FULL extent — the
|
||||
* single fldloadop 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 N_LET
|
||||
* aggregate copy. */
|
||||
if (!destruct && esz > 8) {
|
||||
int k = 0;
|
||||
for (; k + 8 <= esz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BX, k), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, binds[0].off + k));
|
||||
}
|
||||
if (k + 4 <= esz) {
|
||||
ins2(c, A_MOVL, amem(D_BX, k), areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(D_BP, binds[0].off + k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= esz) {
|
||||
ins2(c, A_MOVW, amem(D_BX, k), areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX),
|
||||
amem(D_BP, binds[0].off + k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= esz) {
|
||||
ins2(c, A_MOVB, amem(D_BX, k), areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(D_BP, binds[0].off + k));
|
||||
k += 1;
|
||||
}
|
||||
} else {
|
||||
for (int b = 0; b < nbinds; b++) {
|
||||
int op = fldloadop(binds[b].ftype, binds[b].sz);
|
||||
ins2(c, op, amem(D_BX, binds[b].foff),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, binds[b].off));
|
||||
}
|
||||
}
|
||||
cgstmt(c, n->body, locals, frame);
|
||||
label(c, rpost);
|
||||
|
||||
@@ -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); };
|
||||
|
||||
@@ -105,6 +105,25 @@
|
||||
* | (*ts)[i] compound/store (p7 hot shape) |
|
||||
* c3_free_operand | FA6: free((*ts)[i].slicefield) reads | 69
|
||||
* | through the operand (pA9) |
|
||||
* c4_let_slice_elem | F5: let e = xs[0], 16B struct elem | 70
|
||||
* | (p6min13 graduated — cstage emitted |
|
||||
* | NOTHING, wwstage 8B truncation) |
|
||||
* c4_let_elem_sizes | F5: 24B/40B/12B(MOVQ+MOVL tail) elems | 71
|
||||
* c4_let_deref_leaf | reviewer-C2 inheritance: let c = | 72
|
||||
* | (*ts)[i].cap (40B leaf behind deref |
|
||||
* | spine; ww loud retired, cs no-copy |
|
||||
* | fixed) |
|
||||
* c4_range_byvalue | F5/FC0: by-value range payload full- | 73
|
||||
* | width copy — []struct (sum BOTH |
|
||||
* | fields), []capture 40B (str field |
|
||||
* | header), []str (24B header .len) |
|
||||
* c4_range_edges | range edges: empty slice (zero iters), | 74
|
||||
* | binder MUTATION (by-VALUE: writes must |
|
||||
* | not reach the slice), 12B elem |
|
||||
* | (MOVQ+MOVL tail in the range copy) |
|
||||
* reject_let_unhandled| `let p: pair = mk()!` — non-place rhs | BUILD_FAIL
|
||||
* | dies at C4's loud let tail (was |
|
||||
* | SILENT: cs no init, ww 8B) |
|
||||
*
|
||||
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
|
||||
* both stages) — a build that fails for any other reason (parse error,
|
||||
@@ -1114,6 +1133,179 @@ static const struct row rows[] = {
|
||||
"\treturn 69;\n"
|
||||
"};\n",
|
||||
69, NULL },
|
||||
|
||||
/* C4 (F5, task #7): `let e = xs[0]` from a SLICE base — the #265
|
||||
* source-addr enumeration had TY_ARRAY/N_DOT/N_INDEX bases but no
|
||||
* TY_SLICE base, so cstage emitted NOTHING (slot uninitialised)
|
||||
* and wwstage fell to its scalar default (8B truncation) —
|
||||
* gate-blind cs≠ww. p6min13 graduated verbatim; the slice base
|
||||
* now resolves through cgplaceaddr.
|
||||
*
|
||||
* LANDMINE (task #43): the UNANNOTATED form `let e = xs[0]` is
|
||||
* still cs≠ww — wwstage's aggn/letslotsize gates are annotation-
|
||||
* keyed and skip the copy arm (8B truncation) while cstage keys
|
||||
* the stamped n->type and full-copies. No dual-driver row can pin
|
||||
* a divergent shape; every row below annotates. Do NOT read the
|
||||
* annotated rows as covering the inferred form. */
|
||||
{ "c4_let_slice_elem",
|
||||
"package main;\n"
|
||||
"type th = struct { pc: size, matched: bool };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []th = [];\n"
|
||||
"\tappend(xs, th { pc = 7, matched = true });\n"
|
||||
"\tlet e: th = xs[0];\n"
|
||||
"\tif (e.pc != 7) { return 1; };\n"
|
||||
"\tif (!e.matched) { return 2; };\n"
|
||||
"\treturn 70;\n"
|
||||
"};\n",
|
||||
70, NULL },
|
||||
|
||||
/* C4 size matrix: 24B (3-word run), 40B (str header + 2 words),
|
||||
* and 12B (maxalign 4 → MOVQ+MOVL tail) elements. */
|
||||
{ "c4_let_elem_sizes",
|
||||
"package main;\n"
|
||||
"type t3 = struct { a: i64, b: i64, c: i64 };\n"
|
||||
"type capture = struct { content: str, start: size, end: size };\n"
|
||||
"type t12 = struct { x: u32, y: u32, z: u32 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []t3 = [];\n"
|
||||
"\tappend(xs, t3 { a = 1, b = 2, c = 3 });\n"
|
||||
"\tappend(xs, t3 { a = 4, b = 5, c = 6 });\n"
|
||||
"\tlet e: t3 = xs[1];\n"
|
||||
"\tif (e.a != 4) { return 1; };\n"
|
||||
"\tif (e.b != 5) { return 2; };\n"
|
||||
"\tif (e.c != 6) { return 3; };\n"
|
||||
"\tlet cs: []capture = [];\n"
|
||||
"\tappend(cs, capture { content = \"abc\", start = 11, end = 13 });\n"
|
||||
"\tlet c: capture = cs[0];\n"
|
||||
"\tif (c.content.len != 3) { return 4; };\n"
|
||||
"\tif (c.start != 11) { return 5; };\n"
|
||||
"\tif (c.end != 13) { return 6; };\n"
|
||||
"\tlet ns: []t12 = [];\n"
|
||||
"\tappend(ns, t12 { x = 7u32, y = 8u32, z = 9u32 });\n"
|
||||
"\tlet n: t12 = ns[0];\n"
|
||||
"\tif (n.x != 7u32) { return 7; };\n"
|
||||
"\tif (n.y != 8u32) { return 8; };\n"
|
||||
"\tif (n.z != 9u32) { return 9; };\n"
|
||||
"\treturn 71;\n"
|
||||
"};\n",
|
||||
71, NULL },
|
||||
|
||||
/* Reviewer-C2 inheritance: aggregate leaf behind a deref spine
|
||||
* into a let — `let c = (*ts)[i].cap`. Pre-C4 wwstage loud-bailed
|
||||
* at its cgdot aggregate-leaf gate (correct per rule 7) while
|
||||
* cstage SILENTLY emitted no copy; both now route the let copy
|
||||
* through the resolver. */
|
||||
{ "c4_let_deref_leaf",
|
||||
"package main;\n"
|
||||
"type capture = struct { content: str, start: size, end: size };\n"
|
||||
"type t = struct { pc: size, cap: capture };\n"
|
||||
"fn grab(ts: *[]t, i: size) i32 = {\n"
|
||||
"\tlet c: capture = (*ts)[i].cap;\n"
|
||||
"\tif (c.start != 11) { return 1; };\n"
|
||||
"\tif (c.end != 13) { return 2; };\n"
|
||||
"\tif (c.content.len != 3) { return 3; };\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"
|
||||
"\tlet rc: i32 = grab(&ts, 0);\n"
|
||||
"\tif (rc != 0) { return rc; };\n"
|
||||
"\treturn 72;\n"
|
||||
"};\n",
|
||||
72, NULL },
|
||||
|
||||
/* C4 (F5/FC0): by-value range payload copies through the same
|
||||
* full-width machinery — pre-C4 the single fldloadop word
|
||||
* truncated every aggregate element to 8B (cstage), wwstage's
|
||||
* structural esz fell to its 8-sentinel (or 1 for non-ident
|
||||
* scrutinees: regex.finish's cs≠ww hunk) and the ww checker left
|
||||
* the binding typeless (asserttyped bail on any field read).
|
||||
* Rows: []struct sum of BOTH fields, []capture (40B, str header
|
||||
* inside), []str (24B header, .len readback). */
|
||||
{ "c4_range_byvalue",
|
||||
"package main;\n"
|
||||
"type th = struct { pc: size, weight: size };\n"
|
||||
"type capture = struct { content: str, start: size, end: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []th = [];\n"
|
||||
"\tappend(xs, th { pc = 7, weight = 100 });\n"
|
||||
"\tappend(xs, th { pc = 8, weight = 200 });\n"
|
||||
"\tlet total: size = 0;\n"
|
||||
"\tfor (let t .. xs) {\n"
|
||||
"\t\ttotal += t.pc;\n"
|
||||
"\t\ttotal += t.weight;\n"
|
||||
"\t};\n"
|
||||
"\tif (total != 315) { return 1; };\n"
|
||||
"\tlet cs: []capture = [];\n"
|
||||
"\tappend(cs, capture { content = \"abc\", start = 11, end = 13 });\n"
|
||||
"\tappend(cs, capture { content = \"wy\", start = 21, end = 23 });\n"
|
||||
"\tlet csum: size = 0;\n"
|
||||
"\tfor (let cp .. cs) {\n"
|
||||
"\t\tcsum += cp.content.len: size;\n"
|
||||
"\t\tcsum += cp.start;\n"
|
||||
"\t\tcsum += cp.end;\n"
|
||||
"\t};\n"
|
||||
"\tif (csum != 73) { return 2; };\n"
|
||||
"\tlet ss: []str = [];\n"
|
||||
"\tappend(ss, \"hello\");\n"
|
||||
"\tappend(ss, \"worldly\");\n"
|
||||
"\tlet slen: size = 0;\n"
|
||||
"\tfor (let s .. ss) {\n"
|
||||
"\t\tslen += s.len: size;\n"
|
||||
"\t};\n"
|
||||
"\tif (slen != 12) { return 3; };\n"
|
||||
"\treturn 73;\n"
|
||||
"};\n",
|
||||
73, NULL },
|
||||
|
||||
/* C4 range edges: zero-iteration over an empty slice; binder
|
||||
* MUTATION pinning Hare's by-VALUE payload semantics (writes to
|
||||
* the binder land in its frame slot copy, never the slice — a
|
||||
* by-reference regression would flip xs[0]); and a 12B element
|
||||
* exercising the sized MOVL tail of the range word-copy (the
|
||||
* 16B/24B/40B rows above are all full-word). */
|
||||
{ "c4_range_edges",
|
||||
"package main;\n"
|
||||
"type th = struct { pc: size, matched: bool };\n"
|
||||
"type t12 = struct { x: u32, y: u32, z: u32 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet es: []th = [];\n"
|
||||
"\tfor (let t .. es) { return 1; };\n"
|
||||
"\tlet xs: []th = [];\n"
|
||||
"\tappend(xs, th { pc = 7, matched = true });\n"
|
||||
"\tlet cnt: size = 0;\n"
|
||||
"\tfor (let t2 .. xs) { t2.pc = 99; t2.matched = false; cnt += 1; };\n"
|
||||
"\tif (cnt != 1) { return 2; };\n"
|
||||
"\tif (xs[0].pc != 7) { return 3; };\n"
|
||||
"\tif (!xs[0].matched) { return 4; };\n"
|
||||
"\tlet ns: []t12 = [];\n"
|
||||
"\tappend(ns, t12 { x = 1u32, y = 2u32, z = 3u32 });\n"
|
||||
"\tappend(ns, t12 { x = 4u32, y = 5u32, z = 6u32 });\n"
|
||||
"\tlet sum: size = 0;\n"
|
||||
"\tfor (let n .. ns) { sum += n.x: size; sum += n.y: size; sum += n.z: size; };\n"
|
||||
"\tif (sum != 21) { return 5; };\n"
|
||||
"\treturn 74;\n"
|
||||
"};\n",
|
||||
74, NULL },
|
||||
|
||||
/* C4 loud tail: a non-place rhs (tagged unwrap into a struct let)
|
||||
* has no source address and nothing below the aggregate arm can
|
||||
* initialise a >8B slot — pre-C4 cstage emitted NO init and
|
||||
* wwstage stored 8B, both silent. */
|
||||
{ "reject_let_unhandled",
|
||||
"package main;\n"
|
||||
"type pair = struct { a: i64, b: i64 };\n"
|
||||
"fn mk() (pair | nomem) = {\n"
|
||||
"\treturn pair { a = 1, b = 2 };\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet p: pair = mk()!;\n"
|
||||
"\treturn p.a: i32;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL,
|
||||
"let: aggregate init from unhandled rhs shape (task #7/rule-7)" },
|
||||
};
|
||||
|
||||
/* errlog_has — the build-failure stderr must carry the row's expected
|
||||
|
||||
Reference in New Issue
Block a user