w6c+selfhost: cg_widen_tagged_store basereg + N_ASSIGN tagged field (closes #26)

Extended cg_widen_tagged_store (cstage) / cgwidentaggedstore (wwstage)
to take a base_reg/basereg parameter so the primitive supports non-BP
destinations. Cstage extends body in-place via via_outer gate +
spill+scratch+copy-out; wwstage splits into wrapper (non-BP) +
cgwidentaggedstorebp (BP-only) to dodge the no-goto constraint. New
N_ASSIGN field TY_TAGGED branch routes through the primitive for all
rhs shapes.

Scope-adjacent: fieldsize recurses through N_TTAGGED via slotsize and
TNAME-aliased-to-tagged via aliaslookup. Needed for the test fixtures.

Wwstage read-side N_DOT-of-tagged-field source is filed as task #28;
test rows use mark-canary verification until that lands.
This commit is contained in:
2026-05-14 19:12:20 +09:00
parent 1726bcef18
commit a5919ed8da
8 changed files with 644 additions and 116 deletions

View File

@@ -862,7 +862,7 @@ localfind(Local *head, const char *name)
static void cgexpr(Cg*, Node*, Local*);
static void cgstmt(Cg*, Node*, Local**, int*);
static void cg_widen_tagged_push(Cg*, Local**, Type*, Node*, int);
static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int);
static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int, int);
static void cg_widen_tag_remap(Cg*, Type*, Type*, int);
static void
@@ -913,9 +913,21 @@ cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off)
}
/* cg_widen_tagged_store — write the tagged-union slot bytes for `src`
* into BP+slot_off, sized to `sz` (8 for nullable fold, else 16/24+).
* Used by call-site widening (via cg_widen_tagged_push) and by the
* let/assign/return/struct-field-init paths.
* into base_reg+slot_off, sized to `sz` (8 for nullable fold, else
* 16/24+). Used by call-site widening (via cg_widen_tagged_push) and
* by the let/assign/return/struct-field-init paths.
*
* base_reg picks the addressing root for every write:
* - D_BP: function-frame slot. The original layout — callers pass
* a BP-relative slot_off and the function writes directly.
* - else (e.g. D_BX for a *struct field, D_CX for a top-level
* struct field): pointer-rooted dst. cgexpr inside this function
* trashes every GPR, so we can't carry base_reg across — instead
* we route every write through a fresh BP-rooted scratch slot,
* reload base_reg from a temp spill at the end, and word-copy
* scratch → (base_reg, slot_off). Caller is responsible for
* loading base_reg with the dst address before the call; the
* function preserves it across cgexpr via the spill.
*
* Branches by source shape (tagged_arg_size > 0 source counts as a
* tagged subset — possibly with different variant indices):
@@ -929,13 +941,34 @@ cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off)
* - scalar: cgexpr leaves AX; store at +8 with zero pad. */
static void
cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
int slot_off, int sz)
int base_reg, int slot_off, int sz)
{
/* For pointer-rooted dst, materialise into a BP-rooted scratch
* slot — body writes via `amem(D_BP, write_off + k)` — then copy
* out. Spill base_reg first so cgexpr can clobber freely. */
int via_outer = (base_reg != D_BP);
int base_spill = 0;
int write_off = slot_off;
if (via_outer) {
const char *spname = mklabel(c, "tagbase");
base_spill = local_alloc(c, locals_p, spname, 8, cg_frame);
ins2(c, A_MOVQ, areg(base_reg), amem(D_BP, base_spill));
const char *scname = mklabel(c, "tagscr");
write_off = local_alloc(c, locals_p, scname, sz, cg_frame);
/* Pre-zero so str/scalar branches (which leave high words
* untouched when sz exceeds the variant's footprint) still
* deliver a clean slot to the copy-out. */
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
for (int k = 0; k < sz; k += 8)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, write_off + k));
}
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
if (du == NULL || du->kind != TY_TAGGED) return;
if (du->nullable) {
cgexpr(c, src, *locals_p);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 0));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + 0));
if (via_outer) goto copy_out;
return;
}
/* `expr: TaggedAlias` where the cast's destination IS the union
@@ -970,29 +1003,30 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
ins2(c, A_MOVQ, amem(D_BP, soff + k),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, slot_off + k));
amem(D_BP, write_off + k));
}
} else {
cgexpr(c, src, *locals_p);
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, slot_off + 0));
amem(D_BP, write_off + 0));
if (ssz > 8)
ins2(c, A_MOVQ, areg(D_DX),
amem(D_BP, slot_off + 8));
amem(D_BP, write_off + 8));
if (ssz > 16)
ins2(c, A_MOVQ, areg(D_CX),
amem(D_BP, slot_off + 16));
amem(D_BP, write_off + 16));
if (ssz > 24)
ins2(c, A_MOVQ, areg(D_R8),
amem(D_BP, slot_off + 24));
amem(D_BP, write_off + 24));
}
if (ssz < sz) {
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
for (int k = ssz; k < sz; k += 8)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, slot_off + k));
amem(D_BP, write_off + k));
}
cg_widen_tag_remap(c, du, su, slot_off);
cg_widen_tag_remap(c, du, su, write_off);
if (via_outer) goto copy_out;
return;
}
/* Struct payload: zero the whole slot, then write fields/words
@@ -1002,7 +1036,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
for (int k = 0; k < sz; k += 8)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, slot_off + k));
amem(D_BP, write_off + k));
int tag = cg_tag_for_variant(du, st);
if (src->kind == N_IDENT) {
int soff = localfind(*locals_p, src->str);
@@ -1012,7 +1046,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
ins2(c, A_MOVQ, amem(D_BP, soff + k),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, slot_off + 8 + k));
amem(D_BP, write_off + 8 + k));
k += 8;
}
if (k < ssz) {
@@ -1026,7 +1060,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
ins2(c, lop,
amem(D_BP, soff + k), areg(D_AX));
ins2(c, lop, areg(D_AX),
amem(D_BP, slot_off + 8 + k));
amem(D_BP, write_off + 8 + k));
}
} else if (src->kind == N_STRUCTLIT) {
for (Node *f = src->list; f; f = f->next) {
@@ -1046,37 +1080,39 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
if (fld_isfloat(ftype, &sl_isf32)) {
int mov = sl_isf32 ? A_MOVSS : A_MOVSD;
ins2(c, mov, areg(D_X0),
amem(D_BP, slot_off + 8 + (int)foff));
amem(D_BP, write_off + 8 + (int)foff));
continue;
}
Type *fu = (ftype && ftype->kind == TY_NAMED)
? ftype->under : ftype;
if (fu && fu->kind == TY_STR) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, slot_off + 8 + (int)foff + 0));
amem(D_BP, write_off + 8 + (int)foff + 0));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, slot_off + 8 + (int)foff + 8));
amem(D_BP, write_off + 8 + (int)foff + 8));
continue;
}
int op = A_MOVQ;
if (fsz == 1) op = A_MOVB;
else if (fsz == 4) op = A_MOVL;
ins2(c, op, areg(D_AX),
amem(D_BP, slot_off + 8 + (int)foff));
amem(D_BP, write_off + 8 + (int)foff));
}
}
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
amem(D_BP, slot_off + 0));
amem(D_BP, write_off + 0));
if (via_outer) goto copy_out;
return;
}
/* str payload: AX=ptr, BX=len from cgexpr. */
if (type_isstr(st) || (su && su->kind == TY_STR)) {
cgexpr(c, src, *locals_p);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, slot_off + 16));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + 8));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, write_off + 16));
int tag = cg_tag_for_variant(du, st);
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
amem(D_BP, slot_off + 0));
amem(D_BP, write_off + 0));
if (via_outer) goto copy_out;
return;
}
/* Slice payload: cgexpr leaves (AX=ptr, BX=len, CX=cap). The
@@ -1084,12 +1120,13 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
* destination tagged-union slot be at least 32B. */
if (type_isslice(st) || (su && su->kind == TY_SLICE)) {
cgexpr(c, src, *locals_p);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, slot_off + 16));
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, slot_off + 24));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + 8));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, write_off + 16));
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, write_off + 24));
int tag = cg_tag_for_variant(du, st);
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
amem(D_BP, slot_off + 0));
amem(D_BP, write_off + 0));
if (via_outer) goto copy_out;
return;
}
/* Scalar / pointer / etc. The high slot word (when sz > 16) is
@@ -1099,9 +1136,21 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
* cg_widen_tagged_push pre-zeroes the scratch slot before
* calling us, so the call-site push still sees clean pad. */
cgexpr(c, src, *locals_p);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + 8));
int tag = cg_tag_for_variant(du, st);
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BP, slot_off + 0));
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BP, write_off + 0));
copy_out:
if (via_outer) {
/* cgexpr above clobbered base_reg — reload from spill, then
* word-copy scratch → caller's (base_reg, slot_off). */
ins2(c, A_MOVQ, amem(D_BP, base_spill), areg(base_reg));
for (int k = 0; k < sz; k += 8) {
ins2(c, A_MOVQ, amem(D_BP, write_off + k),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(base_reg, slot_off + k));
}
}
}
/* cg_widen_tagged_push — call-site widening. For shapes where cgexpr
@@ -1168,7 +1217,7 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
for (int k = 0; k < sz; k += 8)
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, scr + k));
cg_widen_tagged_store(c, locals_p, dst, src, scr, sz);
cg_widen_tagged_store(c, locals_p, dst, src, D_BP, scr, sz);
int nwords = sz / 8;
for (int k = nwords - 1; k >= 0; k--) {
ins2(c, A_MOVQ, amem(D_BP, scr + k * 8), areg(D_AX));
@@ -1862,63 +1911,41 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *ft = f->type;
Type *fu = (ft && ft->kind == TY_NAMED)
? ft->under : ft;
/* Tagged-union field — full slot rewrite via the
* shared widener so every rhs shape (whole-tagged
* ident or expr with tag-remap, concrete-variant
* widening of str/slice/struct/scalar/void) lands
* the right tag + payload bytes. The pre-#26
* branch synthesised a single tag from
* cg_tag_for_variant and stored only AX at +8, so
* whole-tagged rhs (vt == fu, no concrete tag)
* silently wrote tag 0 and dropped trailing words.
* cg_widen_tagged_store handles every shape by
* branching on the source's resolved type. */
if (fu && fu->kind == TY_TAGGED
&& n->op == TK_ASSIGN) {
int boff = localfind(locals, base->str);
int is_global = (boff == 0 && !via_ptr
&& let_islet(base->str));
int foff = (int)f->offset;
Type *vt = n->rhs ? n->rhs->type : NULL;
int tag = cg_tag_for_variant(fu, vt);
int addr_in_bx = via_ptr;
if (via_ptr)
ins2(c, A_MOVQ, amem(D_BP, boff),
int fsz = (int)fu->size;
if (via_ptr) {
ins2(c, A_MOVQ,
amem(D_BP, boff),
areg(D_BX));
cgexpr(c, n->rhs, locals);
if (is_global)
cg_widen_tagged_store(c, &locals,
fu, n->rhs, D_BX, foff, fsz);
} else if (is_global) {
ins2(c, A_LEAQ,
masym(c, base->str),
areg(D_CX));
if (type_isstr(vt)) {
if (addr_in_bx) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BX, foff + 8));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BX, foff + 16));
} else if (is_global) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_CX, foff + 8));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_CX, foff + 16));
} else {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, boff + foff + 8));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, boff + foff + 16));
}
areg(D_BX));
cg_widen_tagged_store(c, &locals,
fu, n->rhs, D_BX, foff, fsz);
} else {
if (addr_in_bx)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BX, foff + 8));
else if (is_global)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_CX, foff + 8));
else
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, boff + foff + 8));
cg_widen_tagged_store(c, &locals,
fu, n->rhs, D_BP,
boff + foff, fsz);
}
if (addr_in_bx)
ins2(c, A_MOVQ,
aimm(tag < 0 ? 0 : tag),
amem(D_BX, foff + 0));
else if (is_global)
ins2(c, A_MOVQ,
aimm(tag < 0 ? 0 : tag),
amem(D_CX, foff + 0));
else
ins2(c, A_MOVQ,
aimm(tag < 0 ? 0 : tag),
amem(D_BP, boff + foff + 0));
break;
}
int fsz = (int)(f->type ? f->type->size : 8);
@@ -2781,7 +2808,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, scr + k));
cg_widen_tagged_store(c, &locals, esubu,
n->rhs, scr, ssz);
n->rhs, D_BP, scr, ssz);
/* Compute &arr[i] → BX. */
cgexpr(c, n->lhs->rhs, locals);
if (ssz > 1) {
@@ -2878,7 +2905,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
int off = localfind(locals, n->lhs->str);
if (off == 0) break;
cg_widen_tagged_store(c, &locals, lu, n->rhs,
off, (int)lu->size);
D_BP, off, (int)lu->size);
break;
}
}
@@ -3458,7 +3485,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (v_is_tagged) {
cg_widen_tagged_store(c,
&locals, velem,
a, slot, esz);
a, D_BP, slot, esz);
continue;
}
cgexpr(c, a, locals);
@@ -5158,7 +5185,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* literal — field-by-field at slot+8+field_off), str payload,
* and scalar payload (with zero-pad to the slot size). */
if (n->rhs && lu && lu->kind == TY_TAGGED) {
cg_widen_tagged_store(c, locals, lu, n->rhs, off, sz);
cg_widen_tagged_store(c, locals, lu, n->rhs, D_BP, off, sz);
break;
}
/* slice expression initialiser: build a {ptr, len, cap} header
@@ -5272,7 +5299,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
? ft->under : ft;
if (fu && fu->kind == TY_TAGGED) {
cg_widen_tagged_store(c, locals, fu,
f->lhs, off + (int)foff,
f->lhs, D_BP, off + (int)foff,
(int)fu->size);
continue;
}
@@ -5470,7 +5497,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, scr + k));
cg_widen_tagged_store(c, locals, rt,
n->lhs, scr, sz);
n->lhs, D_BP, scr, sz);
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
areg(D_AX));
if (sz > 8)

View File

@@ -5744,7 +5744,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("(BP)\n");
zz += 8;
};
cgwidentaggedstore(c, ptype, arg, scroff, widensz);
cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz);
let pp: i32 = widensz - 8;
for (pp >= 0) {
emitline("\tMOVQ\t");
@@ -6938,6 +6938,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let k: nkind = tnode.kind;
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
if (k == nkind.N_TNAME) {
let nm: str = tnode.str;
if (streq(nm, "str")) { return 16; };
@@ -6957,6 +6958,13 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
};
return 4; // default storage is i32
};
// Type alias to a tagged-union — recurse through aliaslookup
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
// containing struct rather than the 8B default.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) { return fieldsize(c, aliased); };
};
return 8;
};
if (k == nkind.N_TPTR) { return 8; };
@@ -7633,9 +7641,20 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
return false;
};
// cgwidentaggedstore — write tagged-union slot bytes for `src` into the
// slot at BP+slot_off, sized to slot_sz. Mirrors cg_widen_tagged_store
// in cmd/w6c/cgen.c. Branches by source shape:
// cgwidentaggedstore — write tagged-union slot bytes for `src` into
// the slot at `basereg`+slot_off, sized to slot_sz. Mirrors
// cg_widen_tagged_store in cmd/w6c/cgen.c.
//
// `basereg` selects the addressing root:
// - "BP": function-frame slot (let / assign / return / structlit /
// array-elem scratch). Body writes straight to slot_off(BP).
// - else (e.g. "BX" for *struct field, top-level struct LEAQ
// base): pointer-rooted dst. cgexpr inside trashes every GPR,
// so we route through a fresh BP-rooted scratch slot, spill
// basereg before the body, reload after, then word-copy
// scratch → (basereg, slot_off).
//
// Branches by source shape:
// - nullable dst (8B slot): cgexpr → AX → slot+0.
// - tagged src ident: copy slot words, zero-pad, tag-remap.
// - tagged src via AX/DX/CX ABI (call / tagged-arr index): cgexpr,
@@ -7646,7 +7665,53 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
// tag last.
// - str src: tag@+0, ptr@+8, len@+16.
// - scalar src: tag@+0, value@+8.
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node,
basereg: str, slot_off: i32, slot_sz: i32) void = {
if (streq(basereg, "BP")) {
cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz);
return;
};
// Pointer-rooted dst: spill basereg (cgexpr will trash it),
// materialise into a BP-rooted scratch via the BP path, then
// reload basereg and word-copy scratch → caller's slot.
let bspill: i32 = localadd(c, "@tagbase", 8, nil);
emitline("\tMOVQ\t");
emitline(basereg);
emitline(", ");
emitoff(bspill: i64);
emitline("(BP)\n");
let scr: i32 = localadd(c, "@tagscr", slot_sz, nil);
emitline("\tXORQ\tAX, AX\n");
let z: i32 = 0;
for (z < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((scr + z): i64);
emitline("(BP)\n");
z += 8;
};
cgwidentaggedstorebp(c, dst, src, scr, slot_sz);
emitline("\tMOVQ\t");
emitoff(bspill: i64);
emitline("(BP), ");
emitline(basereg);
emitline("\n");
let k: i32 = 0;
for (k < slot_sz) {
emitline("\tMOVQ\t");
emitoff((scr + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg((slot_off + k): i64, basereg);
emitline("\n");
k += 8;
};
};
// cgwidentaggedstorebp — BP-rooted body. Called via cgwidentaggedstore
// for the natural "BP" case and via the wrapper's scratch path for
// pointer-rooted dst. Direct callers exist only in case of future
// inlined uses inside this file; new code should call the wrapper.
fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
let dt: *node = resolvetagged(c, dst);
if (dt == nil) { return; };
// Nullable fold: one 8B word holding the pointer (or 0 for void).
@@ -10793,7 +10858,7 @@ fn cgcall(c: *cgen, n: *node) void = {
let slot: i32 = doff + j * esz;
if (velemtagged) {
cgwidentaggedstore(c, varp.lhs,
aa2, slot, esz);
aa2, "BP", slot, esz);
} else { if (velemstr) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
@@ -11049,7 +11114,7 @@ fn cgassign(c: *cgen, n: *node) void = {
if (istaggedtype(c, lc.tnode)) {
let lsz: i32 = slotsize(c, lc.tnode);
cgwidentaggedstore(c, lc.tnode,
n.rhs, lc.off, lsz);
n.rhs, "BP", lc.off, lsz);
return;
};
};
@@ -11273,7 +11338,7 @@ fn cgassign(c: *cgen, n: *node) void = {
zz += 8;
};
cgwidentaggedstore(c, elemtn, n.rhs,
scroff, slot_sz);
"BP", scroff, slot_sz);
cgexpr(c, idx);
if (slot_sz > 1) {
emitline("\tMOVQ\t$");
@@ -11619,6 +11684,20 @@ fn cgassign(c: *cgen, n: *node) void = {
for (fi != nil) {
let fn_: str = fi.fname;
if (streq(fn_, fld)) {
// Tagged-union field via *struct base — full slot
// rewrite via cgwidentaggedstore basereg="BX". Pre-#26
// fell through to the scalar store and dropped tag
// + payload.
if (n.op == tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
cgwidentaggedstore(c, fi.tnode,
n.rhs, "BX", fi.foff, fsz);
return;
};
// struct-typed field, struct-ident rhs through
// *struct base: cgexpr can't materialise a whole
// struct value, so word-copy from the rhs slot
@@ -11769,6 +11848,17 @@ fn cgassign(c: *cgen, n: *node) void = {
for (fi != nil) {
let fn_: str = fi.fname;
if (streq(fn_, fld)) {
// Tagged-union field in a direct struct local —
// full slot rewrite at (lc.off + fi.foff)(BP)
// via cgwidentaggedstore basereg="BP". Pre-#26
// fell through and dropped tag + payload.
if (n.op == tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
cgwidentaggedstore(c, fi.tnode,
n.rhs, "BP", lc.off + fi.foff, fsz);
return;
};
// struct-typed field, struct-ident rhs:
// word-copy direct, skipping cgexpr (no
// register convention for a whole struct
@@ -12978,8 +13068,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
emitline("(BP)\n");
zz += 8;
};
cgwidentaggedstore(c, c.fnret, rhs, scroff,
rsz);
cgwidentaggedstore(c, c.fnret, rhs, "BP",
scroff, rsz);
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
@@ -13101,7 +13191,7 @@ fn cglet(c: *cgen, n: *node) void = {
// ABI call), struct payload (literal/ident), str payload,
// scalar payload — with tag remap for tagged-subset widening.
if (istaggedtype(c, tn)) {
cgwidentaggedstore(c, tn, rhs, off, sz);
cgwidentaggedstore(c, tn, rhs, "BP", off, sz);
c.lastwasreturn = 0;
return;
};
@@ -14118,6 +14208,61 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
};
};
};
// Tagged-union struct-field write: `s.f = v` or `(*p).f = v`
// where f is a tagged-union field. cgassign delegates to
// cgwidentaggedstore; for pointer-rooted dst the wrapper
// allocates @tagbase (8B) and @tagscr (slot_sz). Both names
// dedup with other tagged scratch users in the same function.
if (n.kind == nkind.N_ASSIGN) {
let alhs: *node = n.lhs;
if (alhs != nil) {
if (alhs.kind == nkind.N_DOT) {
let abase: *node = alhs.lhs;
if (abase != nil) {
if (abase.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, abase.str);
let btn: *node = nil;
if (lc != nil) { btn = lc.tnode; }
else { btn = letvartnode(c, abase.str); };
let isptr: bool = false;
let stype: *node = nil;
if (btn != nil) {
if (btn.kind == nkind.N_TPTR) {
isptr = true;
stype = btn.lhs;
};
if (btn.kind == nkind.N_TNAME) { stype = btn; };
};
if (stype != nil) {
if (stype.kind == nkind.N_TNAME) {
let si: *structinfo = structlookup(c, stype.str);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, alhs.str)) {
if (istaggedtype(c, fi.tnode)) {
if (isptr) {
if (!scanseenmark(c, "@tagbase")) {
total += 8;
};
if (!scanseenmark(c, "@tagscr")) {
total += 24;
};
};
};
fi = nil;
} else {
fi = fi.finext;
};
};
};
};
};
};
};
};
};
};
// Tagged-union return with struct payload or tagged-subset
// source — cgreturn materialises in @tagscr then loads
// AX/DX/CX. Detect via the same rhsstructpayload predicate

View File

@@ -204,6 +204,61 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
};
};
};
// Tagged-union struct-field write: `s.f = v` or `(*p).f = v`
// where f is a tagged-union field. cgassign delegates to
// cgwidentaggedstore; for pointer-rooted dst the wrapper
// allocates @tagbase (8B) and @tagscr (slot_sz). Both names
// dedup with other tagged scratch users in the same function.
if (n.kind == nkind.N_ASSIGN) {
let alhs: *node = n.lhs;
if (alhs != nil) {
if (alhs.kind == nkind.N_DOT) {
let abase: *node = alhs.lhs;
if (abase != nil) {
if (abase.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, abase.str);
let btn: *node = nil;
if (lc != nil) { btn = lc.tnode; }
else { btn = letvartnode(c, abase.str); };
let isptr: bool = false;
let stype: *node = nil;
if (btn != nil) {
if (btn.kind == nkind.N_TPTR) {
isptr = true;
stype = btn.lhs;
};
if (btn.kind == nkind.N_TNAME) { stype = btn; };
};
if (stype != nil) {
if (stype.kind == nkind.N_TNAME) {
let si: *structinfo = structlookup(c, stype.str);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, alhs.str)) {
if (istaggedtype(c, fi.tnode)) {
if (isptr) {
if (!scanseenmark(c, "@tagbase")) {
total += 8;
};
if (!scanseenmark(c, "@tagscr")) {
total += 24;
};
};
};
fi = nil;
} else {
fi = fi.finext;
};
};
};
};
};
};
};
};
};
};
// Tagged-union return with struct payload or tagged-subset
// source — cgreturn materialises in @tagscr then loads
// AX/DX/CX. Detect via the same rhsstructpayload predicate

View File

@@ -2745,7 +2745,7 @@ fn cgcall(c: *cgen, n: *node) void = {
let slot: i32 = doff + j * esz;
if (velemtagged) {
cgwidentaggedstore(c, varp.lhs,
aa2, slot, esz);
aa2, "BP", slot, esz);
} else { if (velemstr) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
@@ -3001,7 +3001,7 @@ fn cgassign(c: *cgen, n: *node) void = {
if (istaggedtype(c, lc.tnode)) {
let lsz: i32 = slotsize(c, lc.tnode);
cgwidentaggedstore(c, lc.tnode,
n.rhs, lc.off, lsz);
n.rhs, "BP", lc.off, lsz);
return;
};
};
@@ -3225,7 +3225,7 @@ fn cgassign(c: *cgen, n: *node) void = {
zz += 8;
};
cgwidentaggedstore(c, elemtn, n.rhs,
scroff, slot_sz);
"BP", scroff, slot_sz);
cgexpr(c, idx);
if (slot_sz > 1) {
emitline("\tMOVQ\t$");
@@ -3571,6 +3571,20 @@ fn cgassign(c: *cgen, n: *node) void = {
for (fi != nil) {
let fn_: str = fi.fname;
if (streq(fn_, fld)) {
// Tagged-union field via *struct base — full slot
// rewrite via cgwidentaggedstore basereg="BX". Pre-#26
// fell through to the scalar store and dropped tag
// + payload.
if (n.op == tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
cgwidentaggedstore(c, fi.tnode,
n.rhs, "BX", fi.foff, fsz);
return;
};
// struct-typed field, struct-ident rhs through
// *struct base: cgexpr can't materialise a whole
// struct value, so word-copy from the rhs slot
@@ -3721,6 +3735,17 @@ fn cgassign(c: *cgen, n: *node) void = {
for (fi != nil) {
let fn_: str = fi.fname;
if (streq(fn_, fld)) {
// Tagged-union field in a direct struct local —
// full slot rewrite at (lc.off + fi.foff)(BP)
// via cgwidentaggedstore basereg="BP". Pre-#26
// fell through and dropped tag + payload.
if (n.op == tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
cgwidentaggedstore(c, fi.tnode,
n.rhs, "BP", lc.off + fi.foff, fsz);
return;
};
// struct-typed field, struct-ident rhs:
// word-copy direct, skipping cgexpr (no
// register convention for a whole struct

View File

@@ -188,8 +188,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
emitline("(BP)\n");
zz += 8;
};
cgwidentaggedstore(c, c.fnret, rhs, scroff,
rsz);
cgwidentaggedstore(c, c.fnret, rhs, "BP",
scroff, rsz);
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
@@ -311,7 +311,7 @@ fn cglet(c: *cgen, n: *node) void = {
// ABI call), struct payload (literal/ident), str payload,
// scalar payload — with tag remap for tagged-subset widening.
if (istaggedtype(c, tn)) {
cgwidentaggedstore(c, tn, rhs, off, sz);
cgwidentaggedstore(c, tn, rhs, "BP", off, sz);
c.lastwasreturn = 0;
return;
};

View File

@@ -174,7 +174,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("(BP)\n");
zz += 8;
};
cgwidentaggedstore(c, ptype, arg, scroff, widensz);
cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz);
let pp: i32 = widensz - 8;
for (pp >= 0) {
emitline("\tMOVQ\t");
@@ -1368,6 +1368,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let k: nkind = tnode.kind;
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
if (k == nkind.N_TNAME) {
let nm: str = tnode.str;
if (streq(nm, "str")) { return 16; };
@@ -1387,6 +1388,13 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
};
return 4; // default storage is i32
};
// Type alias to a tagged-union — recurse through aliaslookup
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
// containing struct rather than the 8B default.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) { return fieldsize(c, aliased); };
};
return 8;
};
if (k == nkind.N_TPTR) { return 8; };
@@ -2063,9 +2071,20 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
return false;
};
// cgwidentaggedstore — write tagged-union slot bytes for `src` into the
// slot at BP+slot_off, sized to slot_sz. Mirrors cg_widen_tagged_store
// in cmd/w6c/cgen.c. Branches by source shape:
// cgwidentaggedstore — write tagged-union slot bytes for `src` into
// the slot at `basereg`+slot_off, sized to slot_sz. Mirrors
// cg_widen_tagged_store in cmd/w6c/cgen.c.
//
// `basereg` selects the addressing root:
// - "BP": function-frame slot (let / assign / return / structlit /
// array-elem scratch). Body writes straight to slot_off(BP).
// - else (e.g. "BX" for *struct field, top-level struct LEAQ
// base): pointer-rooted dst. cgexpr inside trashes every GPR,
// so we route through a fresh BP-rooted scratch slot, spill
// basereg before the body, reload after, then word-copy
// scratch → (basereg, slot_off).
//
// Branches by source shape:
// - nullable dst (8B slot): cgexpr → AX → slot+0.
// - tagged src ident: copy slot words, zero-pad, tag-remap.
// - tagged src via AX/DX/CX ABI (call / tagged-arr index): cgexpr,
@@ -2076,7 +2095,53 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
// tag last.
// - str src: tag@+0, ptr@+8, len@+16.
// - scalar src: tag@+0, value@+8.
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node,
basereg: str, slot_off: i32, slot_sz: i32) void = {
if (streq(basereg, "BP")) {
cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz);
return;
};
// Pointer-rooted dst: spill basereg (cgexpr will trash it),
// materialise into a BP-rooted scratch via the BP path, then
// reload basereg and word-copy scratch → caller's slot.
let bspill: i32 = localadd(c, "@tagbase", 8, nil);
emitline("\tMOVQ\t");
emitline(basereg);
emitline(", ");
emitoff(bspill: i64);
emitline("(BP)\n");
let scr: i32 = localadd(c, "@tagscr", slot_sz, nil);
emitline("\tXORQ\tAX, AX\n");
let z: i32 = 0;
for (z < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((scr + z): i64);
emitline("(BP)\n");
z += 8;
};
cgwidentaggedstorebp(c, dst, src, scr, slot_sz);
emitline("\tMOVQ\t");
emitoff(bspill: i64);
emitline("(BP), ");
emitline(basereg);
emitline("\n");
let k: i32 = 0;
for (k < slot_sz) {
emitline("\tMOVQ\t");
emitoff((scr + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg((slot_off + k): i64, basereg);
emitline("\n");
k += 8;
};
};
// cgwidentaggedstorebp — BP-rooted body. Called via cgwidentaggedstore
// for the natural "BP" case and via the wrapper's scratch path for
// pointer-rooted dst. Direct callers exist only in case of future
// inlined uses inside this file; new code should call the wrapper.
fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
let dt: *node = resolvetagged(c, dst);
if (dt == nil) { return; };
// Nullable fold: one 8B word holding the pointer (or 0 for void).

View File

@@ -5744,7 +5744,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("(BP)\n");
zz += 8;
};
cgwidentaggedstore(c, ptype, arg, scroff, widensz);
cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz);
let pp: i32 = widensz - 8;
for (pp >= 0) {
emitline("\tMOVQ\t");
@@ -6938,6 +6938,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let k: nkind = tnode.kind;
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
if (k == nkind.N_TNAME) {
let nm: str = tnode.str;
if (streq(nm, "str")) { return 16; };
@@ -6957,6 +6958,13 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
};
return 4; // default storage is i32
};
// Type alias to a tagged-union — recurse through aliaslookup
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
// containing struct rather than the 8B default.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) { return fieldsize(c, aliased); };
};
return 8;
};
if (k == nkind.N_TPTR) { return 8; };
@@ -7633,9 +7641,20 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
return false;
};
// cgwidentaggedstore — write tagged-union slot bytes for `src` into the
// slot at BP+slot_off, sized to slot_sz. Mirrors cg_widen_tagged_store
// in cmd/w6c/cgen.c. Branches by source shape:
// cgwidentaggedstore — write tagged-union slot bytes for `src` into
// the slot at `basereg`+slot_off, sized to slot_sz. Mirrors
// cg_widen_tagged_store in cmd/w6c/cgen.c.
//
// `basereg` selects the addressing root:
// - "BP": function-frame slot (let / assign / return / structlit /
// array-elem scratch). Body writes straight to slot_off(BP).
// - else (e.g. "BX" for *struct field, top-level struct LEAQ
// base): pointer-rooted dst. cgexpr inside trashes every GPR,
// so we route through a fresh BP-rooted scratch slot, spill
// basereg before the body, reload after, then word-copy
// scratch → (basereg, slot_off).
//
// Branches by source shape:
// - nullable dst (8B slot): cgexpr → AX → slot+0.
// - tagged src ident: copy slot words, zero-pad, tag-remap.
// - tagged src via AX/DX/CX ABI (call / tagged-arr index): cgexpr,
@@ -7646,7 +7665,53 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
// tag last.
// - str src: tag@+0, ptr@+8, len@+16.
// - scalar src: tag@+0, value@+8.
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node,
basereg: str, slot_off: i32, slot_sz: i32) void = {
if (streq(basereg, "BP")) {
cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz);
return;
};
// Pointer-rooted dst: spill basereg (cgexpr will trash it),
// materialise into a BP-rooted scratch via the BP path, then
// reload basereg and word-copy scratch → caller's slot.
let bspill: i32 = localadd(c, "@tagbase", 8, nil);
emitline("\tMOVQ\t");
emitline(basereg);
emitline(", ");
emitoff(bspill: i64);
emitline("(BP)\n");
let scr: i32 = localadd(c, "@tagscr", slot_sz, nil);
emitline("\tXORQ\tAX, AX\n");
let z: i32 = 0;
for (z < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((scr + z): i64);
emitline("(BP)\n");
z += 8;
};
cgwidentaggedstorebp(c, dst, src, scr, slot_sz);
emitline("\tMOVQ\t");
emitoff(bspill: i64);
emitline("(BP), ");
emitline(basereg);
emitline("\n");
let k: i32 = 0;
for (k < slot_sz) {
emitline("\tMOVQ\t");
emitoff((scr + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg((slot_off + k): i64, basereg);
emitline("\n");
k += 8;
};
};
// cgwidentaggedstorebp — BP-rooted body. Called via cgwidentaggedstore
// for the natural "BP" case and via the wrapper's scratch path for
// pointer-rooted dst. Direct callers exist only in case of future
// inlined uses inside this file; new code should call the wrapper.
fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
let dt: *node = resolvetagged(c, dst);
if (dt == nil) { return; };
// Nullable fold: one 8B word holding the pointer (or 0 for void).
@@ -10793,7 +10858,7 @@ fn cgcall(c: *cgen, n: *node) void = {
let slot: i32 = doff + j * esz;
if (velemtagged) {
cgwidentaggedstore(c, varp.lhs,
aa2, slot, esz);
aa2, "BP", slot, esz);
} else { if (velemstr) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
@@ -11049,7 +11114,7 @@ fn cgassign(c: *cgen, n: *node) void = {
if (istaggedtype(c, lc.tnode)) {
let lsz: i32 = slotsize(c, lc.tnode);
cgwidentaggedstore(c, lc.tnode,
n.rhs, lc.off, lsz);
n.rhs, "BP", lc.off, lsz);
return;
};
};
@@ -11273,7 +11338,7 @@ fn cgassign(c: *cgen, n: *node) void = {
zz += 8;
};
cgwidentaggedstore(c, elemtn, n.rhs,
scroff, slot_sz);
"BP", scroff, slot_sz);
cgexpr(c, idx);
if (slot_sz > 1) {
emitline("\tMOVQ\t$");
@@ -11619,6 +11684,20 @@ fn cgassign(c: *cgen, n: *node) void = {
for (fi != nil) {
let fn_: str = fi.fname;
if (streq(fn_, fld)) {
// Tagged-union field via *struct base — full slot
// rewrite via cgwidentaggedstore basereg="BX". Pre-#26
// fell through to the scalar store and dropped tag
// + payload.
if (n.op == tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
cgwidentaggedstore(c, fi.tnode,
n.rhs, "BX", fi.foff, fsz);
return;
};
// struct-typed field, struct-ident rhs through
// *struct base: cgexpr can't materialise a whole
// struct value, so word-copy from the rhs slot
@@ -11769,6 +11848,17 @@ fn cgassign(c: *cgen, n: *node) void = {
for (fi != nil) {
let fn_: str = fi.fname;
if (streq(fn_, fld)) {
// Tagged-union field in a direct struct local —
// full slot rewrite at (lc.off + fi.foff)(BP)
// via cgwidentaggedstore basereg="BP". Pre-#26
// fell through and dropped tag + payload.
if (n.op == tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
cgwidentaggedstore(c, fi.tnode,
n.rhs, "BP", lc.off + fi.foff, fsz);
return;
};
// struct-typed field, struct-ident rhs:
// word-copy direct, skipping cgexpr (no
// register convention for a whole struct
@@ -12978,8 +13068,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
emitline("(BP)\n");
zz += 8;
};
cgwidentaggedstore(c, c.fnret, rhs, scroff,
rsz);
cgwidentaggedstore(c, c.fnret, rhs, "BP",
scroff, rsz);
emitline("\tMOVQ\t");
emitoff(scroff: i64);
emitline("(BP), AX\n");
@@ -13101,7 +13191,7 @@ fn cglet(c: *cgen, n: *node) void = {
// ABI call), struct payload (literal/ident), str payload,
// scalar payload — with tag remap for tagged-subset widening.
if (istaggedtype(c, tn)) {
cgwidentaggedstore(c, tn, rhs, off, sz);
cgwidentaggedstore(c, tn, rhs, "BP", off, sz);
c.lastwasreturn = 0;
return;
};
@@ -14118,6 +14208,61 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
};
};
};
// Tagged-union struct-field write: `s.f = v` or `(*p).f = v`
// where f is a tagged-union field. cgassign delegates to
// cgwidentaggedstore; for pointer-rooted dst the wrapper
// allocates @tagbase (8B) and @tagscr (slot_sz). Both names
// dedup with other tagged scratch users in the same function.
if (n.kind == nkind.N_ASSIGN) {
let alhs: *node = n.lhs;
if (alhs != nil) {
if (alhs.kind == nkind.N_DOT) {
let abase: *node = alhs.lhs;
if (abase != nil) {
if (abase.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, abase.str);
let btn: *node = nil;
if (lc != nil) { btn = lc.tnode; }
else { btn = letvartnode(c, abase.str); };
let isptr: bool = false;
let stype: *node = nil;
if (btn != nil) {
if (btn.kind == nkind.N_TPTR) {
isptr = true;
stype = btn.lhs;
};
if (btn.kind == nkind.N_TNAME) { stype = btn; };
};
if (stype != nil) {
if (stype.kind == nkind.N_TNAME) {
let si: *structinfo = structlookup(c, stype.str);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, alhs.str)) {
if (istaggedtype(c, fi.tnode)) {
if (isptr) {
if (!scanseenmark(c, "@tagbase")) {
total += 8;
};
if (!scanseenmark(c, "@tagscr")) {
total += 24;
};
};
};
fi = nil;
} else {
fi = fi.finext;
};
};
};
};
};
};
};
};
};
};
// Tagged-union return with struct payload or tagged-subset
// source — cgreturn materialises in @tagscr then loads
// AX/DX/CX. Detect via the same rhsstructpayload predicate

View File

@@ -423,6 +423,72 @@ static const struct row rows[] = {
" return 42;\n"
"};\n",
42 },
/* Whole-tagged rhs to struct tagged-union field (task #26). cgen
* N_ASSIGN had a TY_TAGGED branch that called cg_tag_for_variant
* with the rhs type — when the rhs was the tagged union itself
* (not a concrete variant), the function returned -1 and the
* branch wrote literal 0 as the tag plus only AX as the payload,
* dropping the original tag and trailing payload words. Fixed
* by routing through cg_widen_tagged_store, which copies all slot
* words from the source's tagged slot and runs cg_widen_tag_remap.
*
* Verification: the write must not bleed into the trailing `mark`
* field. Direct struct local. Reading x.e back via match is a
* separate code path (gated on a wwstage read-side bug for
* tagged N_DOT — filed below as a follow-up); these rows pin the
* write-side bug and let mark act as a canary for stray stores
* past the field boundary. */
{ "tagged_field_value_write_local_mark_canary",
"type ev = (i64 | i32);\n"
"type holder = struct { e: ev, mark: i32 };\n"
"fn main() i32 = {\n"
" let v: ev = 33: i32;\n"
" let x: holder;\n"
" x.mark = 0x3a3a3a3a;\n"
" x.e = v;\n"
" if (x.mark != 0x3a3a3a3a) { return 1; };\n"
" return 42;\n"
"};\n",
42 },
/* Same shape, concrete-variant str widening. The str payload
* occupies +8/+16 (ptr/len); a missing store to +16 (the pre-#26
* shape that wrote only AX) would leave .len at whatever the
* neighbouring memory held. mark canary at +24 catches a stray
* AX-only path that wrote past 16B into the mark slot. */
{ "tagged_field_value_write_str_variant_mark_canary",
"type ev = (i32 | str);\n"
"type holder = struct { e: ev, mark: i32 };\n"
"fn main() i32 = {\n"
" let x: holder;\n"
" x.mark = 0x5e5e5e5e;\n"
" x.e = (\"hello\": ev);\n"
" if (x.mark != 0x5e5e5e5e) { return 1; };\n"
" return 42;\n"
"};\n",
42 },
/* Pointer-rooted dst — `(*p).e = v` exercises the base-reg
* parameterisation added to cg_widen_tagged_store in #26. The
* wrapper spills BX (the *struct pointer), routes the body
* through a BP-rooted scratch, reloads BX, and word-copies the
* scratch to (BX, foff). Without that path the via_ptr arm of
* the new TY_TAGGED branch would either trample BX during cgexpr
* or write to a stale slot address. mark canary at +24 catches
* a stray spill that wrote past the e field. */
{ "tagged_field_value_write_via_ptr_mark_canary",
"type ev = (i64 | i32);\n"
"type holder = struct { e: ev, mark: i32 };\n"
"fn fill(h: *holder) void = {\n"
" let v: ev = 33: i32;\n"
" h.mark = 0x77777777;\n"
" h.e = v;\n"
"};\n"
"fn main() i32 = {\n"
" let x: holder;\n"
" fill(&x);\n"
" if (x.mark != 0x77777777) { return 1; };\n"
" return 42;\n"
"};\n",
42 },
};
static int