wcc/ww: emit correct (tag,payload) for a tagged value in static-init (#19)
A tagged-union value nested in module-level array/struct static-init mis-emitted in both stages: the lit-bytes emitters had no TY_TAGGED arm, so a tagged element/field fell to the int path and the payload landed in the TAG word -- match then read the wrong variant. The zero-placeholder idiom (today the only way to declare a tagged global: zero-init in static, write at runtime) was correct only by accident (int-variant zero folds to (0,0), which equals the right (tag0,0)). Extract a raw-byte core emittaggedbytes/emit_tagged_bytes -- variant tag@+0, int payload@+8, zero-pad to the slot size; no directive, no offset, no reloc -- and refactor the scalar tagged emitter to delegate to it (byte-id-neutral). Add a TY_TAGGED member branch to the array and struct lit-bytes emitters (both stages) that calls the core at the existing full-slot stride, before the int fallthrough. Zero stays (0,0) byte-identical; a non-zero element/field now emits (tag,payload) correctly. A wide (str/slice) or struct/>8B payload nested in an aggregate needs reloc-at-member-offset machinery the aggregate byte-emitters don't have, so it is loud-rejected (rule 7), deferred to #30; the existing slice-of-tagged static-init reject is unchanged. Regenerates the w6c and wwdump combined.ww. Table-driven 843 test: non-zero array/struct (pre-fix returned the wrong variant), the non-tag-0 bool-variant edge, byte-id-neutral zero-placeholder rows, and wide-payload reject rows; each run row also pins cs-vs-ww asm.
This commit is contained in:
@@ -1757,6 +1757,21 @@ fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node,
|
||||
for (vr != nil && vr.kind == nkind.N_CAST) { vr = vr.lhs; };
|
||||
let fu: *tinfo = f.type_;
|
||||
fu = tichase(fu);
|
||||
// #19 option A: a non-nullable tagged-union field rides the shared
|
||||
// (tag,payload) core at the field slot size, mirroring the scalar
|
||||
// tagged global — NOT the int emitter. Wide/struct/non-foldable
|
||||
// payload loud-rejects (task #30 sub-item, rule 7). v is non-nil
|
||||
// here (the absent-field zero-fill is handled above).
|
||||
if (fu != nil && fu.kind == tykind.TY_TAGGED && !typeisnullable(fu)) {
|
||||
if (!emittaggedbytes(c, f.type_, v, fsz, 1)) {
|
||||
let m: str = "emitstructlitbytes: tagged-union struct-field static-init needs a zero/int payload; wide (str/slice) or struct payload is deferred (task #30, rule 7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
if (fu != nil && fu.kind == tykind.TY_STRUCT) {
|
||||
if (vr == nil) {
|
||||
let m: str = "emitstructlitbytes: nested struct field rhs nil (#129 A.2)\n";
|
||||
@@ -1914,6 +1929,54 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node,
|
||||
let eu: *tinfo = au.sub;
|
||||
eu = tichase(eu);
|
||||
|
||||
// #19 option A: a non-nullable tagged-union element rides the shared
|
||||
// (tag,payload) core (emittaggedbytes) at the full slot stride esz,
|
||||
// mirroring the scalar tagged global — NOT the int emitter, which
|
||||
// mis-folds the payload into the tag word. A wide/struct/non-foldable
|
||||
// payload element loud-rejects (task #30 sub-item, rule 7). A nullable
|
||||
// `(*T|void)` element is a 1-word fold, not a tag box — left to the
|
||||
// existing int path (task #15).
|
||||
if (eu != nil && eu.kind == tykind.TY_TAGGED && !typeisnullable(eu)) {
|
||||
let idx: i32 = 0;
|
||||
let last_ev: *node = nil;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil && idx < alen) {
|
||||
if (e.kind == nkind.N_FIELD) {
|
||||
if (streq(e.str, "...")) { break; };
|
||||
};
|
||||
if (!emittaggedbytes(c, au.sub, e, esz, 0)) {
|
||||
let m: str = "emitarraylitbytes: tagged-union array element static-init needs a zero/int payload; wide (str/slice) or struct payload is deferred (task #30, rule 7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
last_ev = e;
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
if (emit_phase == 0) { return true; };
|
||||
idx = 0;
|
||||
let repeat: bool = false;
|
||||
e = rhs.list;
|
||||
for (e != nil && idx < alen) {
|
||||
if (e.kind == nkind.N_FIELD) {
|
||||
if (streq(e.str, "...")) { repeat = true; break; };
|
||||
};
|
||||
emittaggedbytes(c, au.sub, e, esz, 1);
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
for (idx < alen) {
|
||||
if (repeat && last_ev != nil) {
|
||||
emittaggedbytes(c, au.sub, last_ev, esz, 1);
|
||||
} else {
|
||||
let bb: i32 = 0;
|
||||
for (bb < esz) { emitdatawbyte(0u8); bb += 1; };
|
||||
};
|
||||
idx += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
if (eu != nil && eu.kind == tykind.TY_STRUCT) {
|
||||
// Validate: every element must be N_STRUCTLIT (after N_CAST).
|
||||
let idx: i32 = 0;
|
||||
@@ -2481,6 +2544,52 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
|
||||
emitline(".d(SB)\n");
|
||||
};
|
||||
|
||||
// emittaggedbytes — raw sz-byte static-init payload for a tagged-union
|
||||
// value: variant tag@+0 (8B), int payload@+8 (8B), zero-pad to sz. NO
|
||||
// directive open/close, NO reloc — emits exactly sz bytes via
|
||||
// emitdatawbyte at the current cursor. Handles zero (nil rhs) + a
|
||||
// foldable int payload only; a wide (str/slice) payload needs a DATAR the
|
||||
// raw core cannot place inside an already-open aggregate directive, and a
|
||||
// struct/non-foldable payload has no scalar form — both return false
|
||||
// WITHOUT emitting, and the caller loud-rejects (task #30 wide/struct
|
||||
// sub-item). `tti` is the union tinfo (NAMED-peeled + TY_TAGGED-gated
|
||||
// internally by flatvariantidxt). emit_phase 0 = validate only; 1 = emit.
|
||||
// rob's EXTRACT ruling (#19 option A): the scalar wrapper emittaggeddata
|
||||
// and the aggregate member branches (emitarraylitbytes/emitstructlitbytes)
|
||||
// share this raw core so a nested tagged member rides the SAME
|
||||
// (tag,payload) SSoT as a scalar tagged global. Mirror of cstage
|
||||
// emit_tagged_bytes.
|
||||
fn emittaggedbytes(c: *cgen, tti: *tinfo, rhs: *node, sz: i32, emit_phase: i32) bool = {
|
||||
if (tti == nil) { return false; };
|
||||
if (rhs == nil) {
|
||||
if (emit_phase == 1) {
|
||||
let zi: i32 = 0;
|
||||
for (zi < sz) { emitdatawbyte(0u8); zi += 1; };
|
||||
};
|
||||
return true;
|
||||
};
|
||||
let r: *node = rhs;
|
||||
for (r != nil && r.kind == nkind.N_CAST) { r = r.lhs; };
|
||||
if (r == nil) { return false; };
|
||||
let tag: i32 = flatvariantidxt(tti, r.type_: *tinfo, false);
|
||||
if (tag < 0) { return false; };
|
||||
if (nodeisstr(c, r) || nodeisslice(c, r)) { return false; };
|
||||
let v: u64 = 0u64;
|
||||
if (!foldintliteral(r, &v)) { return false; };
|
||||
if (emit_phase == 1) {
|
||||
let acc: u64 = 0u64;
|
||||
let i: i32 = 0;
|
||||
acc = tag: u64;
|
||||
for (i < 8) { emitdatawbyte((acc & 255u64): u8); acc = acc >> 8u64; i += 1; };
|
||||
acc = v;
|
||||
i = 0;
|
||||
for (i < 8) { emitdatawbyte((acc & 255u64): u8); acc = acc >> 8u64; i += 1; };
|
||||
i = 16;
|
||||
for (i < sz) { emitdatawbyte(0u8); i += 1; };
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// emittaggeddata — module-level `let g: (T0 | T1 | ...) = v;` static
|
||||
// init (#87). Byte-MIRRORS a runtime LOCAL tagged box (rob §3 SSoT pin):
|
||||
// tag word at +0 (the const-selected variant index, taggedvariantindex —
|
||||
@@ -2496,8 +2605,7 @@ fn emittaggeddata(c: *cgen, name: str, module: str, tt: *node, rhs: *node, sz: i
|
||||
emitline("DATAW ");
|
||||
emitsymnamehint(c, name, module);
|
||||
emitline("(SB),\"");
|
||||
let zi: i32 = 0;
|
||||
for (zi < sz) { emitdatawbyte(0u8); zi += 1; };
|
||||
emittaggedbytes(c, tt.type_: *tinfo, rhs, sz, 1);
|
||||
emitline("\"\n");
|
||||
return true;
|
||||
};
|
||||
@@ -2559,22 +2667,14 @@ fn emittaggeddata(c: *cgen, name: str, module: str, tt: *node, rhs: *node, sz: i
|
||||
};
|
||||
return true;
|
||||
};
|
||||
let v: u64 = 0u64;
|
||||
if (!foldintliteral(r, &v)) { return false; };
|
||||
// int/zero payload via the shared raw core; validate (phase 0) BEFORE
|
||||
// opening the directive so a non-foldable rhs returns false without
|
||||
// leaving a half-written DATAW.
|
||||
if (!emittaggedbytes(c, tt.type_: *tinfo, rhs, sz, 0)) { return false; };
|
||||
emitline("DATAW ");
|
||||
emitsymnamehint(c, name, module);
|
||||
emitline("(SB),\"");
|
||||
// tag@0
|
||||
acc = tag: u64;
|
||||
i = 0;
|
||||
for (i < 8) { emitdatawbyte((acc & 255u64): u8); acc = acc >> 8u64; i += 1; };
|
||||
// payload@8
|
||||
acc = v;
|
||||
i = 0;
|
||||
for (i < 8) { emitdatawbyte((acc & 255u64): u8); acc = acc >> 8u64; i += 1; };
|
||||
// pad to sz
|
||||
i = 16;
|
||||
for (i < sz) { emitdatawbyte(0u8); i += 1; };
|
||||
emittaggedbytes(c, tt.type_: *tinfo, rhs, sz, 1);
|
||||
emitline("\"\n");
|
||||
return true;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user