wcc/cgen: #134 wwstage inferred unary-int scalar global — peel +/-/~ over int-literal, default to int (align up)

defaultinferredlets gained a single unary peel: an inferred module-global let s = -42 (untyped_int annotation over N_UN(+/-/~) of N_INTLIT) was wwstage SILENT — no DATAW, no MOVQ, MOVSXD on stale AX (exit 168 vs cstage 214). Peel one unary level to the int literal and default the annotation to the 8B machine word int, so the inferred decl is structurally the typed control and the existing typed-path emit/read fires (DATAW + MOVQ, byte-identical to cstage). ww-only align-up; cstage already correct on neg.

Float leg (N_FLOATLIT) carved to #135: cstage integer-types inferred float globals (MOVQ not MOVSD), so a ww-only f64 default would be cs != ww (rule-10) — needs the both-stage cstage-use-site fix. Nested unary - -42 is #136 (single-level peel). #133 (const-expr 7*6) and unary-over-nonident stay loud.

Pin: 947 neg rows (inferred + typed control), cs == ww .s byte-id.
This commit is contained in:
2026-06-07 22:24:03 +09:00
parent 74e60e89f0
commit 2138e28f65
4 changed files with 139 additions and 63 deletions

View File

@@ -39115,32 +39115,50 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
return 0;
};
// defaultinferredlets — #66(b-i): an inferred module-global int-literal
// `let s = 42;` is stamped by the checker with an N_TNAME("untyped_int")
// annotation (d.lhs). letemitsize / emitletdataw / the cgident global-read arm
// key on that annotation's name, which letscalarprim doesn't recognise → the
// global is dropped from collectlets (no DATAW) and the read falls to the
// silent module-leaf (no MOVQ, MOVSXD on stale AX → wrong). cstage instead
// type_default's the untyped int to the 8B machine word BEFORE emit. Mirror
// that here at the single global-decl pass: rewrite the annotation to the
// concrete machine word `int` so all three consumers resolve it as an 8B int
// global (DATAW + MOVQ, byte-identical to cstage). int (not i32) per
// [[project_int_machine_word_derived_limits]] — i32 is the #108 truncation
// trap, opposite polarity.
// Guarded to N_INTLIT ONLY: a const-EXPR rhs (`let s = 7*6`, N_BIN) is also
// stamped untyped_int but is #66(b-ii) — a SEPARATE loud both-stage gap (no
// DATA → link-fail) — and must stay on its loud route, not be defaulted to a
// silent value. Runs before collectlets in cgfile so the mutated d.lhs is
// visible to letpreintern + emitletdataw too.
// defaultinferredlets — #66(b-i)/#134-neg: an inferred module-global whose rhs
// is an int literal (`let s = 42;`) or a single unary +/-/~ over one
// (`let s = -42;`/`~42;`) is stamped by the checker with an
// N_TNAME("untyped_int") annotation (d.lhs). letemitsize / emitletdataw / the
// cgident global-read arm key on that annotation's name, which letscalarprim
// doesn't recognise → the global is dropped from collectlets (no DATAW) and the
// read falls to the silent module-leaf (no MOVQ, MOVSXD on stale AX → wrong).
// cstage instead type_default's the untyped int to the 8B machine word BEFORE
// emit (and folds the unary). Mirror that here at the single global-decl pass:
// peel one unary +/-/~ over an N_INTLIT (operand `.lhs`, operator `.op`, as
// foldintliteral) and rewrite the annotation to the concrete machine word `int`.
// The inferred decl is then structurally the typed control (`let s: int = -42`),
// so all three consumers fire on the existing typed-path code — byte-identical
// to cstage. int (not i32) per [[project_int_machine_word_derived_limits]] —
// i32 is the #108 truncation trap, opposite polarity.
// Scope — INT literal operand ONLY: one unary level (covers -42/+42/~42); a
// nested unary (`- -42`) is a #134-residual (cstage folds it via
// foldintliteral's recursion, ww peels one level and leaves it silent) — not
// widened here. A const-EXPR rhs (`let s = 7*6`, N_BIN) is #133 — a
// SEPARATE loud both-stage gap (no DATA → link-fail) — and a unary over a
// NON-literal (`let s = -x`) is not constant; both stay on their current route.
// An inferred FLOAT global (`let s = 3.0;`) is the #134-float leg carved to
// #135: cstage integer-types the inferred float at the USE site (MOVQ, not
// MOVSD), so defaulting it ww-only here would emit MOVSD vs cstage's MOVQ = a
// cs≠ww divergence (rule-10) — it ships only WITH the cstage float-use-site fix.
// Runs before collectlets in cgfile so the mutated d.lhs is visible to
// letpreintern + emitletdataw too.
fn defaultinferredlets(c: *cgen, file: *node) void = {
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
if (d.kind == nkind.N_LET) {
if (d.lhs != nil && d.rhs != nil) {
if (d.lhs.kind == nkind.N_TNAME
&& streq(d.lhs.str, "untyped_int")
&& d.rhs.kind == nkind.N_INTLIT) {
if (d.lhs != nil && d.rhs != nil
&& d.lhs.kind == nkind.N_TNAME
&& streq(d.lhs.str, "untyped_int")) {
let opnd: *node = d.rhs;
if (opnd.kind == nkind.N_UN
&& (opnd.op == tkind.TK_PLUS
|| opnd.op == tkind.TK_MINUS
|| opnd.op == tkind.TK_TILDE)) {
opnd = opnd.lhs;
};
if (opnd != nil
&& opnd.kind == nkind.N_INTLIT) {
d.lhs.str = "int";
};
};

View File

@@ -1040,32 +1040,50 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
return 0;
};
// defaultinferredlets — #66(b-i): an inferred module-global int-literal
// `let s = 42;` is stamped by the checker with an N_TNAME("untyped_int")
// annotation (d.lhs). letemitsize / emitletdataw / the cgident global-read arm
// key on that annotation's name, which letscalarprim doesn't recognise → the
// global is dropped from collectlets (no DATAW) and the read falls to the
// silent module-leaf (no MOVQ, MOVSXD on stale AX → wrong). cstage instead
// type_default's the untyped int to the 8B machine word BEFORE emit. Mirror
// that here at the single global-decl pass: rewrite the annotation to the
// concrete machine word `int` so all three consumers resolve it as an 8B int
// global (DATAW + MOVQ, byte-identical to cstage). int (not i32) per
// [[project_int_machine_word_derived_limits]] — i32 is the #108 truncation
// trap, opposite polarity.
// Guarded to N_INTLIT ONLY: a const-EXPR rhs (`let s = 7*6`, N_BIN) is also
// stamped untyped_int but is #66(b-ii) — a SEPARATE loud both-stage gap (no
// DATA → link-fail) — and must stay on its loud route, not be defaulted to a
// silent value. Runs before collectlets in cgfile so the mutated d.lhs is
// visible to letpreintern + emitletdataw too.
// defaultinferredlets — #66(b-i)/#134-neg: an inferred module-global whose rhs
// is an int literal (`let s = 42;`) or a single unary +/-/~ over one
// (`let s = -42;`/`~42;`) is stamped by the checker with an
// N_TNAME("untyped_int") annotation (d.lhs). letemitsize / emitletdataw / the
// cgident global-read arm key on that annotation's name, which letscalarprim
// doesn't recognise → the global is dropped from collectlets (no DATAW) and the
// read falls to the silent module-leaf (no MOVQ, MOVSXD on stale AX → wrong).
// cstage instead type_default's the untyped int to the 8B machine word BEFORE
// emit (and folds the unary). Mirror that here at the single global-decl pass:
// peel one unary +/-/~ over an N_INTLIT (operand `.lhs`, operator `.op`, as
// foldintliteral) and rewrite the annotation to the concrete machine word `int`.
// The inferred decl is then structurally the typed control (`let s: int = -42`),
// so all three consumers fire on the existing typed-path code — byte-identical
// to cstage. int (not i32) per [[project_int_machine_word_derived_limits]] —
// i32 is the #108 truncation trap, opposite polarity.
// Scope — INT literal operand ONLY: one unary level (covers -42/+42/~42); a
// nested unary (`- -42`) is a #134-residual (cstage folds it via
// foldintliteral's recursion, ww peels one level and leaves it silent) — not
// widened here. A const-EXPR rhs (`let s = 7*6`, N_BIN) is #133 — a
// SEPARATE loud both-stage gap (no DATA → link-fail) — and a unary over a
// NON-literal (`let s = -x`) is not constant; both stay on their current route.
// An inferred FLOAT global (`let s = 3.0;`) is the #134-float leg carved to
// #135: cstage integer-types the inferred float at the USE site (MOVQ, not
// MOVSD), so defaulting it ww-only here would emit MOVSD vs cstage's MOVQ = a
// cs≠ww divergence (rule-10) — it ships only WITH the cstage float-use-site fix.
// Runs before collectlets in cgfile so the mutated d.lhs is visible to
// letpreintern + emitletdataw too.
fn defaultinferredlets(c: *cgen, file: *node) void = {
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
if (d.kind == nkind.N_LET) {
if (d.lhs != nil && d.rhs != nil) {
if (d.lhs.kind == nkind.N_TNAME
&& streq(d.lhs.str, "untyped_int")
&& d.rhs.kind == nkind.N_INTLIT) {
if (d.lhs != nil && d.rhs != nil
&& d.lhs.kind == nkind.N_TNAME
&& streq(d.lhs.str, "untyped_int")) {
let opnd: *node = d.rhs;
if (opnd.kind == nkind.N_UN
&& (opnd.op == tkind.TK_PLUS
|| opnd.op == tkind.TK_MINUS
|| opnd.op == tkind.TK_TILDE)) {
opnd = opnd.lhs;
};
if (opnd != nil
&& opnd.kind == nkind.N_INTLIT) {
d.lhs.str = "int";
};
};

View File

@@ -39115,32 +39115,50 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
return 0;
};
// defaultinferredlets — #66(b-i): an inferred module-global int-literal
// `let s = 42;` is stamped by the checker with an N_TNAME("untyped_int")
// annotation (d.lhs). letemitsize / emitletdataw / the cgident global-read arm
// key on that annotation's name, which letscalarprim doesn't recognise → the
// global is dropped from collectlets (no DATAW) and the read falls to the
// silent module-leaf (no MOVQ, MOVSXD on stale AX → wrong). cstage instead
// type_default's the untyped int to the 8B machine word BEFORE emit. Mirror
// that here at the single global-decl pass: rewrite the annotation to the
// concrete machine word `int` so all three consumers resolve it as an 8B int
// global (DATAW + MOVQ, byte-identical to cstage). int (not i32) per
// [[project_int_machine_word_derived_limits]] — i32 is the #108 truncation
// trap, opposite polarity.
// Guarded to N_INTLIT ONLY: a const-EXPR rhs (`let s = 7*6`, N_BIN) is also
// stamped untyped_int but is #66(b-ii) — a SEPARATE loud both-stage gap (no
// DATA → link-fail) — and must stay on its loud route, not be defaulted to a
// silent value. Runs before collectlets in cgfile so the mutated d.lhs is
// visible to letpreintern + emitletdataw too.
// defaultinferredlets — #66(b-i)/#134-neg: an inferred module-global whose rhs
// is an int literal (`let s = 42;`) or a single unary +/-/~ over one
// (`let s = -42;`/`~42;`) is stamped by the checker with an
// N_TNAME("untyped_int") annotation (d.lhs). letemitsize / emitletdataw / the
// cgident global-read arm key on that annotation's name, which letscalarprim
// doesn't recognise → the global is dropped from collectlets (no DATAW) and the
// read falls to the silent module-leaf (no MOVQ, MOVSXD on stale AX → wrong).
// cstage instead type_default's the untyped int to the 8B machine word BEFORE
// emit (and folds the unary). Mirror that here at the single global-decl pass:
// peel one unary +/-/~ over an N_INTLIT (operand `.lhs`, operator `.op`, as
// foldintliteral) and rewrite the annotation to the concrete machine word `int`.
// The inferred decl is then structurally the typed control (`let s: int = -42`),
// so all three consumers fire on the existing typed-path code — byte-identical
// to cstage. int (not i32) per [[project_int_machine_word_derived_limits]] —
// i32 is the #108 truncation trap, opposite polarity.
// Scope — INT literal operand ONLY: one unary level (covers -42/+42/~42); a
// nested unary (`- -42`) is a #134-residual (cstage folds it via
// foldintliteral's recursion, ww peels one level and leaves it silent) — not
// widened here. A const-EXPR rhs (`let s = 7*6`, N_BIN) is #133 — a
// SEPARATE loud both-stage gap (no DATA → link-fail) — and a unary over a
// NON-literal (`let s = -x`) is not constant; both stay on their current route.
// An inferred FLOAT global (`let s = 3.0;`) is the #134-float leg carved to
// #135: cstage integer-types the inferred float at the USE site (MOVQ, not
// MOVSD), so defaulting it ww-only here would emit MOVSD vs cstage's MOVQ = a
// cs≠ww divergence (rule-10) — it ships only WITH the cstage float-use-site fix.
// Runs before collectlets in cgfile so the mutated d.lhs is visible to
// letpreintern + emitletdataw too.
fn defaultinferredlets(c: *cgen, file: *node) void = {
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
if (d.kind == nkind.N_LET) {
if (d.lhs != nil && d.rhs != nil) {
if (d.lhs.kind == nkind.N_TNAME
&& streq(d.lhs.str, "untyped_int")
&& d.rhs.kind == nkind.N_INTLIT) {
if (d.lhs != nil && d.rhs != nil
&& d.lhs.kind == nkind.N_TNAME
&& streq(d.lhs.str, "untyped_int")) {
let opnd: *node = d.rhs;
if (opnd.kind == nkind.N_UN
&& (opnd.op == tkind.TK_PLUS
|| opnd.op == tkind.TK_MINUS
|| opnd.op == tkind.TK_TILDE)) {
opnd = opnd.lhs;
};
if (opnd != nil
&& opnd.kind == nkind.N_INTLIT) {
d.lhs.str = "int";
};
};

View File

@@ -59,6 +59,28 @@ static const struct row rows[] = {
"export fn main() i32 = {\n"
" return s: i32;\n"
"};\n", 42 },
/* 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)
* → exit 168 (garbage). cstage folds the unary, emits DATAW + load
* (exit 214 = -42 low-8). Post-fix defaultinferredlets peels the unary
* and defaults to `int`, so cs==ww and both run -42 (exit 214). */
{ "neg_inferred",
"package main;\n"
"let s = -42;\n"
"export fn main() i32 = {\n"
" return s: i32;\n"
"};\n", 214 },
/* 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
* control (the inferred decl IS the typed decl after defaulting). */
{ "neg_typed_ctrl",
"package main;\n"
"let s: int = -42;\n"
"export fn main() i32 = {\n"
" return s: i32;\n"
"};\n", 214 },
{ NULL, NULL, 0 }
};