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:
9
Makefile
9
Makefile
@@ -289,6 +289,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_subslice_ptresz_run \
|
||||
$(BIN)/test_deref_slice_store_run \
|
||||
$(BIN)/test_tuple_nary_destructure_run \
|
||||
$(BIN)/test_overcap_tuple_field_store_run \
|
||||
$(BIN)/test_str_forrange_loopvar_run \
|
||||
$(BIN)/test_composite_call_arg \
|
||||
$(BIN)/test_composite_call_arg_run \
|
||||
@@ -1160,6 +1161,14 @@ $(BIN)/test_tuple_nary_destructure_run: test/wcc/945_tuple_nary_destructure_run.
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# #234: over-cap tuple sret store into a local struct field / indexed local
|
||||
# (run + cs==ww byte-id), deferred forms loud-stop (builderr both drivers).
|
||||
$(BIN)/test_overcap_tuple_field_store_run: test/wcc/940_overcap_tuple_field_store_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_composite_call_arg: test/wcc/723_composite_call_arg.c \
|
||||
$(BIN)/w6c $(BIN)/w6c_ww | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -4785,6 +4785,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
|
||||
@@ -5354,6 +5402,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):
|
||||
@@ -5575,6 +5635,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):
|
||||
@@ -5854,6 +5931,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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
351
test/wcc/940_overcap_tuple_field_store_run.c
Normal file
351
test/wcc/940_overcap_tuple_field_store_run.c
Normal file
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* 940_overcap_tuple_field_store_run — project #234: storing the result of an
|
||||
* over-capacity tuple-returning call (sret: > 4 GP / > 2 SSE eightbytes,
|
||||
* e.g. ([]u8,[]u8) = 6 GP) into a struct field or an indexed lvalue.
|
||||
*
|
||||
* The receive end (project #10 Fold B) wired single-var-let / destructure /
|
||||
* reassign / return-forward, but a FIELD or INDEXED 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. The fix points the callee's hidden sret dest (RDI)
|
||||
* straight at the destination slot when it is a LOCAL (BP-relative), so the
|
||||
* callee writes the WHOLE tuple there:
|
||||
* - LOCAL struct field `s.f = wide();` dest = s.off + field.off.
|
||||
* - INDEXED local array `arr[i] = wide();` with a CONSTANT index — dest
|
||||
* = arr.off + idx*esz (the only indexed form whose
|
||||
* dest is a static BP offset).
|
||||
* Every other dest — via_ptr field (`p.f`), global field (`g.f`), runtime /
|
||||
* slice / pointer index — needs a runtime RDI-pointer dest (deferred to
|
||||
* #234-tail) and HARD-ERRORS LOUD, never a silent truncating store (rule 7).
|
||||
*
|
||||
* Both stages emit byte-identically (rule 10); the wwstage struct-field slot
|
||||
* is correctly sized only with #237 underneath (a tuple struct field's slot
|
||||
* width), committed first in this branch.
|
||||
*
|
||||
* Rows (cap != len in every wide element so a dropped len OR cap is caught):
|
||||
* A indexed_local `arr[1] = wide()`, arr: [2]([]u8,[]u8). RUN both stages
|
||||
* + cs==ww byte-id. Readback via `(&arr[1]):*int` p[i]
|
||||
* (a tuple-element READ `arr[1].N` is a SEPARATE gap,
|
||||
* filed #238) — len0=1,cap0=5,len1=2,cap1=6.
|
||||
* B struct_field `s.f = wide()`, S = struct { hdr:int, f:([]u8,[]u8) }.
|
||||
* foff!=0 (after hdr) + hdr-uncorrupted check. RUN both
|
||||
* stages + cs==ww byte-id, pointer readback `(&s.f):*int`.
|
||||
* C via_ptr_be `p.f = wide()` (p:*S) — BUILDERR, loud #234-tail on
|
||||
* BOTH drivers.
|
||||
* D global_be `g.f = wide()` (g a struct global) — BUILDERR, loud.
|
||||
* E rtindex_be `arr[idx()] = wide()` (runtime index) — BUILDERR, loud.
|
||||
* F ndot_index_be `s.arr[1] = wide()` (N_DOT-base index, array field of a
|
||||
* local) — BUILDERR, loud. Regression witness: this form
|
||||
* shipped SILENT on wwstage when the N_INDEX arm keyed its
|
||||
* entry on sretretsize(elemtn) — elemtn is a value node for
|
||||
* an N_DOT base, so the gate returned 0 and fell through to
|
||||
* the truncating store. Re-keying on callsretsize(rhs)
|
||||
* (callee-return SSoT) fires the base-shape loud-stop.
|
||||
* G chained_index_be `a[0][1] = wide()` (chained N_INDEX base) — same class,
|
||||
* BUILDERR, loud on both stages.
|
||||
*
|
||||
* GATE POLARITY: A/B must build+run exit 0 on BOTH stages AND be cs==ww
|
||||
* byte-identical; C/D/E must FAIL the build with the cited #234-tail
|
||||
* diagnostic on BOTH stages (rule 7 — loud, not an incidental error).
|
||||
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
|
||||
* race does not apply (945/799 precedent).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
file_contains(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[8192];
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
slurp_eq(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb");
|
||||
FILE *fb = fopen(b, "rb");
|
||||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
||||
int rc = 0;
|
||||
for (;;) {
|
||||
int ca = fgetc(fa), cb = fgetc(fb);
|
||||
if (ca != cb) { rc = -1; break; }
|
||||
if (ca == EOF) break;
|
||||
}
|
||||
fclose(fa); fclose(fb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
|
||||
#define K_BUILDERR 1 /* build must FAIL with experr on both drivers */
|
||||
|
||||
struct row { const char *label; const char *src; int kind; int want;
|
||||
const char *experr; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "indexed_local",
|
||||
"package main;\n"
|
||||
"fn wide() ([]u8, []u8) = {\n"
|
||||
" let a: []u8; a.len = 1; a.cap = 5;\n"
|
||||
" let b: []u8; b.len = 2; b.cap = 6;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let arr: [2]([]u8, []u8);\n"
|
||||
" arr[1] = wide();\n"
|
||||
" let p: *int = (&arr[1]): *int;\n"
|
||||
" if (p[1] != 1) { return 1; };\n"
|
||||
" if (p[2] != 5) { return 2; };\n"
|
||||
" if (p[4] != 2) { return 3; };\n"
|
||||
" if (p[5] != 6) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", K_RUN, 0, NULL },
|
||||
{ "struct_field",
|
||||
"package main;\n"
|
||||
"fn wide() ([]u8, []u8) = {\n"
|
||||
" let a: []u8; a.len = 1; a.cap = 5;\n"
|
||||
" let b: []u8; b.len = 2; b.cap = 6;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"type S = struct { hdr: int, f: ([]u8, []u8) };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: S;\n"
|
||||
" s.hdr = 99;\n"
|
||||
" s.f = wide();\n"
|
||||
" if (s.hdr != 99) { return 9; };\n"
|
||||
" let p: *int = (&s.f): *int;\n"
|
||||
" if (p[1] != 1) { return 1; };\n"
|
||||
" if (p[2] != 5) { return 2; };\n"
|
||||
" if (p[4] != 2) { return 3; };\n"
|
||||
" if (p[5] != 6) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", K_RUN, 0, NULL },
|
||||
{ "via_ptr_be",
|
||||
"package main;\n"
|
||||
"fn wide() ([]u8, []u8) = {\n"
|
||||
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"type S = struct { f: ([]u8, []u8) };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: S;\n"
|
||||
" let p: *S = &s;\n"
|
||||
" p.f = wide();\n"
|
||||
" return 0;\n"
|
||||
"};\n", K_BUILDERR, 0, "#234-tail" },
|
||||
{ "global_be",
|
||||
"package main;\n"
|
||||
"fn wide() ([]u8, []u8) = {\n"
|
||||
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"type S = struct { f: ([]u8, []u8) };\n"
|
||||
"let g: S;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" g.f = wide();\n"
|
||||
" return 0;\n"
|
||||
"};\n", K_BUILDERR, 0, "#234-tail" },
|
||||
{ "rtindex_be",
|
||||
"package main;\n"
|
||||
"fn wide() ([]u8, []u8) = {\n"
|
||||
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"fn ix() int = { return 0; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let arr: [2]([]u8, []u8);\n"
|
||||
" arr[ix()] = wide();\n"
|
||||
" return 0;\n"
|
||||
"};\n", K_BUILDERR, 0, "#234-tail" },
|
||||
/* N_DOT-base index `s.arr[1] = wide()`: array-field-of-local. The
|
||||
* dest is a static BP offset but the N_INDEX arm only wires a plain
|
||||
* N_IDENT base, so this is a deferred form — MUST loud-stop. Witness
|
||||
* for the gap that shipped silently on wwstage when the arm was keyed
|
||||
* on sretretsize(elemtn) (a value node for an N_DOT base → 0 → fell
|
||||
* through to the truncating store); now keyed on callsretsize(rhs). */
|
||||
{ "ndot_index_be",
|
||||
"package main;\n"
|
||||
"fn wide() ([]u8, []u8) = {\n"
|
||||
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"type S = struct { arr: [2]([]u8, []u8) };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: S;\n"
|
||||
" s.arr[1] = wide();\n"
|
||||
" return 0;\n"
|
||||
"};\n", K_BUILDERR, 0, "#234-tail" },
|
||||
/* Chained index `a[0][1] = wide()`: N_INDEX base, same deferred class
|
||||
* — loud-stop on both stages. */
|
||||
{ "chained_index_be",
|
||||
"package main;\n"
|
||||
"fn wide() ([]u8, []u8) = {\n"
|
||||
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: [2][2]([]u8, []u8);\n"
|
||||
" a[0][1] = wide();\n"
|
||||
" return 0;\n"
|
||||
"};\n", K_BUILDERR, 0, "#234-tail" },
|
||||
};
|
||||
|
||||
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], errf[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/ocf_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/ocf_%d_d_%d", getpid(), i);
|
||||
snprintf(errf, sizeof errf, "/tmp/ocf_%d_e_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>%s",
|
||||
tmpdir, driver, src, errf);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
if (r->kind == K_BUILDERR) {
|
||||
int ok = (brc != 0)
|
||||
&& (r->experr == NULL || file_contains(errf, r->experr));
|
||||
if (!ok)
|
||||
fprintf(stderr, "row[%s]: %s expected loud #234-tail "
|
||||
"builderr (brc=%d)\n", r->label, driver, brc);
|
||||
unlink(src); unlink(errf); rmdir(tmpdir);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
if (brc != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); unlink(errf); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[256];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); unlink(errf); rmdir(tmpdir);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
|
||||
r->label, driver, got, r->want);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cs==ww .s byte-id (rule 10) for a K_RUN row. */
|
||||
static int
|
||||
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
|
||||
{
|
||||
char src[96], cs_s[96], ws_s[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/ocf_bi_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs_s, sizeof cs_s, "/tmp/ocf_bi_%d_%d_cs.s", getpid(), i);
|
||||
snprintf(ws_s, sizeof ws_s, "/tmp/ocf_bi_%d_%d_ww.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
int rc = 0;
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src);
|
||||
if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c failed\n", r->label); rc = 1; }
|
||||
else {
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, src);
|
||||
if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c_ww failed\n", r->label); rc = 1; }
|
||||
else if (slurp_eq(cs_s, ws_s) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
|
||||
"(rule-10 byte-id)\n", r->label);
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
unlink(src); unlink(cs_s); unlink(ws_s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640], w6c[640], w6c_ww[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "overcap_tuple_field_store: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* cs==ww byte-id for the K_RUN rows. */
|
||||
if (access(w6c_ww, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].kind != K_RUN) continue;
|
||||
total++;
|
||||
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "overcap_tuple_field_store: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("overcap_tuple_field_store: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user