cstage+selfhost+test: fold N_UN over signed int literal in let DATA emit (#19)

emit_lets / emitletdataw's scalar-8B and array arms only matched bare
N_INTLIT / N_RUNELIT / N_TRUE / N_FALSE / N_NIL on the let rhs. `let
x: i8 = -1i8;` arrives as N_UN(TK_MINUS, N_INTLIT(1)) — none of those
— so cstage's scalar arm hit `else continue;` and dropped the DATAW
row entirely; the array arm bailed at the first non-foldable element
and the skip-array-with-non-NIL-rhs fall-through dropped the whole
row. Wwstage's mirror arms silently emitted zero bytes for negative
literals in both shapes.

Severity split — cstage symptom is no DATA emit, the linker fails
loudly at build time. Wwstage symptom is silent-zero element
substitution for negative array values: compiles, runs, returns
wrong answers. Corpus-coverage-blind on the wwstage side, only
surfaces when a consumer reads the wrong value. Single N_UN-fold
helper application retires both symptoms across both stages. Fourth
corpus-coverage-blind unmask this session (catalog: i64 div/mod CQO
#16, IDENT-local /= no-op, #21 call-arg DX drop, now #19 wwstage
silent-zero).

Route all four sites through fold_int_literal / foldintliteral, the
same helper #24 used on the def-emit side (which already covered
N_UN over the leaf set). The wwstage array arm also picks up an
N_CAST peel and drops a dead non-`...` N_FIELD branch (the parser
never emits non-`...` N_FIELD inside an N_ARRLIT — only as the `...`
repeat marker). Same-path sibling cleanup; rule-11 justified.

Tests:
  - 719_signed_data_emit asserts DATAW <sym>(SB),"<bytes>" lines are
    present in both stages' .s for the {i8, i16, i32, i64} × {scalar,
    1D array} matrix, plus cmp -s byte-id between stages per row.
    Corpus-coverage-blind sentinel per rob's STATUS-3 note.
  - 923_signed_data_emit_run runtime-pins the same matrix plus a
    TK_TILDE row through cstage and wwstage drivers.

995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
This commit is contained in:
2026-05-17 07:25:49 +09:00
parent a8561c03a0
commit 9bd1d0d734
7 changed files with 543 additions and 87 deletions

View File

@@ -1108,14 +1108,12 @@ fn emitletdataw(c: *cgen, file: *node) void = {
if (r.kind != nkind.N_CAST) { break; };
r = r.lhs;
};
ok = false;
if (r != nil) {
if (r.kind == nkind.N_INTLIT) { v = r.uval; ok = true; };
if (r.kind == nkind.N_RUNELIT) { v = r.uval; ok = true; };
if (r.kind == nkind.N_TRUE) { v = 1u64; ok = true; };
if (r.kind == nkind.N_FALSE) { v = 0u64; ok = true; };
if (r.kind == nkind.N_NIL) { v = 0u64; ok = true; };
};
// Same helper as emitdefconstants (#24)
// — widens the gate so N_UN over an
// int leaf folds. `let x: i8 = -1i8;`
// arrives as N_UN(TK_MINUS, N_INTLIT)
// after the typed-AST cast peel.
ok = foldintliteral(r, &v);
};
if (ok) {
emitline("DATAW ");
@@ -1245,11 +1243,14 @@ fn emitletdataw(c: *cgen, file: *node) void = {
// Top-level `[N]T = [a, b, ...]` array global.
// Emits N*esz bytes with each element's bytes
// little-endian for the declared primitive width.
// Without this, `let arr: [N]T = ...` references
// from function bodies link-fail with `undefined
// reference to arr`, and bare-name addressing
// (LEAQ arr(SB)) inside cgindex / cgassign has no
// symbol to bind to.
// Element fold goes through foldintliteral (same
// helper as emitdefconstants / scalar arm above)
// so `-1i8` and friends emit their two's-complement
// bytes after the leading N_CAST peel — pre-#19
// this arm only matched bare N_INTLIT/N_RUNELIT and
// silently emitted zero for unfoldable elements.
// `...` (N_FIELD with str="...") repeats the last
// folded value across the remaining slots.
if (d.lhs != nil) {
if (d.lhs.kind == nkind.N_TARRAY) {
let elemn: *node = d.lhs.lhs;
@@ -1273,27 +1274,25 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitline("(SB),\"");
let i: i32 = 0;
let e: *node = elems;
let fillv: u64 = 0u64;
let last: u64 = 0u64;
let inrepeat: bool = false;
for (i < alen) {
let v: u64 = fillv;
let v: u64 = last;
if (!inrepeat && e != nil) {
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
// `..., ...` repeat marker: prior v stays.
inrepeat = true;
} else {
if (e.lhs != nil) {
if (e.lhs.kind == nkind.N_INTLIT) { v = e.lhs.uval; };
if (e.lhs.kind == nkind.N_RUNELIT) { v = e.lhs.uval; };
};
fillv = v;
e = e.next;
};
} else {
if (e.kind == nkind.N_INTLIT) { v = e.uval; };
if (e.kind == nkind.N_RUNELIT) { v = e.uval; };
fillv = v;
let ev: *node = e;
for (ev != nil) {
if (ev.kind != nkind.N_CAST) { break; };
ev = ev.lhs;
};
if (!foldintliteral(ev, &v)) { v = 0u64; };
last = v;
e = e.next;
};
};