w6c_ww: emit a bare str/slice global zero-header once, keyed on type kind (fix #10 part b)

Post-#1, size(str) == size(slice) == 24. emitletdataw's str arm
(~cgen.ww:2074) and slice arm (~cgen.ww:2139) were sequential `if`s
gated on SIZE alone, so a bare 24-byte global matched BOTH and BOTH
fired the no-rhs zero fallback — two `DATAW main.g` rows. cstage
discriminates on type kind (let_isstr/let_isslice, cgen.c:1026/1036)
and emits one; the link+run is correct either way, so the divergence
was byte-id-visible only.

Gate the two arms on the declared type kind via the new letdeclkind
helper (d.lhs.type_, TY_NAMED-peeled — the resolvewalk-stamped
type-expression node), mutually exclusive: a 24B global now hits one
arm. Falls to the str arm when unstamped, where the zero-init bytes are
identical, so byte-id holds for that case too.

cstage already correct — no change. New test 686 (5 runtime rows + 5
byte-id rows) pins single-emit + cs==ww.
This commit is contained in:
2026-06-03 20:55:52 +09:00
parent 63142770de
commit 5bbb81222f
5 changed files with 300 additions and 6 deletions

View File

@@ -32923,6 +32923,24 @@ fn letvarisslice(c: *cgen, name: str) bool = {
return false;
};
// letdeclkind — unwrapped (TY_NAMED-peeled) kind of the let decl's
// declared type, read off the checker-stamped type-expression node
// (d.lhs.type_; resolvewalk stamps N_TNAME/N_TSLICE/… at check.ww:566).
// Mirrors cstage type_unwrap(d->type)->kind, the basis of let_isstr/
// let_isslice (cgen.c:1026/1036). Post-#1 str and slice share a 24B
// header, so emitletdataw's size-only str and slice arms BOTH fired for
// a bare 24B global and double-emitted its DATAW (#10 part b); these two
// arms now branch on kind, not size. nil-safe: returns TY_VOID when
// unstamped so a 24B global still falls to the str arm (zero-init bytes
// are identical either way, so byte-id holds for the unstamped case).
fn letdeclkind(d: *node) tykind = {
if (d.lhs == nil) { return tykind.TY_VOID; };
let ti: *tinfo = d.lhs.type_: *tinfo;
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return tykind.TY_VOID; };
return ti.kind;
};
// letvarisfloat — slot size for a named float global, or 0 if not
// a float-typed let. Walks aliases so the byte-identity contract
// matches C cgen's `let_isfloat` (which resolves Type kinds).
@@ -33935,7 +33953,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitline("\"\n");
};
};
if (sz == primtypesize("str"): i32 && !issg) {
if (sz == primtypesize("str"): i32 && !issg && letdeclkind(d) != tykind.TY_SLICE) {
let r: *node = d.rhs;
for (r != nil) {
if (r.kind != nkind.N_CAST) { break; };
@@ -34000,7 +34018,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
};
};
};
if (sz == tyslicesize(): i32 && !issg) {
if (sz == tyslicesize(): i32 && !issg && letdeclkind(d) == tykind.TY_SLICE) {
// Slice: zero-init only (no slice-literal
// syntax to honour). Any rhs other than
// `nil` is skipped → undefined symbol at

View File

@@ -1059,6 +1059,24 @@ fn letvarisslice(c: *cgen, name: str) bool = {
return false;
};
// letdeclkind — unwrapped (TY_NAMED-peeled) kind of the let decl's
// declared type, read off the checker-stamped type-expression node
// (d.lhs.type_; resolvewalk stamps N_TNAME/N_TSLICE/… at check.ww:566).
// Mirrors cstage type_unwrap(d->type)->kind, the basis of let_isstr/
// let_isslice (cgen.c:1026/1036). Post-#1 str and slice share a 24B
// header, so emitletdataw's size-only str and slice arms BOTH fired for
// a bare 24B global and double-emitted its DATAW (#10 part b); these two
// arms now branch on kind, not size. nil-safe: returns TY_VOID when
// unstamped so a 24B global still falls to the str arm (zero-init bytes
// are identical either way, so byte-id holds for the unstamped case).
fn letdeclkind(d: *node) tykind = {
if (d.lhs == nil) { return tykind.TY_VOID; };
let ti: *tinfo = d.lhs.type_: *tinfo;
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return tykind.TY_VOID; };
return ti.kind;
};
// letvarisfloat — slot size for a named float global, or 0 if not
// a float-typed let. Walks aliases so the byte-identity contract
// matches C cgen's `let_isfloat` (which resolves Type kinds).
@@ -2071,7 +2089,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitline("\"\n");
};
};
if (sz == primtypesize("str"): i32 && !issg) {
if (sz == primtypesize("str"): i32 && !issg && letdeclkind(d) != tykind.TY_SLICE) {
let r: *node = d.rhs;
for (r != nil) {
if (r.kind != nkind.N_CAST) { break; };
@@ -2136,7 +2154,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
};
};
};
if (sz == tyslicesize(): i32 && !issg) {
if (sz == tyslicesize(): i32 && !issg && letdeclkind(d) == tykind.TY_SLICE) {
// Slice: zero-init only (no slice-literal
// syntax to honour). Any rhs other than
// `nil` is skipped → undefined symbol at

View File

@@ -32923,6 +32923,24 @@ fn letvarisslice(c: *cgen, name: str) bool = {
return false;
};
// letdeclkind — unwrapped (TY_NAMED-peeled) kind of the let decl's
// declared type, read off the checker-stamped type-expression node
// (d.lhs.type_; resolvewalk stamps N_TNAME/N_TSLICE/… at check.ww:566).
// Mirrors cstage type_unwrap(d->type)->kind, the basis of let_isstr/
// let_isslice (cgen.c:1026/1036). Post-#1 str and slice share a 24B
// header, so emitletdataw's size-only str and slice arms BOTH fired for
// a bare 24B global and double-emitted its DATAW (#10 part b); these two
// arms now branch on kind, not size. nil-safe: returns TY_VOID when
// unstamped so a 24B global still falls to the str arm (zero-init bytes
// are identical either way, so byte-id holds for the unstamped case).
fn letdeclkind(d: *node) tykind = {
if (d.lhs == nil) { return tykind.TY_VOID; };
let ti: *tinfo = d.lhs.type_: *tinfo;
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return tykind.TY_VOID; };
return ti.kind;
};
// letvarisfloat — slot size for a named float global, or 0 if not
// a float-typed let. Walks aliases so the byte-identity contract
// matches C cgen's `let_isfloat` (which resolves Type kinds).
@@ -33935,7 +33953,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitline("\"\n");
};
};
if (sz == primtypesize("str"): i32 && !issg) {
if (sz == primtypesize("str"): i32 && !issg && letdeclkind(d) != tykind.TY_SLICE) {
let r: *node = d.rhs;
for (r != nil) {
if (r.kind != nkind.N_CAST) { break; };
@@ -34000,7 +34018,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
};
};
};
if (sz == tyslicesize(): i32 && !issg) {
if (sz == tyslicesize(): i32 && !issg && letdeclkind(d) == tykind.TY_SLICE) {
// Slice: zero-init only (no slice-literal
// syntax to honour). Any rhs other than
// `nil` is skipped → undefined symbol at