diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 94891990..ce55fc35 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -2910,6 +2910,21 @@ check_file(Checker *c, Node *file) && !arrlit_init_fits(c, d->type, d->rhs)) err(c, d->pos, "let %s init not assignable", d->str); + /* #133: const-fold a const-EXPR rhs (N_BIN / + * unary-over-N_BIN / def-ref) to a literal so + * cgen's literal-only DATA emitter lays the row, + * mirroring the N_DEF arm above. GATED on the + * plain literal fold missing first (existing + * literal/unary-literal lets keep their node, + * byte-identical) AND the const-fold succeeding + * (a str/struct/slice/call/runtime-operand rhs + * returns 0 silently and is left untouched). + * Stamp LAST — the assignability check above + * consumes the pre-stamp cexpr type. */ + u64 dv; + if (d->rhs && !fold_int_literal(d->rhs, &dv) + && eval_def_const(c, d->rhs, &dv, 0)) + stamp_intlit(c, d->rhs, dv); } else if (d->type && d->type->kind == TY_ARRAY && d->type->alen == 0) { /* `let x: [_]T;` — no initialiser, length can't be diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index b2a6ee1d..a80bf960 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -15443,6 +15443,24 @@ export fn checkfile(c: *checker, file: *node) void = { // were missed). Needed for the array accept-if-fits // range-check to fire on module-level `let A:[N]u8=[..]`. checkletassign(c, d); + // #133: const-fold a const-EXPR rhs (N_BIN / + // unary-over-N_BIN / def-ref) to a literal so cgen's + // literal-only emitletdataw lays the DATA row + + // defaultinferredlets recognises it, mirroring the + // N_DEF arm above. GATED on the plain literal fold + // missing first (existing literal/unary-literal lets + // keep their node, byte-identical) AND the const-fold + // succeeding (a str/struct/slice/call/runtime-operand + // rhs returns false silently and is left untouched). + // Stamp LAST — checkletassign consumes the pre-stamp type. + if (d.rhs != nil) { + let dv: u64 = 0u64; + if (!foldintliteral(d.rhs, &dv)) { + if (evaldefconst(c, d.rhs, &dv, 0)) { + stampintlit(d.rhs, dv); + }; + }; + }; }; d = d.next; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 3a02c2ab..82a9a007 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -5162,6 +5162,24 @@ export fn checkfile(c: *checker, file: *node) void = { // were missed). Needed for the array accept-if-fits // range-check to fire on module-level `let A:[N]u8=[..]`. checkletassign(c, d); + // #133: const-fold a const-EXPR rhs (N_BIN / + // unary-over-N_BIN / def-ref) to a literal so cgen's + // literal-only emitletdataw lays the DATA row + + // defaultinferredlets recognises it, mirroring the + // N_DEF arm above. GATED on the plain literal fold + // missing first (existing literal/unary-literal lets + // keep their node, byte-identical) AND the const-fold + // succeeding (a str/struct/slice/call/runtime-operand + // rhs returns false silently and is left untouched). + // Stamp LAST — checkletassign consumes the pre-stamp type. + if (d.rhs != nil) { + let dv: u64 = 0u64; + if (!foldintliteral(d.rhs, &dv)) { + if (evaldefconst(c, d.rhs, &dv, 0)) { + stampintlit(d.rhs, dv); + }; + }; + }; }; d = d.next; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index d6874473..82bbd86e 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -15443,6 +15443,24 @@ export fn checkfile(c: *checker, file: *node) void = { // were missed). Needed for the array accept-if-fits // range-check to fire on module-level `let A:[N]u8=[..]`. checkletassign(c, d); + // #133: const-fold a const-EXPR rhs (N_BIN / + // unary-over-N_BIN / def-ref) to a literal so cgen's + // literal-only emitletdataw lays the DATA row + + // defaultinferredlets recognises it, mirroring the + // N_DEF arm above. GATED on the plain literal fold + // missing first (existing literal/unary-literal lets + // keep their node, byte-identical) AND the const-fold + // succeeding (a str/struct/slice/call/runtime-operand + // rhs returns false silently and is left untouched). + // Stamp LAST — checkletassign consumes the pre-stamp type. + if (d.rhs != nil) { + let dv: u64 = 0u64; + if (!foldintliteral(d.rhs, &dv)) { + if (evaldefconst(c, d.rhs, &dv, 0)) { + stampintlit(d.rhs, dv); + }; + }; + }; }; d = d.next; }; diff --git a/test/wcc/947_inferred_scalar_global_run.c b/test/wcc/947_inferred_scalar_global_run.c index 3615e79c..92a9143e 100644 --- a/test/wcc/947_inferred_scalar_global_run.c +++ b/test/wcc/947_inferred_scalar_global_run.c @@ -39,7 +39,7 @@ runwait(const char *cmd) return -1; } -struct row { const char *label; const char *src; int want_exit; }; +struct row { const char *label; const char *src; int want_exit; int want_err; }; static const struct row rows[] = { /* R1 — the #66(b-i) repro: inferred int-literal module-global, read @@ -50,7 +50,7 @@ static const struct row rows[] = { "let s = 42;\n" "export fn main() i32 = {\n" " return s: i32;\n" - "};\n", 42 }, + "};\n", 42, 0 }, /* R2 — typed-annotation control: `let s: i64 = 42;` already worked * (cs==ww, DATA emitted). Regression guard for the annotated path. */ { "typed_ctrl", @@ -58,7 +58,7 @@ static const struct row rows[] = { "let s: i64 = 42;\n" "export fn main() i32 = {\n" " return s: i32;\n" - "};\n", 42 }, + "};\n", 42, 0 }, /* R3 — #134 neg leg: inferred unary-over-int-literal module-global * `let s = -42;` (rhs N_UN(TK_MINUS) over N_INTLIT). Pre-fix wwstage * emitted neither DATAW nor MOVQ (un-defaulted untyped_int annotation) @@ -70,7 +70,7 @@ static const struct row rows[] = { "let s = -42;\n" "export fn main() i32 = {\n" " return s: i32;\n" - "};\n", 214 }, + "};\n", 214, 0 }, /* R4 — #134 neg typed control: `let s: int = -42;` (TYPED). Already * cs==ww today (concrete annotation → letscalarprim fires). Locks the * construction proof: R3's emitted .s is byte-identical to this typed @@ -80,8 +80,58 @@ static const struct row rows[] = { "let s: int = -42;\n" "export fn main() i32 = {\n" " return s: i32;\n" - "};\n", 214 }, - { NULL, NULL, 0 } + "};\n", 214, 0 }, + /* #133 — const-EXPR scalar module-global. The let pass-2 arm now + * const-folds an N_BIN / unary-over-N_BIN / def-ref rhs and stamps it + * to an N_INTLIT (mirroring the N_DEF arm), so cgen's literal-only + * DATA emitter lays the row in BOTH stages. Pre-fix: cstage LINK-FAILed + * (`undefined main.s` — read emitted, no DATA word) and wwstage was + * SILENT-WRONG (no DATA, no load, MOVSXD on stale AX → exit 152). */ + + /* C1 — inferred const-expr `let s = 7*6;` → 42 (N_BIN rhs). */ + { "const_inferred", + "package main;\n" + "let s = 7 * 6;\n" + "export fn main() i32 = {\n" + " return s: i32;\n" + "};\n", 42, 0 }, + /* C2 — typed const-expr (b-ii) `let s: i64 = 7*6;` → 42. Pre-fix this + * LINK-FAILed BOTH stages (annotation-independent no-DATA gap); the + * same stamp supplies the foldable value. */ + { "const_typed", + "package main;\n" + "let s: i64 = 7 * 6;\n" + "export fn main() i32 = {\n" + " return s: i32;\n" + "};\n", 42, 0 }, + /* C3 — def-ref through let `def K: int = 6; let s = K*7;` → 42. Proves + * eval_def_const's N_IDENT def-resolution reaches through the new let + * hook. */ + { "const_defref", + "package main;\n" + "def K: int = 6;\n" + "let s = K * 7;\n" + "export fn main() i32 = {\n" + " return s: i32;\n" + "};\n", 42, 0 }, + /* C4 — unary-over-binop `let s = -(2*3);` → -6 (low byte 250). Proves + * the N_UN ∘ N_BIN composition folds through eval_def_const. */ + { "const_unary_binop", + "package main;\n" + "let s = -(2 * 3);\n" + "export fn main() i32 = {\n" + " return s: i32;\n" + "};\n", 250, 0 }, + /* C5 — loud guard: a div-by-zero const-expr module-global init stays a + * compile ERROR in BOTH stages, NOT silently zeroed. Asserts the + * eval_def_const error path is preserved through the let hook. */ + { "const_divzero", + "package main;\n" + "let s = 7 / 0;\n" + "export fn main() i32 = {\n" + " return s: i32;\n" + "};\n", 0, 1 }, + { NULL, NULL, 0, 0 } }; static int @@ -132,6 +182,36 @@ main(void) fputs(rows[i].src, f); fclose(f); + /* Loud-guard rows (#133 R5): both stages must REJECT the + * source at compile (e.g. a div-by-zero const-expr init). No + * value to run / no byte-id — only the error-path preservation + * matters. Assert raw w6c AND w6c_ww both emit non-zero. */ + if (rows[i].want_err) { + char cs_s[64], ws_s[64], cmd[2048]; + snprintf(cs_s, sizeof cs_s, "/tmp/wwisg_%d_%d_cs.s", + getpid(), i); + snprintf(ws_s, sizeof ws_s, "/tmp/wwisg_%d_%d_ww.s", + getpid(), i); + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", + w6c, cs_s, src); + if (runwait(cmd) == 0) { + fprintf(stderr, "row[%s]: w6c accepted a source " + "that must be rejected (loud guard)\n", + rows[i].label); + fail++; + } + 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 accepted a " + "source that must be rejected (loud " + "guard)\n", rows[i].label); + fail++; + } + unlink(src); unlink(cs_s); unlink(ws_s); + continue; + } + /* (a) cstage build + run. */ char tmpdir[64]; snprintf(tmpdir, sizeof tmpdir, "/tmp/wwisg_%d_d_%d",