w6c+w6c_ww: emit module-level slice-literal static-init (header+backing+reloc) (fix #10 part a)
`let g: []T = [v0, v1, …];` at module scope had no cgen arm: emit_lets /
emitletdataw handled str-lit and array-lit but not slice-lit, so NO
`DATAW main.g` was emitted and BOTH stages failed to link ("undefined
reference to main.g"). byte-id-blind — only the link step exposed it.
emit_slice_data / emitslicedata (parallel to the #18 str-array reloc
helper, generalized to a 24B header + array-backed data):
1. writable backing DATAW "<mangled g>.d" holding the k element bytes,
routed through the emit_array_lit_bytes / emitarraylitbytes choke-
point via a synthesized [k]T (int/float element kinds reduce exactly
as a [N]T global's do);
2. 24B header { ptr-placeholder, LE len, LE cap } (len = cap = k), word
sizes from the type table (ty_uintptr/ty_size, primtypesize) per
rule-13;
3. DATAR g+0 -> backing patches the ptr word.
The backing label's second '.' can't collide with a user global (source
identifiers carry no '.').
New emit_lets / slice arm gated on N_ARRLIT + slice-typed; rides on #18,
which keeps the module-level initializer as N_ARRLIT in both stages.
Aliased-slice spelling (`type S = []T; let g: S = [...]`): cstage
let_isslice already resolves the alias via type_unwrap, but wwstage
letvarisslice keyed only on the syntactic N_TSLICE node — unlike its
siblings letvarisstr/letvarisstruct/letvarisfloat, which all walk the
N_TNAME alias chain. So an aliased-slice global misrouted to the str arm
and never reached emitslicedata, link-failing on wwstage while cstage
emitted correctly (a cs≠ww divergence this fix would otherwise introduce).
letvarisslice now walks the alias chain exactly as letvarisstr does
(align wwstage UP to runtime-correct cstage, the #211 pattern); an alias
of a slice IS a slice. emitslicedata gains the nil/non-slice guard cstage
emit_slice_data already had (rule-10 symmetry; unreachable behind the
gate, guards the su.sub deref).
rule-7 loud-stops, symmetric both stages: read-only `def` slice-literal
(DATAR holder must be DATAW, w6a asm.c:362), `...` repeat (a slice
literal has no target length), and slice-of-{str,slice,tagged} elements
(per-element relocs / #17) — never silent no-emit.
Deferred (filed): struct-element module-level slice-literal surfaces a
separate checker cs!=ww ("let: not assignable" on wwstage, wrong runtime
on cstage) — out of #10's data-emission scope.
Test 687 (table-driven): []u8/[]i64/[]i32 element read-back + len + cap +
1-element edge + aliased-slice-type, dual-stage runtime + asm byte-id,
plus 3 build-fail rows for the loud-stops. selfhost combined.ww
regenerated.
This commit is contained in:
@@ -1044,14 +1044,25 @@ fn letvarisstr(c: *cgen, name: str) bool = {
|
||||
// letvarisslice — is the named top-level let a slice global?
|
||||
// Slice headers are 24 bytes; the ABI flows as (AX, BX, CX) so the
|
||||
// load sequence ends with `MOVQ 16(CX), CX` (overwrites the
|
||||
// address holder with the cap). Mirrors C cgen's `let_isslice`.
|
||||
// address holder with the cap). Mirrors C cgen's `let_isslice`,
|
||||
// which resolves the declared type via type_unwrap — so an alias of
|
||||
// a slice IS a slice. Walks the N_TNAME alias chain exactly as the
|
||||
// sibling letvarisstr does (the structural N_TSLICE node is the
|
||||
// terminator, in place of letvarisstr's "str" name): without this,
|
||||
// a `type S = []T; let g: S = [...]` global misroutes to the str arm
|
||||
// and never reaches emitslicedata, diverging from cstage (#10).
|
||||
fn letvarisslice(c: *cgen, name: str) bool = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, name)) {
|
||||
let t: *node = lv.tnode;
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind == nkind.N_TSLICE) { return true; };
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_TSLICE) { return true; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
let nx: *node = aliaslookup(c, t.str);
|
||||
if (nx == nil) { return false; };
|
||||
t = nx;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
lv = lv.lvnext;
|
||||
@@ -1059,24 +1070,6 @@ 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).
|
||||
@@ -2015,6 +2008,102 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str,
|
||||
return true;
|
||||
};
|
||||
|
||||
// emitslicedata — module-level `let g: []T = [v0,…];` static init (#10
|
||||
// part a). Mirror of cstage emit_slice_data. A slice literal needs a
|
||||
// writable backing holding the k elements, a 24B header { ptr, len, cap
|
||||
// }, and a DATAR patching the ptr word with the backing's VA. The
|
||||
// backing rides the emitarraylitbytes choke-point via a synthesized
|
||||
// [k]T so int/float/struct/nested-array elements reduce exactly as a
|
||||
// [N]T global's do. Backing symbol = "<mangled g>.d": a second '.' can
|
||||
// never collide with a user global (source identifiers carry no '.').
|
||||
// Scoped to a writable `let` — A_DATAR's holder must be a DATAW slot
|
||||
// (w6a asm.c:362); read-only `def`, `...` repeat (no target length),
|
||||
// and slice-of-{str,slice,tagged} elements (per-element relocs / #17)
|
||||
// all loud-stop (rule 7, #10 follow-ups).
|
||||
fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
|
||||
rhs: *node) void = {
|
||||
let su: *tinfo = slt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
// Defensive, mirrors cstage emit_slice_data's
|
||||
// `if (u == NULL || u->kind != TY_SLICE) return 0` (rule-10): the
|
||||
// letvarisslice gate already guarantees a slice, so this is
|
||||
// unreachable — it guards the su.sub deref below if the contract
|
||||
// is ever violated rather than nil-derefing.
|
||||
if (su == nil || su.kind != tykind.TY_SLICE) { return; };
|
||||
let etype: *tinfo = su.sub;
|
||||
let eu: *tinfo = etype;
|
||||
for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; };
|
||||
if (eu != nil) {
|
||||
if (eu.kind == tykind.TY_STR || eu.kind == tykind.TY_SLICE
|
||||
|| eu.kind == tykind.TY_TAGGED) {
|
||||
let m: str = "emitslicedata: slice-of-{str,slice,tagged} literal static-init unsupported (#10 follow-up, rule 7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
// Count elements; reject `...` (a slice literal has no target N).
|
||||
let k: i32 = 0;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
if (e.kind == nkind.N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
let m: str = "emitslicedata: '...' repeat has no target length in a slice literal (#10, rule 7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
k += 1;
|
||||
e = e.next;
|
||||
};
|
||||
let esz: i32 = etype.size: i32;
|
||||
// Synthesize [k]T to ride the emitarraylitbytes choke-point.
|
||||
let arrt: *tinfo = newtype(tykind.TY_ARRAY);
|
||||
arrt.sub = etype;
|
||||
arrt.alen = k: u64;
|
||||
arrt.size = (k * esz): u64;
|
||||
if (!emitarraylitbytes(c, arrt, rhs, 0)) {
|
||||
let m: str = "emitslicedata: slice-literal element not a foldable constant (#10, rule 7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// Writable backing data.
|
||||
emitline("DATAW ");
|
||||
emitsymnamehint(c, name, module);
|
||||
emitline(".d(SB),\"");
|
||||
emitarraylitbytes(c, arrt, rhs, 1);
|
||||
emitline("\"\n");
|
||||
// 24B header: ptr placeholder + LE len + LE cap (both = k). Word
|
||||
// sizes from the type table (rule 13).
|
||||
emitline("DATAW ");
|
||||
emitsymnamehint(c, name, module);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
let ptrsz: i32 = primtypesize("uintptr"): i32;
|
||||
for (i < ptrsz) { emitdatawbyte(0u8); i += 1; };
|
||||
let lensz: i32 = primtypesize("size"): i32;
|
||||
i = 0;
|
||||
let kv: u64 = k: u64;
|
||||
for (i < lensz) {
|
||||
emitdatawbyte((kv & 255u64): u8);
|
||||
kv = kv >> 8u64;
|
||||
i += 1;
|
||||
};
|
||||
i = 0;
|
||||
kv = k: u64;
|
||||
for (i < lensz) {
|
||||
emitdatawbyte((kv & 255u64): u8);
|
||||
kv = kv >> 8u64;
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
// Patch the ptr word with the backing VA.
|
||||
emitline("DATAR ");
|
||||
emitsymnamehint(c, name, module);
|
||||
emitline("+0(SB),");
|
||||
emitsymnamehint(c, name, module);
|
||||
emitline(".d(SB)\n");
|
||||
};
|
||||
|
||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
@@ -2089,7 +2178,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
if (sz == primtypesize("str"): i32 && !issg && letdeclkind(d) != tykind.TY_SLICE) {
|
||||
if (sz == primtypesize("str"): i32 && !issg && !letvarisslice(c, nm)) {
|
||||
let r: *node = d.rhs;
|
||||
for (r != nil) {
|
||||
if (r.kind != nkind.N_CAST) { break; };
|
||||
@@ -2154,34 +2243,43 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
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
|
||||
// link.
|
||||
let ok: bool = true;
|
||||
if (d.rhs != nil) {
|
||||
let r: *node = d.rhs;
|
||||
for (r != nil) {
|
||||
if (r.kind != nkind.N_CAST) { break; };
|
||||
r = r.lhs;
|
||||
};
|
||||
ok = false;
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_NIL) { ok = true; };
|
||||
};
|
||||
if (sz == tyslicesize(): i32 && !issg && letvarisslice(c, nm)) {
|
||||
let r: *node = d.rhs;
|
||||
for (r != nil) {
|
||||
if (r.kind != nkind.N_CAST) { break; };
|
||||
r = r.lhs;
|
||||
};
|
||||
if (ok) {
|
||||
emitline("DATAW ");
|
||||
emitsymnamehint(c, nm, d.nmod);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
let szsl: i32 = tyslicesize(): i32;
|
||||
for (i < szsl) {
|
||||
emitdatawbyte(0u8);
|
||||
i += 1;
|
||||
// #10 part a: slice-literal static init
|
||||
// routes through emitslicedata (header +
|
||||
// writable backing + DATAR). Loud-stops on
|
||||
// the deferred element kinds and the read-
|
||||
// only/`...` shapes (rule 7).
|
||||
if (r != nil && r.kind == nkind.N_ARRLIT) {
|
||||
emitslicedata(c, nm, d.nmod,
|
||||
d.lhs.type_: *tinfo, r);
|
||||
} else {
|
||||
// zero-init: accept no rhs or nil.
|
||||
// Any other rhs is skipped →
|
||||
// undefined symbol at link.
|
||||
let ok: bool = true;
|
||||
if (d.rhs != nil) {
|
||||
ok = false;
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_NIL) { ok = true; };
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
emitline("DATAW ");
|
||||
emitsymnamehint(c, nm, d.nmod);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
let szsl: i32 = tyslicesize(): i32;
|
||||
for (i < szsl) {
|
||||
emitdatawbyte(0u8);
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
// Struct globals — any size, zero-init only.
|
||||
@@ -2317,6 +2415,16 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||
emitarraydata(c, "DATA",
|
||||
d.str, d.nmod, at, r);
|
||||
};
|
||||
// #10: a read-only `def g: []T = [...]`
|
||||
// slice literal can't carry the ptr reloc
|
||||
// emitslicedata needs (DATAR holder must be
|
||||
// DATAW, w6a asm.c:362). Loud-stop, never
|
||||
// silent no-emit.
|
||||
if (au.kind == tykind.TY_SLICE) {
|
||||
let m: str = "emitdefconstants: module-level slice-literal init needs a writable `let` (DATAR holder must be DATAW, w6a asm.c:362); read-only `def` unsupported (#10, rule 7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user