wcc/cgen: #8 def str-array element load — emit + pre-intern def-twin + ww load (both stages)

def C:[N]str; C[i] was loud (undefined main.C) both stages. Three folded
fixes, one commit (splitting would ship a bisect point where wwstage
silently returns an element address instead of .len):

P0: the str-array static-init emitter dropped its vestigial directive
=="DATAW" gate so a def table rides the same DATAW-header + DATAR-reloc
path as let. A def str/slice table lives in DATAW by w6a's A_DATAR-holder
constraint -- placement only; def immutability stays checker-enforced.

P1: let_pre_intern / letpreintern walked N_LET only, so a def str-array's
element string-literals were never interned (dangling _S_n). Extracted a
pre_intern_strarray SSoT helper, called for a def str-array arm too, both
stages. Scoped to str fixed arrays; def []T / def [N][]T stay loud (#270).

P2: wwstage cgenexpr lacked a defvartnode fallback in the indexed-element
classify, so a def str-array element load returned the element address
instead of the slice header -- a silent miscompile. One line, aligning
wwstage up to cstage (which was correct). C[1].len now = 3 both stages,
byte-identical.

byte-id 990-997 8/8; w6c/w6c_ww move. test/wcc/819 table-driven. The
def-global scalar str index sibling (def S:str; S[0]) stays task #14.
This commit is contained in:
2026-06-08 18:52:55 +09:00
parent 267e81b89e
commit 1aaa0a3670
7 changed files with 636 additions and 148 deletions

View File

@@ -23401,6 +23401,17 @@ fn cgindex(c: *cgen, n: *node) void = {
etn = idxelemtn(bl.tnode);
} else {
etn = idxelemtn(letvartnode(c, base.str));
// #8/GAP-B: a def-global str/slice-array base lives
// in c.defs, not c.lets — letvartnode misses it →
// etn nil → elem mis-classified scalar, dropping the
// 3-word slice-header load (returns element ADDR not
// .len; cstage's Type-based classify loads the full
// header, the proven let form). defvartnode is the
// def-side sister — same fallback as the base-address
// resolution at cgenexpr.ww:1762.
if (etn == nil) {
etn = idxelemtn(defvartnode(c, base.str));
};
};
// #60: an alias-NAMED base has no element tnode
// (idxelemtn sees the N_TNAME leaf, nil) — classify
@@ -39618,6 +39629,48 @@ fn emitdatawbyte(b: u8) void = {
emitbytes( bb.ptr, 1u64);
};
// preinternstrarray — SSoT for the #18 [N]str element-strlit intern
// ORDER (element order, then `...` repeat-fill). Shared by letpreintern's
// let arm and the #8/GAP-B def arm so both intern labels in the SAME
// order emitstrarraydata references them by — a divergent order would
// mis-pair the DATAR rows with their _S_ rodata. au is the chased
// TY_ARRAY tinfo, r the N_ARRLIT rhs; caller verified the element is str.
fn preinternstrarray(c: *cgen, au: *tinfo, r: *node) void = {
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;
};
};
};
};
// letpreintern — intern strlits referenced from top-level str-let
// initialisers BEFORE emitdatasection runs. Mirrors cmd/w6c/cgen.c
// let_pre_intern: emitletdataw later looks up the same label, and
@@ -39628,6 +39681,29 @@ export fn letpreintern(c: *cgen, file: *node) void = {
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
// #8/GAP-B: a `def [N]str` needs the SAME element-strlit
// pre-interning as the let [N]str arm (the #18 ordering
// contract) so emitstrarraydata's DATAR rows find their _S_
// rodata. letpreintern walked only N_LET; a def's labels were
// allocated too late (emitdefconstants pass) → dangling _S_.
// Str-array ONLY — def tuple/slice/tagged/scalar-str stay out
// of scope (#10/#270 / inline-Sdef).
if (d.kind == nkind.N_DEF) {
let dr: *node = d.rhs;
for (dr != nil && dr.kind == nkind.N_CAST) {
dr = dr.lhs;
};
if (d.lhs != nil && dr != nil
&& dr.kind == nkind.N_ARRLIT) {
let dau: *tinfo = tichase(d.lhs.type_: *tinfo);
if (dau != nil && dau.kind == tykind.TY_ARRAY) {
let deu: *tinfo = tichase(dau.sub);
if (deu != nil && deu.kind == tykind.TY_STR) {
preinternstrarray(c, dau, dr);
};
};
};
};
if (d.kind == nkind.N_LET) {
let r: *node = d.rhs;
for (r != nil) {
@@ -39650,39 +39726,7 @@ export fn letpreintern(c: *cgen, file: *node) void = {
let eu: *tinfo = tichase(au.sub);
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;
};
};
};
preinternstrarray(c, au, r);
};
};
};
@@ -40342,9 +40386,11 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node,
// 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.
// row's DATAR references it. Always emits into DATAW (writable): A_DATAR
// requires a DATAW holder, so both `let` and a read-only `def [N]str`
// (#8/GAP-B) park their backing here — the section bit is the reloc-
// holder constraint, not a mutability grant (def immutability stays
// checker-enforced). 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;
@@ -40355,7 +40401,17 @@ fn emitstrarraydata(c: *cgen, directive: str, name: str, module: str,
eu = tichase(eu);
if (eu == nil) { return false; };
if (eu.kind != tykind.TY_STR) { return false; };
if (!streq(directive, "DATAW")) { return false; };
// #8/GAP-B: a str-element array's backing ALWAYS lives in DATAW
// (writable section), regardless of the caller's let/def directive —
// each element carries an A_DATAR ptr-reloc to its _S_ rodata row, and
// w6a requires a DATAR holder be a DATAW slot (asm.c:362). The passed
// directive ("DATA" for a def, "DATAW" for a let) is therefore IGNORED
// here; the emit below hardcodes DATAW. A `def [N]str` stays immutable
// — the checker rejects writes to a def; DATAW is only the reloc-holder
// placement, not a mutability grant (rule-8 placement detail). Pre-fix
// this gate skipped the def path → no DATA block → w6l undefined
// 'main.C' (#270 lineage; int-def is plain DATA, no holder constraint,
// so it was unaffected).
let esz: i32 = au.sub.size: i32;
let alen: i32 = au.alen: i32;

View File

@@ -1350,6 +1350,48 @@ fn emitdatawbyte(b: u8) void = {
emitbytes( bb.ptr, 1u64);
};
// preinternstrarray — SSoT for the #18 [N]str element-strlit intern
// ORDER (element order, then `...` repeat-fill). Shared by letpreintern's
// let arm and the #8/GAP-B def arm so both intern labels in the SAME
// order emitstrarraydata references them by — a divergent order would
// mis-pair the DATAR rows with their _S_ rodata. au is the chased
// TY_ARRAY tinfo, r the N_ARRLIT rhs; caller verified the element is str.
fn preinternstrarray(c: *cgen, au: *tinfo, r: *node) void = {
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;
};
};
};
};
// letpreintern — intern strlits referenced from top-level str-let
// initialisers BEFORE emitdatasection runs. Mirrors cmd/w6c/cgen.c
// let_pre_intern: emitletdataw later looks up the same label, and
@@ -1360,6 +1402,29 @@ export fn letpreintern(c: *cgen, file: *node) void = {
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
// #8/GAP-B: a `def [N]str` needs the SAME element-strlit
// pre-interning as the let [N]str arm (the #18 ordering
// contract) so emitstrarraydata's DATAR rows find their _S_
// rodata. letpreintern walked only N_LET; a def's labels were
// allocated too late (emitdefconstants pass) → dangling _S_.
// Str-array ONLY — def tuple/slice/tagged/scalar-str stay out
// of scope (#10/#270 / inline-Sdef).
if (d.kind == nkind.N_DEF) {
let dr: *node = d.rhs;
for (dr != nil && dr.kind == nkind.N_CAST) {
dr = dr.lhs;
};
if (d.lhs != nil && dr != nil
&& dr.kind == nkind.N_ARRLIT) {
let dau: *tinfo = tichase(d.lhs.type_: *tinfo);
if (dau != nil && dau.kind == tykind.TY_ARRAY) {
let deu: *tinfo = tichase(dau.sub);
if (deu != nil && deu.kind == tykind.TY_STR) {
preinternstrarray(c, dau, dr);
};
};
};
};
if (d.kind == nkind.N_LET) {
let r: *node = d.rhs;
for (r != nil) {
@@ -1382,39 +1447,7 @@ export fn letpreintern(c: *cgen, file: *node) void = {
let eu: *tinfo = tichase(au.sub);
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;
};
};
};
preinternstrarray(c, au, r);
};
};
};
@@ -2074,9 +2107,11 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node,
// 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.
// row's DATAR references it. Always emits into DATAW (writable): A_DATAR
// requires a DATAW holder, so both `let` and a read-only `def [N]str`
// (#8/GAP-B) park their backing here — the section bit is the reloc-
// holder constraint, not a mutability grant (def immutability stays
// checker-enforced). 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;
@@ -2087,7 +2122,17 @@ fn emitstrarraydata(c: *cgen, directive: str, name: str, module: str,
eu = tichase(eu);
if (eu == nil) { return false; };
if (eu.kind != tykind.TY_STR) { return false; };
if (!streq(directive, "DATAW")) { return false; };
// #8/GAP-B: a str-element array's backing ALWAYS lives in DATAW
// (writable section), regardless of the caller's let/def directive —
// each element carries an A_DATAR ptr-reloc to its _S_ rodata row, and
// w6a requires a DATAR holder be a DATAW slot (asm.c:362). The passed
// directive ("DATA" for a def, "DATAW" for a let) is therefore IGNORED
// here; the emit below hardcodes DATAW. A `def [N]str` stays immutable
// — the checker rejects writes to a def; DATAW is only the reloc-holder
// placement, not a mutability grant (rule-8 placement detail). Pre-fix
// this gate skipped the def path → no DATA block → w6l undefined
// 'main.C' (#270 lineage; int-def is plain DATA, no holder constraint,
// so it was unaffected).
let esz: i32 = au.sub.size: i32;
let alen: i32 = au.alen: i32;

View File

@@ -1881,6 +1881,17 @@ fn cgindex(c: *cgen, n: *node) void = {
etn = idxelemtn(bl.tnode);
} else {
etn = idxelemtn(letvartnode(c, base.str));
// #8/GAP-B: a def-global str/slice-array base lives
// in c.defs, not c.lets — letvartnode misses it →
// etn nil → elem mis-classified scalar, dropping the
// 3-word slice-header load (returns element ADDR not
// .len; cstage's Type-based classify loads the full
// header, the proven let form). defvartnode is the
// def-side sister — same fallback as the base-address
// resolution at cgenexpr.ww:1762.
if (etn == nil) {
etn = idxelemtn(defvartnode(c, base.str));
};
};
// #60: an alias-NAMED base has no element tnode
// (idxelemtn sees the N_TNAME leaf, nil) — classify

View File

@@ -23401,6 +23401,17 @@ fn cgindex(c: *cgen, n: *node) void = {
etn = idxelemtn(bl.tnode);
} else {
etn = idxelemtn(letvartnode(c, base.str));
// #8/GAP-B: a def-global str/slice-array base lives
// in c.defs, not c.lets — letvartnode misses it →
// etn nil → elem mis-classified scalar, dropping the
// 3-word slice-header load (returns element ADDR not
// .len; cstage's Type-based classify loads the full
// header, the proven let form). defvartnode is the
// def-side sister — same fallback as the base-address
// resolution at cgenexpr.ww:1762.
if (etn == nil) {
etn = idxelemtn(defvartnode(c, base.str));
};
};
// #60: an alias-NAMED base has no element tnode
// (idxelemtn sees the N_TNAME leaf, nil) — classify
@@ -39618,6 +39629,48 @@ fn emitdatawbyte(b: u8) void = {
emitbytes( bb.ptr, 1u64);
};
// preinternstrarray — SSoT for the #18 [N]str element-strlit intern
// ORDER (element order, then `...` repeat-fill). Shared by letpreintern's
// let arm and the #8/GAP-B def arm so both intern labels in the SAME
// order emitstrarraydata references them by — a divergent order would
// mis-pair the DATAR rows with their _S_ rodata. au is the chased
// TY_ARRAY tinfo, r the N_ARRLIT rhs; caller verified the element is str.
fn preinternstrarray(c: *cgen, au: *tinfo, r: *node) void = {
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;
};
};
};
};
// letpreintern — intern strlits referenced from top-level str-let
// initialisers BEFORE emitdatasection runs. Mirrors cmd/w6c/cgen.c
// let_pre_intern: emitletdataw later looks up the same label, and
@@ -39628,6 +39681,29 @@ export fn letpreintern(c: *cgen, file: *node) void = {
if (file == nil) { return; };
let d: *node = file.list;
for (d != nil) {
// #8/GAP-B: a `def [N]str` needs the SAME element-strlit
// pre-interning as the let [N]str arm (the #18 ordering
// contract) so emitstrarraydata's DATAR rows find their _S_
// rodata. letpreintern walked only N_LET; a def's labels were
// allocated too late (emitdefconstants pass) → dangling _S_.
// Str-array ONLY — def tuple/slice/tagged/scalar-str stay out
// of scope (#10/#270 / inline-Sdef).
if (d.kind == nkind.N_DEF) {
let dr: *node = d.rhs;
for (dr != nil && dr.kind == nkind.N_CAST) {
dr = dr.lhs;
};
if (d.lhs != nil && dr != nil
&& dr.kind == nkind.N_ARRLIT) {
let dau: *tinfo = tichase(d.lhs.type_: *tinfo);
if (dau != nil && dau.kind == tykind.TY_ARRAY) {
let deu: *tinfo = tichase(dau.sub);
if (deu != nil && deu.kind == tykind.TY_STR) {
preinternstrarray(c, dau, dr);
};
};
};
};
if (d.kind == nkind.N_LET) {
let r: *node = d.rhs;
for (r != nil) {
@@ -39650,39 +39726,7 @@ export fn letpreintern(c: *cgen, file: *node) void = {
let eu: *tinfo = tichase(au.sub);
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;
};
};
};
preinternstrarray(c, au, r);
};
};
};
@@ -40342,9 +40386,11 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node,
// 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.
// row's DATAR references it. Always emits into DATAW (writable): A_DATAR
// requires a DATAW holder, so both `let` and a read-only `def [N]str`
// (#8/GAP-B) park their backing here — the section bit is the reloc-
// holder constraint, not a mutability grant (def immutability stays
// checker-enforced). 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;
@@ -40355,7 +40401,17 @@ fn emitstrarraydata(c: *cgen, directive: str, name: str, module: str,
eu = tichase(eu);
if (eu == nil) { return false; };
if (eu.kind != tykind.TY_STR) { return false; };
if (!streq(directive, "DATAW")) { return false; };
// #8/GAP-B: a str-element array's backing ALWAYS lives in DATAW
// (writable section), regardless of the caller's let/def directive —
// each element carries an A_DATAR ptr-reloc to its _S_ rodata row, and
// w6a requires a DATAR holder be a DATAW slot (asm.c:362). The passed
// directive ("DATA" for a def, "DATAW" for a let) is therefore IGNORED
// here; the emit below hardcodes DATAW. A `def [N]str` stays immutable
// — the checker rejects writes to a def; DATAW is only the reloc-holder
// placement, not a mutability grant (rule-8 placement detail). Pre-fix
// this gate skipped the def path → no DATA block → w6l undefined
// 'main.C' (#270 lineage; int-def is plain DATA, no holder constraint,
// so it was unaffected).
let esz: i32 = au.sub.size: i32;
let alen: i32 = au.alen: i32;