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)