w6c+cgen: emit module-level [N]str static-init data + relocations (fix #18)

A module-level `let xs: [N]str = ["a","b",...];` static init emitted no
.data: a str element carries a ptr->rodata relocation, not just bytes, so
it fell through the byte-only array-emit path and left the table symbol
undefined (w6l: undefined reference). Shared gap on both stages, not
rule-10.

emit_strarray_data / emitstrarraydata apply the scalar-str-global pattern
per element at offset idx*esz: a DATAW row of {0-ptr placeholder, LE len,
cap} plus a per-element DATAR sym+idx*esz,_S_n reloc. let_pre_intern /
letpreintern pre-intern each element strlit so the _S_ rodata rows precede
the DATAR references. Stride routes through etype->size (rule 13). Scoped
to the DATAW (`let`) directive: A_DATAR requires a DATAW holder, so
`def [N]str` and str-in-aggregate stay a filed follow-up.

919_strarray_static_run pins runtime (len-sum, element .ptr deref, var
index, empty slot, repeat suffix) + cs==ww byte-id. w6c + wwdump
combined.ww regenerated.
This commit is contained in:
2026-06-03 11:35:03 +09:00
parent a67259ca69
commit 58c8f4be02
6 changed files with 960 additions and 40 deletions

View File

@@ -1220,20 +1220,82 @@ export fn letpreintern(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
if (d.kind == nkind.N_LET) {
let sz: i32 = letemitsize(c, d);
// #43: route the str-let gate through primtypesize so
// #1 doesn't desync this with emitletdataw's matching
// `sz == primtypesize("str"): i32` strlit-init branch.
if (sz == primtypesize("str"): i32) {
let r: *node = d.rhs;
for (r != nil) {
if (r.kind != nkind.N_CAST) { break; };
r = r.lhs;
let r: *node = d.rhs;
for (r != nil) {
if (r.kind != nkind.N_CAST) { break; };
r = r.lhs;
};
// #18: `let xs: [N]str = […];` — pre-intern each
// element's strlit in element order (then repeat-fill)
// so emitstrarraydata's DATAR rows find an _S_ rodata
// row. Must match that helper's interning order exactly
// to keep labels stable.
let handled: bool = false;
if (d.lhs != nil && r != nil) {
if (d.lhs.kind == nkind.N_TARRAY
&& r.kind == nkind.N_ARRLIT) {
let au: *tinfo = d.lhs.type_: *tinfo;
for (au != nil && au.kind == tykind.TY_NAMED) {
au = au.under;
};
let eu: *tinfo = nil;
if (au != nil) {
if (au.kind == tykind.TY_ARRAY) {
eu = au.sub;
for (eu != nil && eu.kind == tykind.TY_NAMED) {
eu = eu.under;
};
};
};
if (eu != nil && eu.kind == tykind.TY_STR) {
handled = true;
let alen: i32 = au.alen: i32;
let cnt: i32 = 0;
let last_ev: *node = nil;
let repeat: bool = false;
let e: *node = r.list;
for (e != nil && cnt < 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) { break; };
if (ev.kind != nkind.N_STRLIT) { break; };
if (ev.str.len > 0) {
internstrlit(c, ev.str);
};
last_ev = ev;
cnt += 1;
e = e.next;
};
if (repeat && last_ev != nil) {
if (last_ev.str.len > 0) {
for (cnt < alen) {
internstrlit(c, last_ev.str);
cnt += 1;
};
};
};
};
};
if (r != nil) {
if (r.kind == nkind.N_STRLIT) {
if (r.str.len > 0) {
internstrlit(c, r.str);
};
if (!handled) {
let sz: i32 = letemitsize(c, d);
// #43: route the str-let gate through primtypesize so
// #1 doesn't desync this with emitletdataw's matching
// `sz == primtypesize("str"): i32` strlit-init branch.
if (sz == primtypesize("str"): i32) {
if (r != nil) {
if (r.kind == nkind.N_STRLIT) {
if (r.str.len > 0) {
internstrlit(c, r.str);
};
};
};
};
@@ -1775,6 +1837,126 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node,
return true;
};
// emitstrarraydata — module-level `let xs: [N]str = […];` static init
// (#18). Mirror of cstage emit_strarray_data. A str element carries a
// ptr→rodata relocation, not just bytes, so it can't ride
// emitarraylitbytes (bytes-only); instead apply the scalar-str-global
// pattern (DATAW header with a zero ptr placeholder + inline LE len,
// then a per-element DATAR) at offset idx*esz. Each strlit was pre-
// interned by letpreintern so its _S_ rodata row exists before this
// row's DATAR references it. Scoped to DATAW (writable `let`): A_DATAR
// requires a DATAW holder, so a read-only `def [N]str` can't carry the
// relocs (#18 follow-up). Returns false when the element type isn't str.
fn emitstrarraydata(c: *cgen, directive: str, name: str, module: 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; };
let eu: *tinfo = au.sub;
for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; };
if (eu == nil) { return false; };
if (eu.kind != tykind.TY_STR) { return false; };
if (!streq(directive, "DATAW")) { return false; };
let esz: i32 = au.sub.size: i32;
let alen: i32 = au.alen: i32;
let last_ev: *node = nil;
let repeat: bool = false;
let cnt: i32 = 0;
let e: *node = rhs.list;
for (e != nil && cnt < 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 (ev.kind != nkind.N_STRLIT) { return false; };
last_ev = ev;
cnt += 1;
e = e.next;
};
emitline("DATAW ");
emitsymnamehint(c, name, module);
emitline("(SB),\"");
let idx: i32 = 0;
e = 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; };
let i: i32 = 0;
for (i < 8) { emitdatawbyte(0u8); i += 1; };
let v: u64 = ev.str.len: u64;
i = 0;
for (i < 8) {
emitdatawbyte((v & 255u64): u8);
v = v >> 8u64;
i += 1;
};
i = 16;
for (i < esz) { emitdatawbyte(0u8); i += 1; };
idx += 1;
e = e.next;
};
for (idx < alen) {
let v: u64 = 0u64;
if (repeat && last_ev != nil) { v = last_ev.str.len: u64; };
let i: i32 = 0;
for (i < 8) { emitdatawbyte(0u8); i += 1; };
i = 0;
for (i < 8) {
emitdatawbyte((v & 255u64): u8);
v = v >> 8u64;
i += 1;
};
i = 16;
for (i < esz) { emitdatawbyte(0u8); i += 1; };
idx += 1;
};
emitline("\"\n");
idx = 0;
e = 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.str.len > 0) {
let lab: str = internstrlit(c, ev.str);
emitline("DATAR ");
emitsymnamehint(c, name, module);
emitline("+");
emitint((idx * esz): i64);
emitline("(SB),");
emitbytes( lab.ptr, lab.len: u64);
emitline("(SB)\n");
};
idx += 1;
e = e.next;
};
for (idx < alen) {
if (repeat && last_ev != nil && last_ev.str.len > 0) {
let lab: str = internstrlit(c, last_ev.str);
emitline("DATAR ");
emitsymnamehint(c, name, module);
emitline("+");
emitint((idx * esz): i64);
emitline("(SB),");
emitbytes( lab.ptr, lab.len: u64);
emitline("(SB)\n");
};
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;`
@@ -1800,6 +1982,11 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str,
emitline("\"\n");
return true;
};
// str-element arrays carry per-element ptr relocations — handled
// by the dedicated DATAW+DATAR helper (#18).
if (emitstrarraydata(c, directive, name, module, arrt, rhs)) {
return true;
};
if (!emitarraylitbytes(c, arrt, rhs, 0)) { return false; };
emitline(directive);
emitline(" ");