From 36be9f469dc80ce40883e9f3d59223cdbe55e9af Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 4 Jun 2026 02:27:06 +0900 Subject: [PATCH] w6c+w6c_ww: loud-stop non-ident/non-local append spread source (#34 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A spread whose source was not a local ident fell PAST the spread arm: cstage continued into the single-value stores with the N_SPREAD node (garbage store), wwstage silently SKIPPED the value entirely — a silent, cs!=ww-divergent miscompile (append(ys, f()...): cs exit 0 / ww exit 144, want 3), reachable for every element kind and predating #34 for scalars. Both stages now rule-7 loud-stop the shape (deferred, task #37). Also pins the widener's already-tagged single-value source path (tagged_ident_src row, i64 + bool members) and adds the spread_call_loudstop BUILD_FAIL row — test 800 is now 15 rows / 43 fixtures. --- cmd/w6c/cgen.c | 14 +++++++++ selfhost/cmd/w6c/main.combined.ww | 18 +++++++++-- selfhost/cmd/wcc/cgenexpr.ww | 18 +++++++++-- selfhost/cmd/wwdump/main.combined.ww | 18 +++++++++-- test/wcc/800_append_wide_elem.c | 45 ++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 9 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 1826440a..c4beca5b 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -6357,6 +6357,20 @@ cgexpr(Cg *c, Node *n, Local *locals) fatal("#34: append() element kind " "unsupported (rule-7)"); for (Node *vn = sn->next; vn; vn = vn->next) { + /* #34 review: a spread whose source is not a + * local ident used to fall PAST the spread arm + * into the single-value stores with the + * N_SPREAD node (cstage garbage store; wwstage + * silently SKIPPED it — divergent). Deferred + * source shape, task #37. */ + if (vn->kind == N_SPREAD) { + if (!vn->lhs || vn->lhs->kind != N_IDENT) + fatal("#34: append() spread source " + "shape unsupported (rule-7)"); + if (localfind(locals, vn->lhs->str) == 0) + fatal("#34: append() spread source " + "ident is not a local (rule-7)"); + } if (vn->kind == N_SPREAD && vn->lhs && vn->lhs->kind == N_IDENT) { int it_off = localfind(locals, vn->lhs->str); diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 7c37c445..3ba6796e 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -23942,11 +23942,23 @@ fn cgappend(c: *cgen, n: *node) void = { let vn: *node = sn.next; for (vn != nil) { if (vn.kind == nkind.N_SPREAD) { + // #34 review: a non-ident/non-local spread source used + // to be silently SKIPPED here while cstage fell past + // its spread arm into the single-value stores with the + // N_SPREAD node (garbage store) — divergent. Deferred + // source shape, task #37. let it: *node = vn.lhs; - if (it == nil) { vn = vn.next; continue; }; - if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; }; + if (it == nil || it.kind != nkind.N_IDENT) { + let m34p: str = "#34: append() spread source shape unsupported (rule-7)\n"; + os.write(2, m34p.ptr, m34p.len: u64); + os.exit(1); + }; let itlocal: *local = localfindnode(c, it.str); - if (itlocal == nil) { vn = vn.next; continue; }; + if (itlocal == nil) { + let m34q: str = "#34: append() spread source ident is not a local (rule-7)\n"; + os.write(2, m34q.ptr, m34q.len: u64); + os.exit(1); + }; let it_off: i32 = itlocal.off; let load_op: str = tnodeloadop(c, etnode, esz); emitline("\tSUBQ\t$8, SP\n"); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 1aa52644..06051375 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -4149,11 +4149,23 @@ fn cgappend(c: *cgen, n: *node) void = { let vn: *node = sn.next; for (vn != nil) { if (vn.kind == nkind.N_SPREAD) { + // #34 review: a non-ident/non-local spread source used + // to be silently SKIPPED here while cstage fell past + // its spread arm into the single-value stores with the + // N_SPREAD node (garbage store) — divergent. Deferred + // source shape, task #37. let it: *node = vn.lhs; - if (it == nil) { vn = vn.next; continue; }; - if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; }; + if (it == nil || it.kind != nkind.N_IDENT) { + let m34p: str = "#34: append() spread source shape unsupported (rule-7)\n"; + os.write(2, m34p.ptr, m34p.len: u64); + os.exit(1); + }; let itlocal: *local = localfindnode(c, it.str); - if (itlocal == nil) { vn = vn.next; continue; }; + if (itlocal == nil) { + let m34q: str = "#34: append() spread source ident is not a local (rule-7)\n"; + os.write(2, m34q.ptr, m34q.len: u64); + os.exit(1); + }; let it_off: i32 = itlocal.off; let load_op: str = tnodeloadop(c, etnode, esz); emitline("\tSUBQ\t$8, SP\n"); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index d0a22421..a002f5d3 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -23942,11 +23942,23 @@ fn cgappend(c: *cgen, n: *node) void = { let vn: *node = sn.next; for (vn != nil) { if (vn.kind == nkind.N_SPREAD) { + // #34 review: a non-ident/non-local spread source used + // to be silently SKIPPED here while cstage fell past + // its spread arm into the single-value stores with the + // N_SPREAD node (garbage store) — divergent. Deferred + // source shape, task #37. let it: *node = vn.lhs; - if (it == nil) { vn = vn.next; continue; }; - if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; }; + if (it == nil || it.kind != nkind.N_IDENT) { + let m34p: str = "#34: append() spread source shape unsupported (rule-7)\n"; + os.write(2, m34p.ptr, m34p.len: u64); + os.exit(1); + }; let itlocal: *local = localfindnode(c, it.str); - if (itlocal == nil) { vn = vn.next; continue; }; + if (itlocal == nil) { + let m34q: str = "#34: append() spread source ident is not a local (rule-7)\n"; + os.write(2, m34q.ptr, m34q.len: u64); + os.exit(1); + }; let it_off: i32 = itlocal.off; let load_op: str = tnodeloadop(c, etnode, esz); emitline("\tSUBQ\t$8, SP\n"); diff --git a/test/wcc/800_append_wide_elem.c b/test/wcc/800_append_wide_elem.c index dcaa58e6..2e432237 100644 --- a/test/wcc/800_append_wide_elem.c +++ b/test/wcc/800_append_wide_elem.c @@ -60,10 +60,18 @@ * | scratch slots where wwstage dedups) | * spread_str | append(ys, xs...) of []str + single | 7 * spread_tagged | append(ys, xs...) of [](i64|bool) | 42 + * tagged_ident_src | append of ALREADY-TAGGED locals | 42 + * | (i64 + bool members) — the widener's | + * | already-tagged source path. | * struct_call_loudstop | append(xs, f()) struct-from-call is | BUILD_FAIL * | the deferred source shape — must | * | fail LOUD on both stages (rule-7), | * | never silently store one word. | + * spread_call_loudstop | append(ys, f()...) non-ident spread | BUILD_FAIL + * | source — pre-review this fell PAST | + * | the spread arm (cstage garbage store | + * | vs wwstage silent skip, divergent); | + * | now a rule-7 loud-stop (task #37). | * * Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id, * which subsumes the frame-size canary (TEXT main,$N) — the @tagbase/ @@ -251,6 +259,28 @@ static const struct row rows[] = { "};\n", 42 }, + { "tagged_ident_src", + "package main;\n" + "type cell = (i64 | bool);\n" + "export fn main() i32 = {\n" + "\tlet xs: []cell = [];\n" + "\tlet c: cell = 40i64;\n" + "\tlet b: cell = true;\n" + "\tappend(xs, c);\n" + "\tappend(xs, b);\n" + "\tlet r: i32 = 0;\n" + "\tmatch (xs[0]) {\n" + "\tcase let v: i64 => r += v: i32;\n" + "\tcase bool => r = 99;\n" + "\t};\n" + "\tmatch (xs[1]) {\n" + "\tcase i64 => r = 98;\n" + "\tcase let w: bool => { if (w) { r += 2; }; };\n" + "\t};\n" + "\treturn r;\n" + "};\n", + 42 }, + { "struct_call_loudstop", "package main;\n" "type pt = struct { x: i32, y: i32, z: i64 };\n" @@ -263,6 +293,21 @@ static const struct row rows[] = { "\treturn 0;\n" "};\n", BUILD_FAIL }, + + { "spread_call_loudstop", + "package main;\n" + "fn mks() []str = {\n" + "\tlet xs: []str = [];\n" + "\tlet a: str = \"abc\";\n" + "\tappend(xs, a);\n" + "\treturn xs;\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet ys: []str = [];\n" + "\tappend(ys, mks()...);\n" + "\treturn ys[0].len: i32;\n" + "};\n", + BUILD_FAIL }, }; static int