w6c+wwstage: store over-cap tuple sret into local field/index (#234)

The STORE-twin of the Fold-B over-cap-tuple sret RECEIVE (a937d67). Fold B
wired single-var-let / destructure / reassign / return-forward to receive a
> 4-eightbyte (sret) tuple-returning call, but a FIELD or INDEXED-lvalue
dest stayed unwired: the store dropped the callee's sret body (a truncated
MOVQ through a stale RDI) — a silent miscompile, gate-blind because the
bootstrap never field-stores a wide tuple.

Per Rob's ruling A (one class, one commit): convert the silent miscompile
into either a CORRECT store or a LOUD stop, never a fall-through.

  - cstage cmd/w6c/cgen.c: the struct-field N_DOT store and the N_INDEX
    lvalue store each gain an arm keyed on cg_sret_retsize(dest) > 0 &&
    rhs == N_CALL. A LOCAL dest (BP-relative, not via_ptr / global) sets
    cg_sret_dest_off so the callee's hidden RDI writes the WHOLE tuple
    straight into the slot — field: boff + foff; indexed: boff + cidx*esz
    (a CONSTANT index into a local value array, the only indexed form whose
    dest is a static BP offset). Every other dest fatals "#234-tail".
  - wwstage selfhost/cmd/wcc/cgenexpr.ww: symmetric (rule 10). The direct
    struct-local field branch sets c.sretdestoff = lc.off + fi.foff; the
    via_ptr branch, the global branch, and the N_INDEX arm hard-stop loud
    with the same #234-tail diagnostic. The field branches key on
    sretretsize(fi.tnode) > 0 (fi.tnode is a real type-AST node). The
    N_INDEX arm keys its ENTRY on callsretsize(c, n.rhs) > 0 — the
    callee-return-type SSoT (cgenutil.ww) the receive sites use — NOT on
    sretretsize(elemtn): elemtn is only a type node for an N_IDENT base, a
    VALUE node for an N_DOT base (`s.arr[i]`) / chained (`a[i][k]`), which
    fell to sretretsize=0 and let those forms drop SILENTLY through to the
    truncating store. The callee return type equals the dest-element type
    (checker-guaranteed), so the verdict is byte-identical to cstage's
    cg_sret_retsize, and the base-shape split then loud-stops every
    non-local-array form, base-kind-independent.

Deferred (#234-tail): a via_ptr field (`p.f`), a global field (`g.f`), an
N_DOT-base index (`s.arr[i]`), a chained index (`a[i][k]`), and a runtime /
slice / pointer index all need a runtime RDI-pointer dest, which
cg_sret_dest_off (BP-relative only) can't express — they hard-error loud
(rule 7), never a truncating store.

Depends on #237 (committed first): the wwstage struct-field slot for a
tuple field is only correctly sized with that fix, so the struct-field arm
is byte-id-symmetric here.

Test 940: indexed-on-local and local-struct-field rows RUN on both stages
(exit 0) AND assert cs==ww byte-id; readback via a raw pointer
(`(&dest):*int; p[i]`) since a tuple-element read `dest.N` is a separate gap
(#238). Builderr rows assert the via_ptr / global / runtime-index /
N_DOT-base / chained-index forms loud-stop with #234-tail on BOTH drivers
(the N_DOT-base + chained rows are the regression witnesses for the wwstage
silent-store gap closed by the callsretsize re-key). The bootstrap exercises
no such store, so the w6c/wwdump combined amalgams regen with no asm change
(byte-id-neutral bootstrap; the new hard-error never fires self-compiling).
This commit is contained in:
2026-06-01 17:35:04 +09:00
parent 93d8ece740
commit 20fe5419d2
6 changed files with 679 additions and 0 deletions

View File

@@ -23569,6 +23569,54 @@ fn cgassign(c: *cgen, n: *node) void = {
return;
};
};
// #234: over-cap sret STORE into an indexed lvalue —
// `arr[i] = wide();` STORE-twin of the Fold-B sret RECEIVE
// (a937d67). c.sretdestoff is a STATIC BP-relative offset, so
// only a CONSTANT index into a LOCAL value array yields a
// static dest slot (off + idx*esz) the callee can sret
// straight into. Every other indexed form — runtime index,
// slice/ptr base, N_DOT-base (`s.arr[i]`), chained (`a[i][k]`),
// global base — needs the runtime RDI-pointer dest variant
// deferred to #234-tail and HARD-STOPS loud (rule 7). Mirror of
// cstage cgen.c (#234) N_INDEX arm.
//
// Gate ENTRY on callsretsize(n.rhs) — the callee-return-type
// SSoT (cgenutil.ww) the receive sites use — NOT on
// sretretsize(elemtn): elemtn is only a type node for an
// N_IDENT base, but a value node for N_DOT (4695) / chained
// (4710), which fell to sretretsize=0 and let those forms
// drop SILENTLY through to the truncating store. The callee
// return type equals the dest-element type (checker-guaranteed),
// so the verdict is byte-identical to cstage's cg_sret_retsize.
// The base-shape split below then loud-stops every non-local-
// array form, base-kind-independent.
if (n.rhs != nil && n.rhs.kind == nkind.N_CALL
&& callsretsize(c, n.rhs) > 0) {
let islocalarr: bool = false;
if (baselocal != nil) {
let btn: *node = baselocal.tnode;
if (btn != nil) {
if (btn.kind == nkind.N_TARRAY) { islocalarr = true; };
};
};
let constidx: bool = false;
let cidx: i32 = 0;
if (idx != nil) {
if (idx.kind == nkind.N_INTLIT) {
constidx = true;
cidx = idx.uval: i32;
};
};
if (!islocalarr || !constidx) {
let m234: str = "#234-tail: over-cap tuple sret store to non-local/dynamic-index dest unsupported\n";
os.write(2, m234.ptr, m234.len: u64);
os.exit(1);
};
c.sretdestoff = baselocal.off + cidx * esz;
cgexpr(c, n.rhs);
c.sretdestoff = 0;
return;
};
cgexpr(c, n.rhs); // value → AX
// str/slice: spill cap (CX) + len (BX) before
// computing the index so the post-index store can
@@ -24138,6 +24186,18 @@ fn cgassign(c: *cgen, n: *node) void = {
n.rhs, "BX", fi.foff, fsz);
return;
};
// #234-tail: via-ptr (`p.f`) sret field STORE. The dest must
// be a runtime RDI pointer (the BP-relative c.sretdestoff
// can't name `p.f`); deferred. HARD-STOP loud, never fall
// through to the truncating generic store (rule 7).
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_CALL
&& sretretsize(c, fi.tnode) > 0) {
let m234: str = "#234-tail: over-cap tuple sret store to via-ptr field dest unsupported\n";
os.write(2, m234.ptr, m234.len: u64);
os.exit(1);
};
// struct-typed field via *struct base — three
// rhs shapes (call/structlit added with #5;
// closes #27 marker here):
@@ -24359,6 +24419,23 @@ fn cgassign(c: *cgen, n: *node) void = {
n.rhs, "BP", lc.off + fi.foff, fsz);
return;
};
// #234: over-cap sret STORE into a LOCAL struct field —
// `s.f = wide();` where f's type returns via sret
// (sretretsize > 0: a >24B struct OR an over-cap tuple).
// STORE-twin of the Fold-B sret RECEIVE (a937d67): point
// the callee's hidden RDI dest at the field slot
// (c.sretdestoff = lc.off + fi.foff) so it writes the
// WHOLE value there, never the truncating generic store
// below. Mirror of cstage cgen.c (#234) field local arm.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_CALL
&& sretretsize(c, fi.tnode) > 0) {
c.sretdestoff = lc.off + fi.foff;
cgexpr(c, n.rhs);
c.sretdestoff = 0;
return;
};
// struct-typed field on a direct struct
// local — three rhs shapes (call/structlit
// added with #5; closes #27 marker here):
@@ -24638,6 +24715,18 @@ fn cgassign(c: *cgen, n: *node) void = {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fld)) {
// #234-tail: GLOBAL (`g.f`) sret field STORE. c.sretdestoff is
// BP-relative only and can't name a global slot; the runtime
// RDI-pointer dest variant is deferred. HARD-STOP loud, never
// the truncating generic store (rule 7).
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_CALL
&& sretretsize(c, fi.tnode) > 0) {
let m234: str = "#234-tail: over-cap tuple sret store to global field dest unsupported\n";
os.write(2, m234.ptr, m234.len: u64);
os.exit(1);
};
// struct-typed field on a global struct base —
// three rhs shapes (call/structlit added with
// #5; closes #27 marker here):