wcc: struct-composite let/def DATA emit via SSoT helper (#129 A.2)

Extract emit_struct_data + emit_struct_lit_bytes helpers (both stages,
mirrored) for module-level let/def with N_STRUCTLIT initializer. Walks
Tfield linked-list in declaration order, zero-fills padding via per-
field offset (rule 13, no hardcoded sizes), dispatches per field kind:
integer via fold_int_literal, float via inline bitcast + sign-XOR byte-
loop (A.1 shape, no INT64_MIN materialised — sibling #144), nested
struct via recursion (#145 inner-field-name-leak gates the test row).
Out-of-scope field kinds (str/slice/ptr/array) fatal loud per rule 7.

LOAD-side widened symmetric to A.1 precedent: cstage cgexpr N_DOT
direct-struct-ident + chained-N_DOT widened via new DefStruct registry
(def_isstructdef populated in let_collect); wwstage cgdot direct-struct-
global falls through to defvarstructinfo on letvarstructinfo nil
(defent.dtnode field added, populated in collectdefs). Both stages
materialise struct-def via LEAQ name(SB) same as struct-let.

Pre-existing cstage scalar 8B short-circuit at emit_lets caused silent
fold-fail-continue on 8B struct lits (`struct{i32,i32}`); gate now
excludes let_isstruct so 8B struct lits route through emit_struct_data.
Wwstage's `!issg` gate was already correct; symmetric ordering restored.

Closes (all bootstrap-NEUTRAL pre-impl; γ-cleanup #40 first consumer):
- emit_lets `is_struct continue` skip → struct lets emitted no DATA
- emit_defs no struct arm → struct defs emitted no DATA
- cstage cgexpr N_DOT for struct-def emitted MOVSXD (BP), AX (broken
  stack-frame read)
- cstage emit_lets sz==8 short-circuit silently skipped 8B struct lits

Test 918 (7 rows: let_int_struct / def_int_struct / let_float_field /
def_float_field / let_empty_struct / let_int_struct_8b / let_norhs_
struct_regression) registered. Make test: 181/181 incl. 990-997 byte-id
+ combined_ww_fresh.

Followups filed:
- #145 (task #41) — nested struct-lit inner field-name leaks as extern
- #42 — wwstage dotchainresolve missing defvarstructinfo lookup (A.2-
  scope-clean today; surfaces post-#145 nested-struct shapes)
- A.3 (task #39) — array static-init audit (parks shape-4 array-in-struct)
- γ-cleanup (task #40) — lib/math const-floatinfo re-fold, blocked-by A.2
This commit is contained in:
2026-05-27 05:04:05 +09:00
parent 1f8fcdc0ee
commit 0ed0b3933c
7 changed files with 1155 additions and 14 deletions

View File

@@ -16583,9 +16583,15 @@ fn cgdot(c: *cgen, n: *node) void = {
// load at fi.foff(CX). Mirrors the local "Direct struct local"
// branch above, swapping the BP frame slot for the global VA.
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
// #129 A.2: also handles struct-typed `def`s via defvarstructinfo;
// emitstructdata gives them DATA storage at name(SB), and this
// LEAQ-and-offset shape mirrors the let path. Pre-A.2 the def
// fell through to the integer-let MOVQ catch-all (reading garbage
// from the wrong offset).
if (lhs != nil) {
if (lhs.kind == nkind.N_IDENT) {
let si: *structinfo = letvarstructinfo(c, lhs.str);
if (si == nil) { si = defvarstructinfo(c, lhs.str); };
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -24903,6 +24909,36 @@ fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
return nil;
};
// defvarstructinfo — sister of letvarstructinfo for top-level struct
// `def`s. #129 A.2 adds DATA storage for struct-typed defs; the
// LOAD-side cgdot direct-struct-global branch needs to resolve the
// def's structinfo the same way it resolves a let's, so the field-
// offset arithmetic + LEAQ name(SB) routing fires. Walks c.defs and
// the type-spec node (defent.dtnode), aliaslookup-chasing TY_NAMED
// through to the underlying struct name. Returns nil for non-struct
// defs (int/float/str — those use the existing emitsymname-based
// paths).
fn defvarstructinfo(c: *cgen, name: str) *structinfo = {
let e: *defent = c.defs;
for (e != nil) {
if (streq(e.dname, name)) {
let t: *node = e.dtnode;
for (t != nil) {
if (t.kind != nkind.N_TNAME) { return nil; };
let nm: str = t.str;
let si: *structinfo = structlookup(c, nm);
if (si != nil) { return si; };
let nx: *node = aliaslookup(c, nm);
if (nx == nil) { return nil; };
t = nx;
};
return nil;
};
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 = {
@@ -25061,6 +25097,160 @@ fn emitfloatlitdata(c: *cgen, directive: str, name: str,
return true;
};
// emitstructlitbytes — payload of a struct-typed top-level let/def
// with N_STRUCTLIT rhs. Walks structt.fields, zero-fills padding via
// the per-field offset (rule 13), dispatches per field type:
// foldintliteral for int/bool/nil, inline bitcast+sign-XOR for float,
// recursive call for nested struct. Other field kinds (str / slice /
// ptr-with-address / array) are out of #129 A.2 scope — rule-7 aborts
// loud rather than silently emitting wrong bytes. Mirror of cstage
// emit_struct_lit_bytes. `base` offsets the field-start computation
// so the recursive call walks an inner struct's fields within its
// outer parent's byte stream.
fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node,
base: u64) bool = {
let su: *tinfo = structt;
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
if (su == nil) { return false; };
if (su.kind != tykind.TY_STRUCT) { return false; };
let pos: u64 = base;
let f: *tfield = su.fields;
for (f != nil) {
let fstart: u64 = base + f.offset;
for (pos < fstart) {
emitdatawbyte(0u8);
pos = pos + 1u64;
};
let v: *node = nil;
if (rhs != nil) {
let fnod: *node = rhs.list;
for (fnod != nil) {
if (streq(fnod.str, f.name)) {
v = fnod.lhs;
break;
};
fnod = fnod.next;
};
};
let fsz: i32 = f.type_.size: i32;
if (v == nil) {
let i: i32 = 0;
for (i < fsz) {
emitdatawbyte(0u8);
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
let vr: *node = v;
for (vr != nil && vr.kind == nkind.N_CAST) { vr = vr.lhs; };
let fu: *tinfo = f.type_;
for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; };
if (fu != nil && fu.kind == tykind.TY_STRUCT) {
if (vr == nil) {
let m: str = "emitstructlitbytes: nested struct field rhs nil (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (vr.kind != nkind.N_STRUCTLIT) {
let m: str = "emitstructlitbytes: nested struct rhs not N_STRUCTLIT (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
emitstructlitbytes(c, f.type_, vr, fstart);
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
if (typeisfloat(f.type_)) {
let isf32: bool = (fsz == 4);
let neg: bool = false;
let fr: *node = vr;
if (fr != nil) { if (fr.kind == nkind.N_UN) {
if (fr.op == tkind.TK_MINUS) {
neg = true;
fr = fr.lhs;
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
} else { if (fr.op == tkind.TK_PLUS) {
fr = fr.lhs;
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
};};
};};
if (fr == nil) {
let m: str = "emitstructlitbytes: float field rhs nil (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (fr.kind != nkind.N_FLOATLIT) {
let m: str = "emitstructlitbytes: float field rhs not FLOATLIT (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
let bits: u64 = fr.uval;
if (isf32) {
let dv: f64 = *((&bits): *f64);
let fv: f32 = (dv: f32);
let uv: u32 = *((&fv): *u32);
bits = uv: u64;
};
let i: i32 = 0;
let nb: u64 = bits;
for (i < fsz) {
let b: u8 = (nb & 255u64): u8;
if (neg) {
if (i == fsz - 1) { b = b ^ 128u8; };
};
emitdatawbyte(b);
nb = nb >> 8u64;
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
let iv: u64 = 0u64;
if (!foldintliteral(vr, &iv)) {
let m: str = "emitstructlitbytes: field rhs not foldable (str/slice/ptr/array out of #129 A.2 scope)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
let i: i32 = 0;
let nb: u64 = iv;
for (i < fsz) {
emitdatawbyte((nb & 255u64): u8);
nb = nb >> 8u64;
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
};
let endpos: u64 = base + structt.size;
for (pos < endpos) {
emitdatawbyte(0u8);
pos = pos + 1u64;
};
return true;
};
// emitstructdata — top-level wrapper. Opens the DATA/DATAW directive
// then delegates to emitstructlitbytes. Shared between emitletdataw
// struct arm and emitdefconstants struct arm (#129 A.2).
fn emitstructdata(c: *cgen, directive: str, name: str,
structt: *tinfo, rhs: *node) bool = {
let su: *tinfo = structt;
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
if (su == nil) { return false; };
if (su.kind != tykind.TY_STRUCT) { return false; };
emitline(directive);
emitline(" ");
emitsymname(c, name);
emitline("(SB),\"");
emitstructlitbytes(c, structt, rhs, 0u64);
emitline("\"\n");
return true;
};
fn emitletdataw(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
@@ -25080,6 +25270,21 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitfloatlitdata(c, "DATAW", nm, fsz,
d.rhs);
};
// #129 A.2: struct-typed let with N_STRUCTLIT rhs
// routes through the emitstructdata SSoT helper.
// Pre-A.2 emitletdataw had no struct arm, so the
// declaration fell out of the .data section and
// the link surfaced an undefined-symbol error.
if (issg) {
let r: *node = d.rhs;
if (r != nil) {
if (r.kind == nkind.N_STRUCTLIT) {
let st: *tinfo = d.lhs.type_: *tinfo;
emitstructdata(c, "DATAW", nm,
st, r);
};
};
};
// Skip the scalar 8B path when the global is a
// fixed-size array that just happens to sum to 8
// bytes (e.g. [4]u16, [8]u8) — the array path
@@ -25341,6 +25546,25 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
if (dfsz > 0) {
emitfloatlitdata(c, "DATA", d.str,
dfsz, d.rhs);
} else {
// #129 A.2: struct-typed def with N_STRUCTLIT
// rhs. The checker stamps d.lhs.type_ with the
// struct's tinfo; helper peels TY_NAMED. Parallel
// to emitletdataw struct arm; uses DATA (read-
// only) directive.
if (r != nil) { if (r.kind == nkind.N_STRUCTLIT) {
let st: *tinfo = d.lhs.type_: *tinfo;
let su: *tinfo = st;
for (su != nil && su.kind == tykind.TY_NAMED) {
su = su.under;
};
if (su != nil) {
if (su.kind == tykind.TY_STRUCT) {
emitstructdata(c, "DATA",
d.str, st, r);
};
};
};};
};
};
if (ok) {
@@ -25606,6 +25830,9 @@ type defent = struct {
dname: str,
dmod: str, // originating module (`// MODULE: foo`), or empty
drhs: *node,
dtnode: *node, // #129 A.2: type-spec node (d.lhs); needed for
// struct-def structinfo lookup at the cgdot
// LOAD-side widening site.
dnext: *defent,
};
@@ -25614,7 +25841,7 @@ fn collectdefs(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
if (d.kind == nkind.N_DEF) {
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dnext=c.defs})!;
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dtnode=d.lhs, dnext=c.defs})!;
c.defs = e;
};
d = d.next;

View File

@@ -1114,6 +1114,36 @@ fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
return nil;
};
// defvarstructinfo — sister of letvarstructinfo for top-level struct
// `def`s. #129 A.2 adds DATA storage for struct-typed defs; the
// LOAD-side cgdot direct-struct-global branch needs to resolve the
// def's structinfo the same way it resolves a let's, so the field-
// offset arithmetic + LEAQ name(SB) routing fires. Walks c.defs and
// the type-spec node (defent.dtnode), aliaslookup-chasing TY_NAMED
// through to the underlying struct name. Returns nil for non-struct
// defs (int/float/str — those use the existing emitsymname-based
// paths).
fn defvarstructinfo(c: *cgen, name: str) *structinfo = {
let e: *defent = c.defs;
for (e != nil) {
if (streq(e.dname, name)) {
let t: *node = e.dtnode;
for (t != nil) {
if (t.kind != nkind.N_TNAME) { return nil; };
let nm: str = t.str;
let si: *structinfo = structlookup(c, nm);
if (si != nil) { return si; };
let nx: *node = aliaslookup(c, nm);
if (nx == nil) { return nil; };
t = nx;
};
return nil;
};
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 = {
@@ -1272,6 +1302,160 @@ fn emitfloatlitdata(c: *cgen, directive: str, name: str,
return true;
};
// emitstructlitbytes — payload of a struct-typed top-level let/def
// with N_STRUCTLIT rhs. Walks structt.fields, zero-fills padding via
// the per-field offset (rule 13), dispatches per field type:
// foldintliteral for int/bool/nil, inline bitcast+sign-XOR for float,
// recursive call for nested struct. Other field kinds (str / slice /
// ptr-with-address / array) are out of #129 A.2 scope — rule-7 aborts
// loud rather than silently emitting wrong bytes. Mirror of cstage
// emit_struct_lit_bytes. `base` offsets the field-start computation
// so the recursive call walks an inner struct's fields within its
// outer parent's byte stream.
fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node,
base: u64) bool = {
let su: *tinfo = structt;
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
if (su == nil) { return false; };
if (su.kind != tykind.TY_STRUCT) { return false; };
let pos: u64 = base;
let f: *tfield = su.fields;
for (f != nil) {
let fstart: u64 = base + f.offset;
for (pos < fstart) {
emitdatawbyte(0u8);
pos = pos + 1u64;
};
let v: *node = nil;
if (rhs != nil) {
let fnod: *node = rhs.list;
for (fnod != nil) {
if (streq(fnod.str, f.name)) {
v = fnod.lhs;
break;
};
fnod = fnod.next;
};
};
let fsz: i32 = f.type_.size: i32;
if (v == nil) {
let i: i32 = 0;
for (i < fsz) {
emitdatawbyte(0u8);
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
let vr: *node = v;
for (vr != nil && vr.kind == nkind.N_CAST) { vr = vr.lhs; };
let fu: *tinfo = f.type_;
for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; };
if (fu != nil && fu.kind == tykind.TY_STRUCT) {
if (vr == nil) {
let m: str = "emitstructlitbytes: nested struct field rhs nil (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (vr.kind != nkind.N_STRUCTLIT) {
let m: str = "emitstructlitbytes: nested struct rhs not N_STRUCTLIT (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
emitstructlitbytes(c, f.type_, vr, fstart);
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
if (typeisfloat(f.type_)) {
let isf32: bool = (fsz == 4);
let neg: bool = false;
let fr: *node = vr;
if (fr != nil) { if (fr.kind == nkind.N_UN) {
if (fr.op == tkind.TK_MINUS) {
neg = true;
fr = fr.lhs;
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
} else { if (fr.op == tkind.TK_PLUS) {
fr = fr.lhs;
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
};};
};};
if (fr == nil) {
let m: str = "emitstructlitbytes: float field rhs nil (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (fr.kind != nkind.N_FLOATLIT) {
let m: str = "emitstructlitbytes: float field rhs not FLOATLIT (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
let bits: u64 = fr.uval;
if (isf32) {
let dv: f64 = *((&bits): *f64);
let fv: f32 = (dv: f32);
let uv: u32 = *((&fv): *u32);
bits = uv: u64;
};
let i: i32 = 0;
let nb: u64 = bits;
for (i < fsz) {
let b: u8 = (nb & 255u64): u8;
if (neg) {
if (i == fsz - 1) { b = b ^ 128u8; };
};
emitdatawbyte(b);
nb = nb >> 8u64;
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
let iv: u64 = 0u64;
if (!foldintliteral(vr, &iv)) {
let m: str = "emitstructlitbytes: field rhs not foldable (str/slice/ptr/array out of #129 A.2 scope)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
let i: i32 = 0;
let nb: u64 = iv;
for (i < fsz) {
emitdatawbyte((nb & 255u64): u8);
nb = nb >> 8u64;
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
};
let endpos: u64 = base + structt.size;
for (pos < endpos) {
emitdatawbyte(0u8);
pos = pos + 1u64;
};
return true;
};
// emitstructdata — top-level wrapper. Opens the DATA/DATAW directive
// then delegates to emitstructlitbytes. Shared between emitletdataw
// struct arm and emitdefconstants struct arm (#129 A.2).
fn emitstructdata(c: *cgen, directive: str, name: str,
structt: *tinfo, rhs: *node) bool = {
let su: *tinfo = structt;
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
if (su == nil) { return false; };
if (su.kind != tykind.TY_STRUCT) { return false; };
emitline(directive);
emitline(" ");
emitsymname(c, name);
emitline("(SB),\"");
emitstructlitbytes(c, structt, rhs, 0u64);
emitline("\"\n");
return true;
};
fn emitletdataw(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
@@ -1291,6 +1475,21 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitfloatlitdata(c, "DATAW", nm, fsz,
d.rhs);
};
// #129 A.2: struct-typed let with N_STRUCTLIT rhs
// routes through the emitstructdata SSoT helper.
// Pre-A.2 emitletdataw had no struct arm, so the
// declaration fell out of the .data section and
// the link surfaced an undefined-symbol error.
if (issg) {
let r: *node = d.rhs;
if (r != nil) {
if (r.kind == nkind.N_STRUCTLIT) {
let st: *tinfo = d.lhs.type_: *tinfo;
emitstructdata(c, "DATAW", nm,
st, r);
};
};
};
// Skip the scalar 8B path when the global is a
// fixed-size array that just happens to sum to 8
// bytes (e.g. [4]u16, [8]u8) — the array path
@@ -1552,6 +1751,25 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
if (dfsz > 0) {
emitfloatlitdata(c, "DATA", d.str,
dfsz, d.rhs);
} else {
// #129 A.2: struct-typed def with N_STRUCTLIT
// rhs. The checker stamps d.lhs.type_ with the
// struct's tinfo; helper peels TY_NAMED. Parallel
// to emitletdataw struct arm; uses DATA (read-
// only) directive.
if (r != nil) { if (r.kind == nkind.N_STRUCTLIT) {
let st: *tinfo = d.lhs.type_: *tinfo;
let su: *tinfo = st;
for (su != nil && su.kind == tykind.TY_NAMED) {
su = su.under;
};
if (su != nil) {
if (su.kind == tykind.TY_STRUCT) {
emitstructdata(c, "DATA",
d.str, st, r);
};
};
};};
};
};
if (ok) {
@@ -1817,6 +2035,9 @@ type defent = struct {
dname: str,
dmod: str, // originating module (`// MODULE: foo`), or empty
drhs: *node,
dtnode: *node, // #129 A.2: type-spec node (d.lhs); needed for
// struct-def structinfo lookup at the cgdot
// LOAD-side widening site.
dnext: *defent,
};
@@ -1825,7 +2046,7 @@ fn collectdefs(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
if (d.kind == nkind.N_DEF) {
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dnext=c.defs})!;
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dtnode=d.lhs, dnext=c.defs})!;
c.defs = e;
};
d = d.next;

View File

@@ -1964,9 +1964,15 @@ fn cgdot(c: *cgen, n: *node) void = {
// load at fi.foff(CX). Mirrors the local "Direct struct local"
// branch above, swapping the BP frame slot for the global VA.
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
// #129 A.2: also handles struct-typed `def`s via defvarstructinfo;
// emitstructdata gives them DATA storage at name(SB), and this
// LEAQ-and-offset shape mirrors the let path. Pre-A.2 the def
// fell through to the integer-let MOVQ catch-all (reading garbage
// from the wrong offset).
if (lhs != nil) {
if (lhs.kind == nkind.N_IDENT) {
let si: *structinfo = letvarstructinfo(c, lhs.str);
if (si == nil) { si = defvarstructinfo(c, lhs.str); };
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {

View File

@@ -16583,9 +16583,15 @@ fn cgdot(c: *cgen, n: *node) void = {
// load at fi.foff(CX). Mirrors the local "Direct struct local"
// branch above, swapping the BP frame slot for the global VA.
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
// #129 A.2: also handles struct-typed `def`s via defvarstructinfo;
// emitstructdata gives them DATA storage at name(SB), and this
// LEAQ-and-offset shape mirrors the let path. Pre-A.2 the def
// fell through to the integer-let MOVQ catch-all (reading garbage
// from the wrong offset).
if (lhs != nil) {
if (lhs.kind == nkind.N_IDENT) {
let si: *structinfo = letvarstructinfo(c, lhs.str);
if (si == nil) { si = defvarstructinfo(c, lhs.str); };
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
@@ -24903,6 +24909,36 @@ fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
return nil;
};
// defvarstructinfo — sister of letvarstructinfo for top-level struct
// `def`s. #129 A.2 adds DATA storage for struct-typed defs; the
// LOAD-side cgdot direct-struct-global branch needs to resolve the
// def's structinfo the same way it resolves a let's, so the field-
// offset arithmetic + LEAQ name(SB) routing fires. Walks c.defs and
// the type-spec node (defent.dtnode), aliaslookup-chasing TY_NAMED
// through to the underlying struct name. Returns nil for non-struct
// defs (int/float/str — those use the existing emitsymname-based
// paths).
fn defvarstructinfo(c: *cgen, name: str) *structinfo = {
let e: *defent = c.defs;
for (e != nil) {
if (streq(e.dname, name)) {
let t: *node = e.dtnode;
for (t != nil) {
if (t.kind != nkind.N_TNAME) { return nil; };
let nm: str = t.str;
let si: *structinfo = structlookup(c, nm);
if (si != nil) { return si; };
let nx: *node = aliaslookup(c, nm);
if (nx == nil) { return nil; };
t = nx;
};
return nil;
};
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 = {
@@ -25061,6 +25097,160 @@ fn emitfloatlitdata(c: *cgen, directive: str, name: str,
return true;
};
// emitstructlitbytes — payload of a struct-typed top-level let/def
// with N_STRUCTLIT rhs. Walks structt.fields, zero-fills padding via
// the per-field offset (rule 13), dispatches per field type:
// foldintliteral for int/bool/nil, inline bitcast+sign-XOR for float,
// recursive call for nested struct. Other field kinds (str / slice /
// ptr-with-address / array) are out of #129 A.2 scope — rule-7 aborts
// loud rather than silently emitting wrong bytes. Mirror of cstage
// emit_struct_lit_bytes. `base` offsets the field-start computation
// so the recursive call walks an inner struct's fields within its
// outer parent's byte stream.
fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node,
base: u64) bool = {
let su: *tinfo = structt;
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
if (su == nil) { return false; };
if (su.kind != tykind.TY_STRUCT) { return false; };
let pos: u64 = base;
let f: *tfield = su.fields;
for (f != nil) {
let fstart: u64 = base + f.offset;
for (pos < fstart) {
emitdatawbyte(0u8);
pos = pos + 1u64;
};
let v: *node = nil;
if (rhs != nil) {
let fnod: *node = rhs.list;
for (fnod != nil) {
if (streq(fnod.str, f.name)) {
v = fnod.lhs;
break;
};
fnod = fnod.next;
};
};
let fsz: i32 = f.type_.size: i32;
if (v == nil) {
let i: i32 = 0;
for (i < fsz) {
emitdatawbyte(0u8);
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
let vr: *node = v;
for (vr != nil && vr.kind == nkind.N_CAST) { vr = vr.lhs; };
let fu: *tinfo = f.type_;
for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; };
if (fu != nil && fu.kind == tykind.TY_STRUCT) {
if (vr == nil) {
let m: str = "emitstructlitbytes: nested struct field rhs nil (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (vr.kind != nkind.N_STRUCTLIT) {
let m: str = "emitstructlitbytes: nested struct rhs not N_STRUCTLIT (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
emitstructlitbytes(c, f.type_, vr, fstart);
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
if (typeisfloat(f.type_)) {
let isf32: bool = (fsz == 4);
let neg: bool = false;
let fr: *node = vr;
if (fr != nil) { if (fr.kind == nkind.N_UN) {
if (fr.op == tkind.TK_MINUS) {
neg = true;
fr = fr.lhs;
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
} else { if (fr.op == tkind.TK_PLUS) {
fr = fr.lhs;
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
};};
};};
if (fr == nil) {
let m: str = "emitstructlitbytes: float field rhs nil (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (fr.kind != nkind.N_FLOATLIT) {
let m: str = "emitstructlitbytes: float field rhs not FLOATLIT (#129 A.2)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
let bits: u64 = fr.uval;
if (isf32) {
let dv: f64 = *((&bits): *f64);
let fv: f32 = (dv: f32);
let uv: u32 = *((&fv): *u32);
bits = uv: u64;
};
let i: i32 = 0;
let nb: u64 = bits;
for (i < fsz) {
let b: u8 = (nb & 255u64): u8;
if (neg) {
if (i == fsz - 1) { b = b ^ 128u8; };
};
emitdatawbyte(b);
nb = nb >> 8u64;
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
continue;
};
let iv: u64 = 0u64;
if (!foldintliteral(vr, &iv)) {
let m: str = "emitstructlitbytes: field rhs not foldable (str/slice/ptr/array out of #129 A.2 scope)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
let i: i32 = 0;
let nb: u64 = iv;
for (i < fsz) {
emitdatawbyte((nb & 255u64): u8);
nb = nb >> 8u64;
i = i + 1;
};
pos = fstart + fsz: u64;
f = f.tnext;
};
let endpos: u64 = base + structt.size;
for (pos < endpos) {
emitdatawbyte(0u8);
pos = pos + 1u64;
};
return true;
};
// emitstructdata — top-level wrapper. Opens the DATA/DATAW directive
// then delegates to emitstructlitbytes. Shared between emitletdataw
// struct arm and emitdefconstants struct arm (#129 A.2).
fn emitstructdata(c: *cgen, directive: str, name: str,
structt: *tinfo, rhs: *node) bool = {
let su: *tinfo = structt;
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
if (su == nil) { return false; };
if (su.kind != tykind.TY_STRUCT) { return false; };
emitline(directive);
emitline(" ");
emitsymname(c, name);
emitline("(SB),\"");
emitstructlitbytes(c, structt, rhs, 0u64);
emitline("\"\n");
return true;
};
fn emitletdataw(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
@@ -25080,6 +25270,21 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitfloatlitdata(c, "DATAW", nm, fsz,
d.rhs);
};
// #129 A.2: struct-typed let with N_STRUCTLIT rhs
// routes through the emitstructdata SSoT helper.
// Pre-A.2 emitletdataw had no struct arm, so the
// declaration fell out of the .data section and
// the link surfaced an undefined-symbol error.
if (issg) {
let r: *node = d.rhs;
if (r != nil) {
if (r.kind == nkind.N_STRUCTLIT) {
let st: *tinfo = d.lhs.type_: *tinfo;
emitstructdata(c, "DATAW", nm,
st, r);
};
};
};
// Skip the scalar 8B path when the global is a
// fixed-size array that just happens to sum to 8
// bytes (e.g. [4]u16, [8]u8) — the array path
@@ -25341,6 +25546,25 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
if (dfsz > 0) {
emitfloatlitdata(c, "DATA", d.str,
dfsz, d.rhs);
} else {
// #129 A.2: struct-typed def with N_STRUCTLIT
// rhs. The checker stamps d.lhs.type_ with the
// struct's tinfo; helper peels TY_NAMED. Parallel
// to emitletdataw struct arm; uses DATA (read-
// only) directive.
if (r != nil) { if (r.kind == nkind.N_STRUCTLIT) {
let st: *tinfo = d.lhs.type_: *tinfo;
let su: *tinfo = st;
for (su != nil && su.kind == tykind.TY_NAMED) {
su = su.under;
};
if (su != nil) {
if (su.kind == tykind.TY_STRUCT) {
emitstructdata(c, "DATA",
d.str, st, r);
};
};
};};
};
};
if (ok) {
@@ -25606,6 +25830,9 @@ type defent = struct {
dname: str,
dmod: str, // originating module (`// MODULE: foo`), or empty
drhs: *node,
dtnode: *node, // #129 A.2: type-spec node (d.lhs); needed for
// struct-def structinfo lookup at the cgdot
// LOAD-side widening site.
dnext: *defent,
};
@@ -25614,7 +25841,7 @@ fn collectdefs(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
if (d.kind == nkind.N_DEF) {
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dnext=c.defs})!;
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dtnode=d.lhs, dnext=c.defs})!;
c.defs = e;
};
d = d.next;