wcc: array static-init let/def DATA emit via SSoT helper (#129 A.3)

Extract emit_array_data + emit_array_lit_bytes helpers (both stages,
mirrored) for module-level let/def with N_ARRLIT initializer or no-rhs
zero-init. Two-pass validate-then-emit: validate pass walks elements
and fails atomically on any non-foldable element (no partial-byte
emit on failure); emit pass writes element bytes after success.
Element-kind dispatch: integer via fold_int_literal byte-for-byte
preserved from pre-A.3 inline arm (bootstrap NEUTRAL — 6 live consumers
in lib/os/bufio/strings/encoding-utf8/strconv-stof_data), float via
inline bitcast + sign-XOR byte-loop (A.1 shape, no INT64_MIN — sibling
#144), struct via recursion into emit_struct_lit_bytes (A.2 helper).
Out-of-scope element kinds (ptr-elem, nested-array) rule-7 fatal.

emit_struct_lit_bytes gains TY_ARRAY field arm calling emit_array_lit_
bytes recursively — closes A.2 parked shape-15 (array-in-struct
`def D: dt = dt{tag=42, buf=[1u8,2u8,3u8,4u8]};`).

LOAD-side widened symmetric to A.2 precedent: cstage cgindex N_INDEX
direct-ident isglobal gate widened via new DefArray registry
(def_isarraydef populated in let_collect parallel to DefStruct);
wwstage cgindex N_INDEX falls through to defvartnode on letvartnode nil
(reads defent.dtnode field added in A.2). Both stages materialise
array-def via LEAQ name(SB) same as array-let.

Mid-impl rule-7 stop: refactor initially routed only rhs==N_ARRLIT
through emitarraydata, leaving nil-rhs zero-init arrays (e.g.
`let f64tos_buf: [64]u8;` in lib/strconv) silently SKIPPED → undef-ref
at link of wwstage-rebuilt selfhost binaries. Caught on first gate run
via bootstrap 994/995 RED. Fixed by adding nil-rhs branch to
emitarraydata (zero-fills arrt.size bytes) + widening wwstage caller
to route both N_ARRLIT and nil through helper. Same-class-lower-stratum
pattern (recurring across A.1 N_UN-peel, A.2 sz==8-short-circuit, A.3
nil-rhs-drop); banked as feedback memory.

Test 919 (11 rows: int-elem 1B/4B/8B + signed-N_UN-peel + float-elem
f64/f32 + def-int / def-float / struct-with-array-field shape-15 +
explicit-zero + single-elem-regression) registered. Make test:
182/182 incl. 990-997 byte-id + combined_ww_fresh.

Followups filed:
- #43 — wwstage emitletdataw str/slice-size arms lack !isarr guards;
  hypothetical no-rhs [16/24]u8 triple-emits (NOT A.3-introduced;
  no live consumer; 2-line parity fix)
This commit is contained in:
2026-05-27 05:56:24 +09:00
parent 0ed0b3933c
commit 9e3bc4ea37
7 changed files with 1562 additions and 242 deletions

View File

@@ -1144,6 +1144,21 @@ fn defvarstructinfo(c: *cgen, name: str) *structinfo = {
return nil;
};
// defvartnode — sister of letvartnode for top-level `def`s. Returns
// the type-spec node (defent.dtnode) for the named def, or nil. #129
// A.3 uses it in cgindex's array-base resolution so a `def: [N]T`
// resolves through the same N_TARRAY-detect → LEAQ name(SB) shape as
// a let array. Parallel to defvarstructinfo (#129 A.2) at the LOAD
// side widening.
fn defvartnode(c: *cgen, name: str) *node = {
let e: *defent = c.defs;
for (e != nil) {
if (streq(e.dname, name)) { return e.dtnode; };
e = e.dnext;
};
return nil;
};
// emitdatawbyte — write one byte of an asm string literal using
// the same escape rules as emitdefconstants / emitdatasection.
fn emitdatawbyte(b: u8) void = {
@@ -1368,6 +1383,30 @@ fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node,
f = f.tnext;
continue;
};
// #129 A.3: array-typed field with N_ARRLIT rhs (the shape
// parked in A.2). Recurses through emitarraylitbytes for
// element-kind dispatch. Rule-7 stops loudly if rhs shape
// doesn't match.
if (fu != nil && fu.kind == tykind.TY_ARRAY) {
if (vr == nil) {
let m: str = "emitstructlitbytes: array field rhs nil (#129 A.3)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (vr.kind != nkind.N_ARRLIT) {
let m: str = "emitstructlitbytes: array field rhs not N_ARRLIT (#129 A.3)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (!emitarraylitbytes(c, f.type_, vr, 1)) {
let m: str = "emitstructlitbytes: array field rhs has non-reducible elements (#129 A.3)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
if (typeisfloat(f.type_)) {
let isf32: bool = (fsz == 4);
let neg: bool = false;
@@ -1456,6 +1495,260 @@ fn emitstructdata(c: *cgen, directive: str, name: str,
return true;
};
// emitarraylitbytes — emit alen * esz bytes for an [N]T top-level let/
// def with N_ARRLIT rhs. Mirrors cstage emit_array_lit_bytes. Per-
// element dispatch:
// - int (covers bool/rune/typed-int/N_UN-int): foldintliteral per
// element. Existing pre-#129-A.3 emitletdataw array arm logic
// preserved byte-for-byte so bootstrap consumers (lib/os, lib/
// bufio, lib/strings, lib/encoding/utf8, lib/strconv/stof_data)
// don't shift.
// - float (f32/f64): peel N_CAST/N_UN(±), bitcast magnitude via
// pointer-cast round-trip (mirror emitfloatlitdata), sign-XOR
// top byte of each element inline. No 2^63 immediate.
// - struct: per element call emitstructlitbytes (#129 A.2 helper).
// - other element kinds (ptr/nested-array): returns false — caller
// falls through to zero-init.
//
// Two-pass validate-then-emit (`emit_phase=0` validate-only, `=1`
// actually emit) keeps emit-on-failure from emitting partial bytes
// into an open DATA literal.
fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node,
emit_phase: i32) bool = {
let au: *tinfo = arrt;
for (au != nil && au.kind == tykind.TY_NAMED) { au = au.under; };
if (au == nil) { return false; };
if (au.kind != tykind.TY_ARRAY) { return false; };
let esz: i32 = au.sub.size: i32;
let alen: i32 = au.alen: i32;
let eu: *tinfo = au.sub;
for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; };
if (eu != nil && eu.kind == tykind.TY_STRUCT) {
// Validate: every element must be N_STRUCTLIT (after N_CAST).
let idx: i32 = 0;
let last_ev: *node = nil;
let e: *node = rhs.list;
for (e != nil && idx < alen) {
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { break; };
};
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
if (ev == nil) { return false; };
if (ev.kind != nkind.N_STRUCTLIT) { return false; };
last_ev = ev;
idx += 1;
e = e.next;
};
if (emit_phase == 0) { return true; };
idx = 0;
let repeat: bool = false;
e = rhs.list;
for (e != nil && idx < alen) {
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { repeat = true; break; };
};
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
emitstructlitbytes(c, au.sub, ev, 0u64);
idx += 1;
e = e.next;
};
for (idx < alen) {
if (repeat && last_ev != nil) {
emitstructlitbytes(c, au.sub, last_ev, 0u64);
} else {
let bb: i32 = 0;
for (bb < esz) { emitdatawbyte(0u8); bb += 1; };
};
idx += 1;
};
return true;
};
if (typeisfloat(au.sub)) {
let isf32: bool = typeisf32(au.sub);
// Validate.
let idx: i32 = 0;
let e: *node = rhs.list;
for (e != nil && idx < alen) {
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { break; };
};
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
if (ev != nil) { if (ev.kind == nkind.N_UN) {
if (ev.op == tkind.TK_MINUS) {
ev = ev.lhs;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
} else { if (ev.op == tkind.TK_PLUS) {
ev = ev.lhs;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
};};
};};
if (ev == nil) { return false; };
if (ev.kind != nkind.N_FLOATLIT) { return false; };
idx += 1;
e = e.next;
};
if (emit_phase == 0) { return true; };
idx = 0;
let last_bits: u64 = 0u64;
let last_neg: bool = false;
let repeat: bool = false;
e = rhs.list;
for (e != nil && idx < alen) {
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { repeat = true; break; };
};
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
let neg: bool = false;
if (ev != nil) { if (ev.kind == nkind.N_UN) {
if (ev.op == tkind.TK_MINUS) {
neg = true;
ev = ev.lhs;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
} else { if (ev.op == tkind.TK_PLUS) {
ev = ev.lhs;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
};};
};};
let bits: u64 = ev.uval;
if (isf32) {
let dv: f64 = *((&bits): *f64);
let fv: f32 = (dv: f32);
let uv: u32 = *((&fv): *u32);
bits = uv: u64;
};
let bb: i32 = 0;
let nb: u64 = bits;
for (bb < esz) {
let byt: u8 = (nb & 255u64): u8;
if (neg) {
if (bb == esz - 1) { byt = byt ^ 128u8; };
};
emitdatawbyte(byt);
nb = nb >> 8u64;
bb += 1;
};
last_bits = bits;
last_neg = neg;
idx += 1;
e = e.next;
};
for (idx < alen) {
if (repeat) {
let bb: i32 = 0;
let nb: u64 = last_bits;
for (bb < esz) {
let byt: u8 = (nb & 255u64): u8;
if (last_neg) {
if (bb == esz - 1) { byt = byt ^ 128u8; };
};
emitdatawbyte(byt);
nb = nb >> 8u64;
bb += 1;
};
} else {
let bb: i32 = 0;
for (bb < esz) { emitdatawbyte(0u8); bb += 1; };
};
idx += 1;
};
return true;
};
// Int-element path — preserved byte-for-byte from the pre-A.3
// emitletdataw in-place arm so bootstrap consumers (u8/i8/u16
// arrays) don't shift.
let idx: i32 = 0;
let e: *node = rhs.list;
let last: u64 = 0u64;
let repeat: bool = false;
// Validate first.
for (e != nil && idx < alen) {
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) { repeat = true; break; };
};
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
if (ev == nil) { return false; };
if (!foldintliteral(ev, &last)) { return false; };
idx += 1;
e = e.next;
};
if (emit_phase == 0) { return true; };
idx = 0;
last = 0u64;
repeat = false;
e = rhs.list;
let inrepeat: bool = false;
for (idx < alen) {
let v: u64 = last;
if (!inrepeat && e != nil) {
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
inrepeat = true;
} else {
e = e.next;
};
} else {
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
if (!foldintliteral(ev, &v)) { v = 0u64; };
last = v;
e = e.next;
};
};
let nb: u64 = v;
let bb: i32 = 0;
for (bb < esz) {
emitdatawbyte((nb & 255u64): u8);
nb = nb >> 8u64;
bb += 1;
};
idx += 1;
};
return true;
};
// emitarraydata — top-level wrapper. Two-pass validate-then-emit
// avoids partial-byte corruption if the rhs shape can't reduce.
// nil rhs is the "no-rhs zero-init" shape (e.g. `let buf: [N]u8;`
// in lib/strconv/strconv.ww:287, lib/os/os.ww:92, etc.) — emit
// alen*esz zero bytes. This was the implicit pre-A.3 emitletdataw
// behavior (the old loop emitted zeros when `elems` was nil); the
// refactor would have skipped emit entirely without this branch,
// causing `undefined reference to strconv.f64tos_buf` at link.
fn emitarraydata(c: *cgen, directive: str, name: str,
arrt: *tinfo, rhs: *node) bool = {
let au: *tinfo = arrt;
for (au != nil && au.kind == tykind.TY_NAMED) { au = au.under; };
if (au == nil) { return false; };
if (au.kind != tykind.TY_ARRAY) { return false; };
if (rhs == nil) {
let total: u64 = arrt.size;
emitline(directive);
emitline(" ");
emitsymname(c, name);
emitline("(SB),\"");
let i: u64 = 0u64;
for (i < total) { emitdatawbyte(0u8); i = i + 1u64; };
emitline("\"\n");
return true;
};
if (!emitarraylitbytes(c, arrt, rhs, 0)) { return false; };
emitline(directive);
emitline(" ");
emitsymname(c, name);
emitline("(SB),\"");
emitarraylitbytes(c, arrt, rhs, 1);
emitline("\"\n");
return true;
};
fn emitletdataw(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
@@ -1642,72 +1935,31 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitline("\"\n");
};
};
// Top-level `[N]T = [a, b, ...]` array global.
// Emits N*esz bytes with each element's bytes
// little-endian for the declared primitive width.
// 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.
// #129 A.3: array global routes through the
// emitarraydata SSoT helper. Int-elem path is
// byte-for-byte preserved (bootstrap consumers in
// lib/os, lib/bufio, lib/strings, lib/encoding/
// utf8, lib/strconv/stof_data don't shift). Float/
// struct elements gain emit via element-kind
// dispatch. Helper validates pre-emit so partial
// fold-failures don't corrupt the DATA literal.
// No-rhs arrays (e.g. `let buf: [N]u8;`) go through
// the same helper with rhs=nil → zero-fill branch.
if (d.lhs != nil) {
if (d.lhs.kind == nkind.N_TARRAY) {
let elemn: *node = d.lhs.lhs;
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
let rh: *node = d.rhs;
let route: bool = false;
if (rh == nil) { route = true; };
if (rh != nil) {
if (rh.kind == nkind.N_ARRLIT) {
route = true;
};
};
let total: i32 = sz;
let alen: i32 = total / esz;
let elems: *node = nil;
if (d.rhs != nil) {
if (d.rhs.kind == nkind.N_ARRLIT) {
elems = d.rhs.list;
};
if (route) {
let at: *tinfo = d.lhs.type_: *tinfo;
emitarraydata(c, "DATAW", nm,
at, rh);
};
emitline("DATAW ");
emitsymname(c, nm);
emitline("(SB),\"");
let i: i32 = 0;
let e: *node = elems;
let last: u64 = 0u64;
let inrepeat: bool = false;
for (i < alen) {
let v: u64 = last;
if (!inrepeat && e != nil) {
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
inrepeat = true;
} else {
e = e.next;
};
} else {
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;
};
};
let nb: u64 = v;
let b: i32 = 0;
for (b < esz) {
emitdatawbyte((nb & 255u64): u8);
nb = nb >> 8u64;
b += 1;
};
i += 1;
};
emitline("\"\n");
};
};
};
@@ -1770,6 +2022,21 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
};
};
};};
// #129 A.3: array-typed def with N_ARRLIT rhs.
// Parallel to emitletdataw array arm; uses DATA.
if (r != nil) { if (r.kind == nkind.N_ARRLIT) {
let at: *tinfo = d.lhs.type_: *tinfo;
let au: *tinfo = at;
for (au != nil && au.kind == tykind.TY_NAMED) {
au = au.under;
};
if (au != nil) {
if (au.kind == tykind.TY_ARRAY) {
emitarraydata(c, "DATA",
d.str, at, r);
};
};
};};
};
};
if (ok) {