w6c+selfhost: tagged-arr element ABI + full selfhost mirror
Closes the remaining tagged-union gaps after the prior two commits:
1. Tagged element in an array/slice (cstage). N_INDEX load now reads
slot words into AX/DX/CX, matching the tagged-return ABI so match
/ call-arg / let-init paths consume `arr[i]` uniformly. N_INDEX
store routes through a scratch slot + cg_widen_tagged_store +
byte-copy to &arr[i], so the full widening machinery (scalar /
str / struct payload / tagged subset / nullable fold) lights up
for element writes too.
2. Selfhost mirror — the cgen widen helpers (struct payload,
tagged-subset, spread-flatten) C cgen has had for two commits
finally land in selfhost:
cgwidentaggedstore — single writer for nullable / tagged ident /
tagged via AX:DX:CX / struct (lit + ident) /
str / scalar source shapes.
cgwidentagremap — CMPQ-chain tag remap for variant-subset.
rhsstructpayload — struct-name predicate; filters `!void` /
`!i32` aliases that share N_STRUCTLIT shape
but aren't structs.
rhstaggedident,
rhstaggedabicall — source-shape predicates.
flatvariantidx — spread-aware variant index lookup. Walks
`(...inner | T)` entries by resolving the
alias and inlining the inner's variants so
wwstage's tag order matches the check.c
flattening cstage does at type resolution.
cglet tagged init, cgassign tagged-ident reassign, cgreturn struct
/ subset payload, pushargsrev struct payload, cgindex tagged
element load, cgassign N_INDEX tagged element store all delegate
to these. cgmatch picks up scrutt from N_INDEX bases (element
type) and uses flatvariantidx for case dispatch.
3. Selfhost frame accounting: scanlocals reserves a 24B @tagscr slot
when the body contains a tagged-arr store, a struct-payload
tagged return, or a struct-payload call arg — dedup'd via
scanseenmark so multiple sites share one slot. N_LET stubs now
carry tnode so walk-time type checks see the array element type.
slotsize TARRAY learned to size tagged / struct / ptr / aliased
elements (was 8B-default for anything not N_TNAME-primitive,
undersizing tagged-element arrays).
Scalar / str call-arg widening keeps its direct-push fast path
(no scratch), so wwstage's asm on selfhost source remains
byte-identical to cstage's — 993/995 still pass.
700_e2e: 9 new rows — scalar/str/struct/subset/nullable variants in
arrays and slices, plus pass-arg / let-init / return / match shapes.
This commit is contained in:
@@ -38,6 +38,16 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
||||
total += sz;
|
||||
};
|
||||
// Carry the let's tnode into the stub so scanlocals can
|
||||
// dispatch on type later in the walk — e.g. detecting
|
||||
// `arr[i] = ...` where arr is a tagged-element array,
|
||||
// which needs an @tagscr scratch slot reservation.
|
||||
let stub: *local = localfindnode(c, n.str);
|
||||
if (stub != nil) {
|
||||
if (stub.tnode == nil) {
|
||||
if (n.lhs != nil) { stub.tnode = n.lhs; };
|
||||
};
|
||||
};
|
||||
};
|
||||
// Multi-let from a tuple-returning call: each binding's size
|
||||
// comes from its annotated type (l.lhs) when present, else from
|
||||
@@ -145,6 +155,128 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-arr/slice index store needs a 24B scratch slot
|
||||
// (`@tagscr`) for cgwidentaggedstore to materialise the source
|
||||
// in before copying to the element address. Reserved once per
|
||||
// function (dedup'd via scanseenmark) regardless of how many
|
||||
// tagged-arr stores the body contains.
|
||||
if (n.kind == nkind.N_ASSIGN) {
|
||||
let alhs: *node = n.lhs;
|
||||
if (alhs != nil) {
|
||||
if (alhs.kind == nkind.N_INDEX) {
|
||||
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); };
|
||||
if (btn != nil) {
|
||||
let bk: nkind = btn.kind;
|
||||
let etn: *node = nil;
|
||||
if (bk == nkind.N_TARRAY) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { etn = btn.lhs; };
|
||||
if (etn != nil) {
|
||||
if (istaggedtype(c, etn)) {
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// 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
|
||||
// the cgen uses, so we only reserve when the cgen will
|
||||
// actually emit a scratch-using path. `!void` / `!i32`
|
||||
// aliases share N_STRUCTLIT shape but resolve to
|
||||
// non-struct types — they fall through to scalar/str and
|
||||
// don't need scratch.
|
||||
if (n.kind == nkind.N_RETURN) {
|
||||
if (c.fnret != nil) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
if (!isnullabletype(c.fnret)) {
|
||||
let rhs: *node = n.lhs;
|
||||
let needs: bool = false;
|
||||
if (rhs != nil) {
|
||||
let sn: str = rhsstructpayload(c, rhs);
|
||||
if (sn.len > 0) { needs = true; };
|
||||
if (rhs.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, rhs.str);
|
||||
if (lc != nil) {
|
||||
if (istaggedtype(c, lc.tnode)) {
|
||||
needs = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (needs) {
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Call-site struct-payload widening uses @tagscr — when the
|
||||
// arg is a struct literal/ident and the callee's param is
|
||||
// tagged, pushargsrev materialises in scratch and pushes.
|
||||
// Scalar / str args take the direct-push fast path (no
|
||||
// scratch). Tagged-typed ident args also skip widening (the
|
||||
// slot is already laid out, so pushargsrev pushes slot words
|
||||
// directly). Both fast paths agree with C cgen bytewise, so
|
||||
// only struct-payload sites get a scratch reservation.
|
||||
if (n.kind == nkind.N_CALL) {
|
||||
let callee: *node = n.lhs;
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
};
|
||||
if (cnm.len > 0) {
|
||||
let ps: *node = fnparamslookup(c, cnm);
|
||||
let a: *node = n.list;
|
||||
for (a != nil) {
|
||||
if (ps == nil) { a = nil; }
|
||||
else {
|
||||
if (ps.kind == nkind.N_PARAM) {
|
||||
let pt: *node = ps.lhs;
|
||||
if (istaggedtype(c, pt)) {
|
||||
if (!isnullabletype(pt)) {
|
||||
let sn: str = rhsstructpayload(c, a);
|
||||
if (sn.len > 0) {
|
||||
let isidentstruct: bool = false;
|
||||
if (a.kind == nkind.N_IDENT) {
|
||||
// Struct ident as
|
||||
// tagged arg — pushargsrev
|
||||
// still routes through the
|
||||
// scratch path.
|
||||
isidentstruct = true;
|
||||
};
|
||||
let _u: bool = isidentstruct;
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (a != nil) {
|
||||
a = a.next;
|
||||
ps = ps.next;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.lhs != nil) { total += scanlocals(c, n.lhs); };
|
||||
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
||||
if (n.cond != nil) { total += scanlocals(c, n.cond); };
|
||||
|
||||
@@ -112,7 +112,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
// tagged-union type expression `tagged`. -1 if `tagged` isn't an
|
||||
// nkind.N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch
|
||||
// does inline; pulled out so `is` / `as` can reuse it.
|
||||
fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
||||
fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (vt == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
@@ -120,16 +120,7 @@ fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
||||
want.ptr = nil; want.len = 0;
|
||||
if (vt.kind == nkind.N_TNAME) { want = vt.str; };
|
||||
if (want.len == 0) { return -1; };
|
||||
let v: *node = tagged.list;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (streq(v.str, want)) { return idx; };
|
||||
};
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
return -1;
|
||||
return flatvariantidx(c, tagged, want);
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
@@ -246,7 +237,7 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||
if (want < 0) { want = 0; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
@@ -334,7 +325,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||
if (want < 0) { want = 0; };
|
||||
let okl: str = mklabel(c, "asrt_ok");
|
||||
emitline("\tMOVQ\t");
|
||||
@@ -566,6 +557,43 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
esz = indexbaseesz(c, base);
|
||||
};};
|
||||
};
|
||||
// Tagged-union element: load slot words into (AX=tag, DX=val0,
|
||||
// CX=val1) matching the tagged-return ABI so call-arg / let /
|
||||
// match consumers see the same shape as a tagged-returning fn.
|
||||
// Slot size = esz (8/16/24); nullable folded element is one
|
||||
// word, which the fallthrough below handles via MOVQ AX.
|
||||
let elem_tagged: bool = false;
|
||||
let elem_slot_sz: i32 = esz;
|
||||
if (base != nil) {
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let bl: *local = baselocal;
|
||||
let etn: *node = nil;
|
||||
if (bl != nil) {
|
||||
let btn: *node = bl.tnode;
|
||||
if (btn != nil) {
|
||||
let bk: nkind = btn.kind;
|
||||
if (bk == nkind.N_TARRAY) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { etn = btn.lhs; };
|
||||
};
|
||||
} else {
|
||||
let tn: *node = letvartnode(c, base.str);
|
||||
if (tn != nil) {
|
||||
let bk: nkind = tn.kind;
|
||||
if (bk == nkind.N_TARRAY) { etn = tn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { etn = tn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { etn = tn.lhs; };
|
||||
};
|
||||
};
|
||||
if (istaggedtype(c, etn)) {
|
||||
if (!isnullabletype(etn)) {
|
||||
elem_tagged = true;
|
||||
elem_slot_sz = slotsize(c, etn);
|
||||
esz = elem_slot_sz;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -584,6 +612,16 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (elem_tagged) {
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
if (elem_slot_sz > 8) {
|
||||
emitline("\tMOVQ\t8(BX), DX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(BX), AX\n");
|
||||
return;
|
||||
};
|
||||
if (esz == 16) {
|
||||
emitline("\tMOVQ\t8(BX), CX\n");
|
||||
emitline("\tMOVQ\t(BX), AX\n");
|
||||
@@ -612,6 +650,16 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (elem_tagged) {
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
if (elem_slot_sz > 8) {
|
||||
emitline("\tMOVQ\t8(BX), DX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(BX), AX\n");
|
||||
return;
|
||||
};
|
||||
// str element (16B): load (ptr, len) into (AX, BX) so
|
||||
// the value flows through the str-rhs convention.
|
||||
if (esz == 16) {
|
||||
@@ -633,6 +681,19 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, base);
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tADDQ\tBX, AX\n");
|
||||
if (elem_tagged) {
|
||||
// AX holds the element address. Copy to BX (loading slot+0
|
||||
// into AX clobbers it), then read slot words.
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
if (elem_slot_sz > 8) {
|
||||
emitline("\tMOVQ\t8(BX), DX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(BX), AX\n");
|
||||
return;
|
||||
};
|
||||
if (esz == 16) {
|
||||
emitline("\tMOVQ\t8(AX), BX\n");
|
||||
emitline("\tMOVQ\t(AX), AX\n");
|
||||
@@ -747,15 +808,16 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, ?, etc.). Spill into a
|
||||
// 24B `@match_spill` scratch slot and dispatch off it.
|
||||
// Tagged returns follow the AX:DX:CX convention, so store
|
||||
// all three words at +0/+8/+16; nullable returns are
|
||||
// single-word (AX = ptr) and only read +0, so the extra
|
||||
// stores are harmless. For N_CALL we recover the return
|
||||
// type via fnretlookup so nullable dispatch can pick the
|
||||
// pointer-vs-null discriminator. Mirrors C cgen's
|
||||
// @match_spill path in cmd/w6c/cgen.c N_MATCH.
|
||||
// Non-ident scrutinee (call result, arr[i], ?, etc.).
|
||||
// Spill into a 24B `@match_spill` scratch slot and
|
||||
// dispatch off it. Tagged returns (N_CALL) follow the
|
||||
// AX:DX:CX convention; tagged-element loads (N_INDEX)
|
||||
// after the cgindex fix produce the same triple.
|
||||
// Nullable returns are single-word (AX = ptr); only +0
|
||||
// is read, so the extra stores are harmless. We
|
||||
// recover the scrutinee type from fnretlookup (N_CALL)
|
||||
// or the base local's array element type (N_INDEX) so
|
||||
// dispatch can compute variant indices.
|
||||
scrutoff = localalloc(c, "@match_spill", 24, nil);
|
||||
if (scrut.kind == nkind.N_CALL) {
|
||||
let callee: *node = scrut.lhs;
|
||||
@@ -770,6 +832,27 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
if (scrut.kind == nkind.N_INDEX) {
|
||||
let ibase: *node = scrut.lhs;
|
||||
if (ibase != nil) {
|
||||
if (ibase.kind == nkind.N_IDENT) {
|
||||
let bl: *local = localfindnode(c, ibase.str);
|
||||
let btn: *node = nil;
|
||||
if (bl != nil) { btn = bl.tnode; }
|
||||
else { btn = letvartnode(c, ibase.str); };
|
||||
if (btn != nil) {
|
||||
let bk: nkind = btn.kind;
|
||||
let etn: *node = nil;
|
||||
if (bk == nkind.N_TARRAY) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { etn = btn.lhs; };
|
||||
if (etn != nil) {
|
||||
scrutt = resolvetagged(c, etn);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, scrut);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
@@ -832,23 +915,8 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let patname: str;
|
||||
patname.ptr = nil; patname.len = 0;
|
||||
if (pat.kind == nkind.N_TNAME) { patname = pat.str; };
|
||||
let v: *node = scrutt.list;
|
||||
let idx: i32 = 0;
|
||||
let found: bool = false;
|
||||
for (v != nil) {
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.str, patname)) {
|
||||
want = idx;
|
||||
found = true;
|
||||
v = nil;
|
||||
};
|
||||
};
|
||||
if (v != nil) {
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
if (!found) { want = 0; };
|
||||
let r: i32 = flatvariantidx(c, scrutt, patname);
|
||||
if (r >= 0) { want = r; };
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
@@ -2156,6 +2224,25 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union local reassignment: `r = expr;` where r has a
|
||||
// tagged-union type. Delegate to cgwidentaggedstore (same path
|
||||
// as cglet's tagged-init). Covers nullable fold, tagged source,
|
||||
// struct payload, str payload, scalar payload, with tag remap.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
if (istaggedtype(c, lc.tnode)) {
|
||||
let lsz: i32 = slotsize(c, lc.tnode);
|
||||
cgwidentaggedstore(c, lc.tnode,
|
||||
n.rhs, lc.off, lsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// `*p = v` — deref-assign. Element width comes from the
|
||||
// pointer's declared type. Mirrors C cgen: eval rhs (AX,
|
||||
// and BX if str), push, eval pointer, pop value, store.
|
||||
@@ -2256,12 +2343,20 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let isglobalptr: bool = false;
|
||||
let globalname: str;
|
||||
globalname.ptr = nil; globalname.len = 0;
|
||||
let elemtn: *node = nil;
|
||||
if (base != nil) {
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let bn: str = base.str;
|
||||
baselocal = localfindnode(c, bn);
|
||||
if (baselocal != nil) {
|
||||
esz = elemsizeof(baselocal.tnode);
|
||||
let btn: *node = baselocal.tnode;
|
||||
if (btn != nil) {
|
||||
let bk: nkind = btn.kind;
|
||||
if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
|
||||
};
|
||||
} else {
|
||||
let tn: *node = letvartnode(c, bn);
|
||||
if (tn != nil) {
|
||||
@@ -2269,11 +2364,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
isglobalarr = true;
|
||||
globalname = bn;
|
||||
esz = elemsizeof(tn);
|
||||
elemtn = tn.lhs;
|
||||
};
|
||||
if (tn.kind == nkind.N_TPTR) {
|
||||
isglobalptr = true;
|
||||
globalname = bn;
|
||||
esz = elemsizeof(tn);
|
||||
elemtn = tn.lhs;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -2281,6 +2378,80 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
esz = indexbaseesz(c, base);
|
||||
};};
|
||||
};
|
||||
// Tagged-union element: materialize source in a shared
|
||||
// scratch slot via cgwidentaggedstore (handles struct /
|
||||
// str / scalar / subset / nullable variants uniformly),
|
||||
// then compute &arr[i] and byte-copy. The scratch
|
||||
// (@tagscr) is reused across all tagged-arr stores in
|
||||
// the function and counted once in scanlocals.
|
||||
if (elemtn != nil) {
|
||||
if (istaggedtype(c, elemtn)) {
|
||||
let slot_sz: i32 = slotsize(c, elemtn);
|
||||
let scroff: i32 = localadd(c, "@tagscr",
|
||||
24, nil);
|
||||
// Pre-zero scratch (matches push helper).
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zz: i32 = 0;
|
||||
for (zz < slot_sz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((scroff + zz): i64);
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, elemtn, n.rhs,
|
||||
scroff, slot_sz);
|
||||
cgexpr(c, idx);
|
||||
if (slot_sz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(slot_sz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (isglobalarr) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, globalname);
|
||||
emitline("(SB), BX\n");
|
||||
} else { if (isglobalptr) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, globalname);
|
||||
emitline("(SB), BX\n");
|
||||
} else { if (baselocal != nil) {
|
||||
let tn: *node = baselocal.tnode;
|
||||
let isarr: bool = false;
|
||||
if (tn != nil) {
|
||||
if (tn.kind == nkind.N_TARRAY) {
|
||||
isarr = true;
|
||||
};
|
||||
};
|
||||
if (isarr) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
};};};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
let cc: i32 = 0;
|
||||
for (cc < slot_sz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scroff + cc): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(cc: i64);
|
||||
emitline("(BX)\n");
|
||||
cc += 8;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.rhs); // value → AX
|
||||
if (esz == 16) { emitline("\tPUSHQ\tBX\n"); };
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
|
||||
@@ -162,6 +162,53 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Struct payload or tagged-subset return — materialise
|
||||
// the widened value in scratch via cgwidentaggedstore
|
||||
// (handles tag remap and zero pad), then load AX/DX/CX
|
||||
// from the slot.
|
||||
let needswiden: bool = false;
|
||||
if (!isnullabletype(c.fnret)) {
|
||||
if (!forwardtagged) {
|
||||
let sname: str = rhsstructpayload(c, rhs);
|
||||
if (sname.len > 0) { needswiden = true; };
|
||||
if (rhstaggedident(c, rhs) != nil) {
|
||||
needswiden = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (needswiden) {
|
||||
let rsz: i32 = slotsize(c, c.fnret);
|
||||
let scroff: i32 = localadd(c, "@tagscr",
|
||||
24, nil);
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zz: i32 = 0;
|
||||
for (zz < rsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((scroff + zz): i64);
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, c.fnret, rhs, scroff,
|
||||
rsz);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scroff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
if (rsz > 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scroff + 8): i64);
|
||||
emitline("(BP), DX\n");
|
||||
};
|
||||
if (rsz > 16) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scroff + 16): i64);
|
||||
emitline("(BP), CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
if (isnullabletype(c.fnret)) {
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
@@ -247,75 +294,12 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
let off: i32 = localadd(c, nm, sz, tn);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
// Tagged-union init: `let r: (T | E) = expr;`.
|
||||
// - If rhs is a CALL to a fn returning tagged-union,
|
||||
// the result is already in (AX=tag, DX=v0, CX=v1);
|
||||
// just spill all three.
|
||||
// - Otherwise rhs is a bare variant value: pack tag +
|
||||
// value(s).
|
||||
// Tagged-union init: delegate to cgwidentaggedstore, which
|
||||
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||
// ABI call), struct payload (literal/ident), str payload,
|
||||
// scalar payload — with tag remap for tagged-subset widening.
|
||||
if (istaggedtype(c, tn)) {
|
||||
let nullable: bool = isnullabletype(tn);
|
||||
let rhsreturnstagged: bool = false;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let calleename: str;
|
||||
calleename.ptr = nil; calleename.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { calleename = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(c, rt)) { rhsreturnstagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
if (nullable) {
|
||||
// Slot is one 8B word; AX is the pointer
|
||||
// (or 0 for null/void). Same path whether
|
||||
// the rhs is a call or a bare variant.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
if (rhsreturnstagged) {
|
||||
// Spill size/8 registers (tag + value words).
|
||||
// Slots smaller than 24 don't carry a CX word.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
if (sz > 16) {
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
let tagidx: i32 = taggedvariantindex(c, tn, rhs);
|
||||
if (tagidx < 0) { tagidx = 0; };
|
||||
if (nodeisstr(c, rhs)) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(tagidx: i64);
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
cgwidentaggedstore(c, tn, rhs, off, sz);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -72,6 +72,34 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
return rest + 1;
|
||||
};
|
||||
if (widensz > 0) {
|
||||
// Struct-payload widening into a tagged-union param uses
|
||||
// @tagscr (zero + cgwidentaggedstore writes fields + tag,
|
||||
// then push slot words high → low). Scalar / str go via
|
||||
// the direct push fast path below — keeps wwstage's asm
|
||||
// byte-identical to cstage for selfhost source.
|
||||
let pname: str = rhsstructpayload(c, arg);
|
||||
if (pname.len > 0) {
|
||||
let ptype: *node = param.lhs;
|
||||
let scroff: i32 = localadd(c, "@tagscr", 24, nil);
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zz: i32 = 0;
|
||||
for (zz < widensz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((scroff + zz): i64);
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, ptype, arg, scroff, widensz);
|
||||
let pp: i32 = widensz - 8;
|
||||
for (pp >= 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scroff + pp): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
pp -= 8;
|
||||
};
|
||||
return rest + widensz / 8;
|
||||
};
|
||||
cgexpr(c, arg);
|
||||
if (nodeisstr(c, arg)) {
|
||||
// slot 24: [+0]=tag,[+8]=ptr,[+16]=len. Push high→low
|
||||
@@ -955,8 +983,23 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let en: str = elemn.str;
|
||||
let ps: i32 = primsize(en);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
if (ps > 0) { esz = ps; }
|
||||
else {
|
||||
// Named struct / aliased type: size off
|
||||
// the structinfo if present.
|
||||
let si: *structinfo = structlookup(c, en);
|
||||
if (si != nil) { esz = si.totsize; };
|
||||
};
|
||||
} else { if (elemn.kind == nkind.N_TTAGGED) {
|
||||
// Tagged-union element: full slot (8 tag +
|
||||
// padded max payload). Matches C cgen's
|
||||
// resolve_type for `[N]TAGGED`.
|
||||
esz = slotsize(c, elemn);
|
||||
} else { if (elemn.kind == nkind.N_TPTR) {
|
||||
esz = 8;
|
||||
} else { if (elemn.kind == nkind.N_TSTRUCT) {
|
||||
esz = slotsize(c, elemn);
|
||||
}; }; }; };
|
||||
};
|
||||
return (esz: i64 * elen): i32;
|
||||
};
|
||||
@@ -1424,21 +1467,40 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
|
||||
if (rhs == nil) { return -1; };
|
||||
let wantname: str = rhstargetname(c, rhs);
|
||||
if (wantname.len > 0) {
|
||||
let v: *node = tagged.list;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.str, wantname)) { return idx; };
|
||||
};
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
let r: i32 = flatvariantidx(c, tagged, wantname);
|
||||
if (r >= 0) { return r; };
|
||||
};
|
||||
// Fallback: by str-shape (resolves aliases).
|
||||
// Fallback: by str-shape (resolves aliases). Walks the
|
||||
// spread-flattened variant list so a `(...inner | str)` outer
|
||||
// agrees with the (i32 | str) inner's str position.
|
||||
let wantstr: bool = nodeisstr(c, rhs);
|
||||
let v: *node = tagged.list;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
if (isspread) {
|
||||
let inner: *node = v;
|
||||
if (inner.kind == nkind.N_TNAME) {
|
||||
let a: *node = aliaslookup(c, inner.str);
|
||||
if (a != nil) { inner = a; };
|
||||
};
|
||||
if (inner != nil) {
|
||||
if (inner.kind == nkind.N_TTAGGED) {
|
||||
let iv: *node = inner.list;
|
||||
for (iv != nil) {
|
||||
let ivisstr: bool = false;
|
||||
if (iv.kind == nkind.N_TNAME) {
|
||||
if (isstrtype(c, iv)) { ivisstr = true; };
|
||||
};
|
||||
if (ivisstr == wantstr) { return idx; };
|
||||
iv = iv.next;
|
||||
idx += 1;
|
||||
};
|
||||
v = v.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
};
|
||||
let visstr: bool = false;
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (isstrtype(c, v)) { visstr = true; };
|
||||
@@ -1449,3 +1511,404 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
|
||||
// flatvariantidx — walk `tagged`'s variant list (with spread `...inner`
|
||||
// expansion) and return the flat 0-based index where `want` matches.
|
||||
// Mirrors check.c's spread flatten at type resolution: an outer
|
||||
// `(...inner | T)` has the inner's variants inlined in declaration
|
||||
// order, so the tag indices stay in sync between cstage (which
|
||||
// resolves types upfront) and wwstage (which doesn't). Returns -1 if
|
||||
// no variant matches.
|
||||
fn flatvariantidx(c: *cgen, tagged: *node, want: str) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
if (want.len == 0) { return -1; };
|
||||
let v: *node = tagged.list;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
if (isspread) {
|
||||
let inner: *node = v;
|
||||
if (inner.kind == nkind.N_TNAME) {
|
||||
let a: *node = aliaslookup(c, inner.str);
|
||||
if (a != nil) { inner = a; };
|
||||
};
|
||||
if (inner != nil) {
|
||||
if (inner.kind == nkind.N_TTAGGED) {
|
||||
let iv: *node = inner.list;
|
||||
for (iv != nil) {
|
||||
if (iv.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(iv.str, want)) {
|
||||
return idx;
|
||||
};
|
||||
};
|
||||
iv = iv.next;
|
||||
idx += 1;
|
||||
};
|
||||
v = v.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.str, want)) { return idx; };
|
||||
};
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
|
||||
// cgwidentagremap — when widening from one tagged union to a wider one,
|
||||
// rewrite the source's variant tag at slot_off+0 to use the destination's
|
||||
// variant indices. No-op when src and dst index orders coincide.
|
||||
// Mirrors cg_widen_tag_remap in cmd/w6c/cgen.c.
|
||||
fn cgwidentagremap(c: *cgen, dst: *node, src: *node, slot_off: i32) void = {
|
||||
if (dst == nil) { return; };
|
||||
if (src == nil) { return; };
|
||||
if (dst.kind != nkind.N_TTAGGED) { return; };
|
||||
if (src.kind != nkind.N_TTAGGED) { return; };
|
||||
let identity: bool = true;
|
||||
let v: *node = src.list;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let di: i32 = cgtagvariantidx(c, dst, v);
|
||||
if (di < 0) { di = 0; };
|
||||
if (di != idx) { identity = false; v = nil; }
|
||||
else { v = v.next; idx += 1; };
|
||||
};
|
||||
if (identity) { return; };
|
||||
let done: str = mklabel(c, "remap_done");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(slot_off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
v = src.list;
|
||||
idx = 0;
|
||||
for (v != nil) {
|
||||
let next: str = mklabel(c, "remap_next");
|
||||
let di: i32 = cgtagvariantidx(c, dst, v);
|
||||
if (di < 0) { di = 0; };
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(idx: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(next);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(di: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(slot_off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tJMP\t");
|
||||
emitline(done);
|
||||
emitline("\n");
|
||||
emitlabel(next);
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
emitlabel(done);
|
||||
return;
|
||||
};
|
||||
|
||||
// rhsisstructpayload — is `src` a struct value (literal or local ident
|
||||
// of a struct type)? Returns the struct name, or empty str. Only true
|
||||
// when the name is registered in c.structs — `!void` / `!i32` aliases
|
||||
// share the N_STRUCTLIT / N_TNAME shape but aren't structs, and must
|
||||
// fall through to the scalar/str/tagged-source paths instead.
|
||||
fn rhsstructpayload(c: *cgen, src: *node) str = {
|
||||
let empty: str;
|
||||
empty.ptr = nil; empty.len = 0;
|
||||
if (src == nil) { return empty; };
|
||||
if (src.kind == nkind.N_STRUCTLIT) {
|
||||
let trefn: *node = src.lhs;
|
||||
if (trefn != nil) {
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (trefn.kind == nkind.N_IDENT) { nm = trefn.str; };
|
||||
if (trefn.kind == nkind.N_TNAME) { nm = trefn.str; };
|
||||
if (nm.len > 0) {
|
||||
if (structlookup(c, nm) != nil) { return nm; };
|
||||
};
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
if (src.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, src.str);
|
||||
if (lc != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
if (tn != nil) {
|
||||
if (tn.kind == nkind.N_TNAME) {
|
||||
if (structlookup(c, tn.str) != nil) {
|
||||
return tn.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// rhstaggedsource — return the tagged-type node for `src` when src is a
|
||||
// tagged-typed local ident; nil otherwise. The slot-copy path uses this
|
||||
// to walk variants for tag remap.
|
||||
fn rhstaggedident(c: *cgen, src: *node) *node = {
|
||||
if (src == nil) { return nil; };
|
||||
if (src.kind != nkind.N_IDENT) { return nil; };
|
||||
let lc: *local = localfindnode(c, src.str);
|
||||
if (lc == nil) { return nil; };
|
||||
let tn: *node = lc.tnode;
|
||||
if (!istaggedtype(c, tn)) { return nil; };
|
||||
return resolvetagged(c, tn);
|
||||
};
|
||||
|
||||
// rhstaggedabicall — does `src` produce a tagged value via the AX/DX/CX
|
||||
// return ABI? True for N_CALL of a tagged-returning fn and N_INDEX of a
|
||||
// tagged-element base. Used to decide whether cgexpr/spill works for the
|
||||
// tagged-source branch of cgwidentaggedstore.
|
||||
fn rhstaggedabicall(c: *cgen, src: *node) bool = {
|
||||
if (src == nil) { return false; };
|
||||
if (src.kind == nkind.N_CALL) {
|
||||
let callee: *node = src.lhs;
|
||||
if (callee != nil) {
|
||||
let calleename: str;
|
||||
calleename.ptr = nil; calleename.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { calleename = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (rt != nil) {
|
||||
if (istaggedtype(c, rt)) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (src.kind == nkind.N_INDEX) {
|
||||
let base: *node = src.lhs;
|
||||
if (base != nil) {
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let bl: *local = localfindnode(c, base.str);
|
||||
if (bl != nil) {
|
||||
let btn: *node = bl.tnode;
|
||||
if (btn != nil) {
|
||||
let bk: nkind = btn.kind;
|
||||
let elemt: *node = nil;
|
||||
if (bk == nkind.N_TARRAY) { elemt = btn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { elemt = btn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { elemt = btn.lhs; };
|
||||
if (elemt != nil) {
|
||||
if (istaggedtype(c, elemt)) {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
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:
|
||||
// - 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,
|
||||
// spill words; no remap (callee already speaks dst tag order — or
|
||||
// it doesn't, in which case the source is the wider one and remap
|
||||
// would need a reversed direction we don't currently emit).
|
||||
// - struct src (literal or ident): zero slot, write fields at +8+foff,
|
||||
// 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 = {
|
||||
let dt: *node = resolvetagged(c, dst);
|
||||
if (dt == nil) { return; };
|
||||
// Nullable fold: one 8B word holding the pointer (or 0 for void).
|
||||
if (isnullabletype(dst)) {
|
||||
cgexpr(c, src);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(slot_off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
// Tagged source ident: byte-copy slot words then tag-remap.
|
||||
let st: *node = rhstaggedident(c, src);
|
||||
if (st != nil) {
|
||||
let lc: *local = localfindnode(c, src.str);
|
||||
let ssz: i32 = slotsize(c, lc.tnode);
|
||||
let soff: i32 = lc.off;
|
||||
let k: i32 = 0;
|
||||
for (k < ssz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((soff + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + k): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 8;
|
||||
};
|
||||
if (ssz < slot_sz) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let p: i32 = ssz;
|
||||
for (p < slot_sz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + p): i64);
|
||||
emitline("(BP)\n");
|
||||
p += 8;
|
||||
};
|
||||
};
|
||||
cgwidentagremap(c, dt, st, slot_off);
|
||||
return;
|
||||
};
|
||||
// Tagged source via AX/DX/CX register ABI (N_CALL, N_INDEX of
|
||||
// tagged element).
|
||||
if (rhstaggedabicall(c, src)) {
|
||||
cgexpr(c, src);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(slot_off: i64);
|
||||
emitline("(BP)\n");
|
||||
if (slot_sz > 8) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((slot_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
if (slot_sz > 16) {
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((slot_off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Struct payload (literal or ident).
|
||||
let sname: str = rhsstructpayload(c, src);
|
||||
if (sname.len > 0) {
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zoff: i32 = 0;
|
||||
for (zoff < slot_sz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + zoff): i64);
|
||||
emitline("(BP)\n");
|
||||
zoff += 8;
|
||||
};
|
||||
let tag: i32 = taggedvariantindex(c, dt, src);
|
||||
if (tag < 0) { tag = 0; };
|
||||
if (src.kind == nkind.N_STRUCTLIT) {
|
||||
let fnode: *node = src.list;
|
||||
for (fnode != nil) {
|
||||
if (fnode.kind == nkind.N_FIELD) {
|
||||
let fname: str = fnode.str;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fname)) {
|
||||
cgexpr(c, fnode.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isstrtype(c, fi.tnode)) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
let sop: str = fieldstoreop(fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
}; };
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fnode = fnode.next;
|
||||
};
|
||||
} else {
|
||||
// Struct ident source: byte-copy struct words to slot+8+k.
|
||||
let lc: *local = localfindnode(c, src.str);
|
||||
let soff: i32 = 0;
|
||||
if (lc != nil) { soff = lc.off; };
|
||||
let stotal: i32 = si.totsize;
|
||||
let ki: i32 = 0;
|
||||
for (ki + 8 <= stotal) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((soff + ki): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8 + ki): i64);
|
||||
emitline("(BP)\n");
|
||||
ki += 8;
|
||||
};
|
||||
if (ki < stotal) {
|
||||
let tail: i32 = stotal - ki;
|
||||
let lop: str = "MOVQ";
|
||||
if (tail == 4) { lop = "MOVL"; }
|
||||
else { if (tail == 1) { lop = "MOVB"; }; };
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitoff((soff + ki): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((slot_off + 8 + ki): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(tag: i64);
|
||||
emitline(", ");
|
||||
emitoff(slot_off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Str payload.
|
||||
if (nodeisstr(c, src)) {
|
||||
cgexpr(c, src);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((slot_off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
let tag: i32 = taggedvariantindex(c, dt, src);
|
||||
if (tag < 0) { tag = 0; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(tag: i64);
|
||||
emitline(", ");
|
||||
emitoff(slot_off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
// Scalar payload.
|
||||
cgexpr(c, src);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
let tag: i32 = taggedvariantindex(c, dt, src);
|
||||
if (tag < 0) { tag = 0; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(tag: i64);
|
||||
emitline(", ");
|
||||
emitoff(slot_off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user