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

@@ -3468,6 +3468,32 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
break;
}
/* #234: over-cap sret STORE into a struct field —
* `s.f = wide();` where f's type returns via sret
* (cg_sret_retsize > 0: a >24B struct OR an over-cap
* tuple — Fold A made the callee sret it). The STORE-
* twin of the Fold-B sret RECEIVE (a937d67): point the
* callee's hidden RDI dest straight at the field slot
* (cg_sret_dest_off) so it writes the WHOLE value there.
* Without this the generic scalar store below emits a
* truncated `MOVQ AX, off(BP)` and silently drops the
* sret body. cg_sret_dest_off is BP-relative ONLY, so
* this covers a LOCAL struct base; a via_ptr (`p.f`) or
* global base needs the runtime RDI-pointer dest variant
* deferred to #234-tail and HARD-STOPS loud (rule 7 —
* never fall through to the truncating store). */
if (n->op == TK_ASSIGN && n->rhs
&& n->rhs->kind == N_CALL
&& cg_sret_retsize(f->type) > 0) {
if (via_ptr || is_global || boff == 0)
fatal("#234-tail: over-cap tuple "
"sret store to non-local dest "
"unsupported");
cg_sret_dest_off = boff + foff;
cgexpr(c, n->rhs, locals);
cg_sret_dest_off = 0;
break;
}
/* struct-typed field, three rhs shapes:
* - N_IDENT: word-copy from the rhs slot directly
* onto the destination field. cgexpr cannot
@@ -4560,6 +4586,32 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
break;
}
/* #234: over-cap sret STORE into an indexed lvalue —
* `arr[i] = wide();` STORE-twin of the Fold-B sret RECEIVE
* (a937d67). cg_sret_dest_off is a STATIC BP-relative
* offset, so only a CONSTANT index into a LOCAL value array
* yields a static dest slot (boff + idx*esz) the callee can
* sret straight into. Every other indexed form — runtime
* index, slice/ptr base, global base — needs the runtime
* RDI-pointer dest variant deferred to #234-tail and HARD-
* STOPS loud (rule 7 — never the truncating store below). */
if (n->op == TK_ASSIGN && n->rhs
&& n->rhs->kind == N_CALL
&& esub && cg_sret_retsize(esub) > 0) {
int cidx = (n->lhs->rhs
&& n->lhs->rhs->kind == N_INTLIT)
? (int)n->lhs->rhs->uval : -1;
int sboff = (base->kind == N_IDENT)
? localfind(locals, base->str) : 0;
if (!is_arr || cidx < 0 || sboff == 0)
fatal("#234-tail: over-cap tuple sret "
"store to non-local dest "
"unsupported");
cg_sret_dest_off = sboff + cidx * esz;
cgexpr(c, n->rhs, locals);
cg_sret_dest_off = 0;
break;
}
if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN) {
cgexpr(c, n->rhs, locals); /* AX=ptr (BX=len,CX=cap if str) */
/* str/slice: stash cap+len so all three store