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

@@ -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).