cgen: N-ary tuple destructure positional store + loud-stop (both stages, #83)
Replace the str-only XOR (e0_is_str ^ e1_is_str) at the tuple send
(N_RETURN) and receive (N_MLET/N_MASSIGN) sites with a positional
per-element register cursor, mirroring harec create_unpack_bindings
(ref/harec/src/check.c:1354-1416). Each element rides consecutive
eightbytes over [AX,DX,CX,R8]; a slice/str rides its 3-word
{ptr,len,cap} header (ref/hare/rt/ensure.ha:4-8), a scalar rides 1.
Send and receive walk the SAME type-table widths so element->register
agrees. This routes []u8 elements through the 3-word path (the XOR was
slice-blind, dropping len+cap to the scalar fallback) and closes the
pre-existing (scalar,slice) cs!=ww divergence by construction. cstage
and wwstage emit byte-identical asm.
Both receive sites derive each element's width from the rhs tuple's
element types (n->rhs->type->params / the callee return type) -- the
SAME producer view the send site walks -- NOT the binding type: a `_`
lvalue is an N_IDENT with empty str the checker never type-stamps, so a
binding-typed width mis-sized a wide `_` and desynced the cursor for the
next element (cstage read DX, wwstage R8). harec `_` skips the store but
CONSUMES its tuple offset; the cursor advance honours that.
Loud-stop (rule 7): the register file holds 4 eightbytes; a tuple whose
elements sum to >4 (([]u8,[]u8)/(str,str)=6) cannot be register-returned,
so the send site aborts at compile time citing the return-ABI capacity
(#10) rather than silently miscompiling. The receive loop guards the
same predicate (defense-in-depth). Routed through each stage's EXISTING
pinned-fatal idiom: cstage fatal() (cmd/wcc/err.c), wwstage the inline
os.write(2,...)+os.exit(1) at cgen.ww:604 -- no new diagnostics path.
N_MASSIGN (`a,b=f()`, bare comma, pre-declared) is a retained
ww-EXTENSION beyond Hare's binding-only tuple-unpack (Go/rob-pike
multi-assign, rule-9 carve-out); the loop covers it identically to
N_MLET.
Test 945_tuple_nary_destructure_run: (i64,[]u8)+(i64,str) store+read
len/cap for both N_MLET and N_MASSIGN, a single-str control, a wide-
first blank `_,a=f()` row (the cursor-desync discriminator), and a
([]u8,[]u8) row asserting the loud BUILDERR carries the cited
diagnostic; dual ww/ww_ww drivers.
This commit is contained in:
@@ -20062,47 +20062,134 @@ fn rundefers(c: *cgen) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// #83: positional tuple register-return ABI. Tuple elements ride
|
||||
// consecutive eightbytes over [AX,DX,CX,R8] (tupreg by index); a
|
||||
// slice/str rides its 3-word {ptr,len,cap} header (tyslicesize SSoT,
|
||||
// ref/hare/rt/ensure.ha:4-8), a scalar rides 1. SEND (cgreturn) and
|
||||
// RECEIVE (cgmlet/cgmassign) walk the SAME widths so element->register
|
||||
// agrees — mirrors harec create_unpack_bindings
|
||||
// (ref/harec/src/check.c:1354-1416). Capacity is 4 (AX,DX,CX,R8).
|
||||
fn tupreg(i: i32) str = {
|
||||
if (i == 0) { return "AX"; };
|
||||
if (i == 1) { return "DX"; };
|
||||
if (i == 2) { return "CX"; };
|
||||
return "R8";
|
||||
};
|
||||
|
||||
fn tupebytes(wide: bool) i32 = {
|
||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||
return 1;
|
||||
};
|
||||
|
||||
// rettupleof — the N_TTUPLE return-type node of an N_CALL rhs (else nil).
|
||||
// wwstage has no checker, so the receive sites read each tuple element's
|
||||
// width from the called fn's declared return type. Mirrors the callee
|
||||
// resolution shared by cgmlet/cgmassign.
|
||||
fn rettupleof(c: *cgen, rhs: *node) *node = {
|
||||
if (rhs == nil) { return nil; };
|
||||
if (rhs.kind != nkind.N_CALL) { return nil; };
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len == 0) { return nil; };
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp == nil) { return nil; };
|
||||
if (rtyp.kind != nkind.N_TTUPLE) { return nil; };
|
||||
return rtyp;
|
||||
};
|
||||
|
||||
// tupstore — store the tuple element at register-cursor `cur` into the
|
||||
// BP-relative slot at `off`. A slice/str stores its 3-word {ptr,len,cap}
|
||||
// header (ref/hare/rt/ensure.ha:4-8) at off/+8/+16 from consecutive
|
||||
// cursor registers; a scalar stores 1 word. Byte-identical to the cstage
|
||||
// N_MLET/N_MASSIGN store (cmd/w6c/cgen.c).
|
||||
fn tupstore(cur: i32, off: i32, wide: bool) void = {
|
||||
if (wide) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 0));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 1));
|
||||
emitline(", ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 2));
|
||||
emitline(", ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
|
||||
fn cgreturn(c: *cgen, n: *node) void = {
|
||||
rundefers(c);
|
||||
let rhs: *node = n.lhs;
|
||||
if (rhs != nil) {
|
||||
// Tuple return `return a, b;`, word-indexed AX→DX→CX→R8 (the
|
||||
// SAME register sequence as the tagged-union return below; the
|
||||
// tuple just fills it positionally):
|
||||
// (scalar, scalar) — AX = v0, DX = v1.
|
||||
// (scalar, str) / (str, scalar) — AX = scalar elem,
|
||||
// DX = str.ptr, CX = str.len, R8 = str.cap.
|
||||
// str IS []u8 (24B) → 32B tuple; cap rides R8, matching the
|
||||
// tagged-union return that already uses R8 for slot+24
|
||||
// (#1/Phase 3, task #5). Receive sites destructure off the
|
||||
// same regs regardless of position.
|
||||
// #83: positional per-element register-return (harec
|
||||
// create_unpack_bindings, ref/harec/src/check.c:1354-1416). Each
|
||||
// element rides consecutive eightbytes over [AX,DX,CX,R8]
|
||||
// (tupreg); a slice/str rides its 3-word {ptr,len,cap} header
|
||||
// (ref/hare/rt/ensure.ha:4-8), cgexpr leaving it in (AX,BX,CX); a
|
||||
// scalar rides 1 word in AX. Spill each element L->R, then pop
|
||||
// into the cursor's registers in reverse so positional slot i
|
||||
// lands in tupreg(i) — (scalar,str) keeps the historical AX +
|
||||
// DX,CX,R8. The SAME cursor drives the receive sites. Over-
|
||||
// capacity is a loud stop (return-ABI #10), never a silent drop.
|
||||
if (rhs.kind == nkind.N_TUPLE) {
|
||||
let v: *node = rhs.list;
|
||||
if (v != nil) {
|
||||
let v2: *node = v.next;
|
||||
if (v2 != nil) {
|
||||
let v0_is_str: bool = nodeisstr(c, v);
|
||||
let v1_is_str: bool = nodeisstr(c, v2);
|
||||
if ((v0_is_str || v1_is_str) && !(v0_is_str && v1_is_str)) {
|
||||
let strn: *node = v;
|
||||
let scaln: *node = v2;
|
||||
if (v1_is_str) { strn = v2; scaln = v; };
|
||||
cgexpr(c, scaln);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, strn);
|
||||
emitline("\tMOVQ\tCX, R8\n");
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
} else {
|
||||
cgexpr(c, v2);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, v);
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
let total: i32 = 0;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
total = total + tupebytes(wide);
|
||||
e = e.next;
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
||||
// uses fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple return exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
e = rhs.list;
|
||||
for (e != nil) {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
cgexpr(c, e);
|
||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||
if (wide) {
|
||||
emitline("\tPUSHQ\tBX\n"); // .len
|
||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
let i: i32 = total - 1;
|
||||
for (i >= 0) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(tupreg(i));
|
||||
emitline("\n");
|
||||
i = i - 1;
|
||||
};
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
@@ -21152,131 +21239,58 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
|
||||
// as C — no fixture uses >2 today).
|
||||
fn cgmassign(c: *cgen, n: *node) void = {
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
// one-str only; two-str destructure is gap (task #22). The str
|
||||
// tuple element's 24B slot already exists (reassignment), so
|
||||
// localfind it and mirror cgmlet's (DX,CX,R8)->(.ptr,.len,.cap)
|
||||
// routing; the bare scalar fallback below would store only DX and
|
||||
// drop len/cap. wwstage has no checker, so str-ness comes from the
|
||||
// called fn's return-type tuple element (as in cgmlet).
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs != nil) {
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len > 0) {
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp != nil) {
|
||||
if (rtyp.kind == nkind.N_TTUPLE) {
|
||||
let pp: *node = rtyp.list;
|
||||
if (pp != nil) {
|
||||
p0t = pp.lhs;
|
||||
if (pp.next != nil) {
|
||||
p1t = pp.next.lhs;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let s0_is_str: bool = isstrtype(c, p0t);
|
||||
let s1_is_str: bool = isstrtype(c, p1t);
|
||||
// #83: positional per-element destructure REASSIGN. Same cursor as
|
||||
// cgmlet (and cgreturn; harec create_unpack_bindings,
|
||||
// ref/harec/src/check.c:1354-1416), but the slots already exist
|
||||
// (reassignment) so localfind them. wwstage has no checker, so each
|
||||
// element's width comes from the called fn's return-type tuple
|
||||
// element (N_TTUPLE param) walked in lockstep with the bindings; a
|
||||
// slice/str rides its 3-word {ptr,len,cap} header
|
||||
// (ref/hare/rt/ensure.ha:4-8). A missing/non-ident binding consumes
|
||||
// its register slot without storing (mirrors harec `_`). This bare-
|
||||
// comma `a, s = f()` multi-assign is a retained ww-EXTENSION beyond
|
||||
// Hare (Hare tuple-unpack is binding-only); ww keeps the Go/rob-pike
|
||||
// multi-assign idiom — rule-9 carve-out. Over-capacity loud-stops.
|
||||
let rettuple: *node = rettupleof(c, n.rhs);
|
||||
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (l0.kind == nkind.N_IDENT) {
|
||||
if (l1.kind == nkind.N_IDENT) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let off0: i32 = localfind(c, l0.str);
|
||||
let off1: i32 = localfind(c, l1.str);
|
||||
if (off0 != 0) {
|
||||
if (off1 != 0) {
|
||||
if (s0_is_str) {
|
||||
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off0 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off1 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let total: i32 = 0;
|
||||
let l: *node = n.list;
|
||||
let pt: *node = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
total = total + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
// fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
emitline("\tPUSHQ\tDX\n");
|
||||
if (l0 != nil) {
|
||||
if (l0.kind == nkind.N_IDENT) {
|
||||
let off: i32 = localfind(c, l0.str);
|
||||
if (off != 0) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
if (l1 != nil) {
|
||||
if (l1.kind == nkind.N_IDENT) {
|
||||
let off: i32 = localfind(c, l1.str);
|
||||
if (off != 0) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
let cur: i32 = 0;
|
||||
l = n.list;
|
||||
pt = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let off: i32 = 0;
|
||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||
if (off != 0) {
|
||||
tupstore(cur, off, wide);
|
||||
};
|
||||
cur = cur + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
@@ -21297,115 +21311,57 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs == nil) { return; };
|
||||
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len > 0) {
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp != nil) {
|
||||
if (rtyp.kind == nkind.N_TTUPLE) {
|
||||
let pp: *node = rtyp.list;
|
||||
if (pp != nil) {
|
||||
p0t = pp.lhs;
|
||||
if (pp.next != nil) {
|
||||
p1t = pp.next.lhs;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
let t0: *node = nil;
|
||||
let t1: *node = nil;
|
||||
if (l0 != nil) { t0 = l0.lhs; };
|
||||
if (l1 != nil) { t1 = l1.lhs; };
|
||||
if (t0 == nil) { t0 = p0t; };
|
||||
if (t1 == nil) { t1 = p1t; };
|
||||
|
||||
let s0_is_str: bool = isstrtype(c, t0);
|
||||
let s1_is_str: bool = isstrtype(c, t1);
|
||||
// #83: positional per-element destructure let-binding. Same cursor
|
||||
// as cgmassign (and cgreturn; harec create_unpack_bindings,
|
||||
// ref/harec/src/check.c:1354-1416). wwstage has no checker, so each
|
||||
// binding's type is its explicit annotation (l.lhs) when present,
|
||||
// else the called fn's return-type tuple element (N_TTUPLE param)
|
||||
// walked in lockstep. A slice/str rides its 3-word {ptr,len,cap}
|
||||
// header (ref/hare/rt/ensure.ha:4-8) into a header-sized slot; a
|
||||
// scalar rides 1 word into an 8B slot. Over-capacity loud-stops.
|
||||
let rettuple: *node = rettupleof(c, rhs);
|
||||
|
||||
cgexpr(c, rhs);
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let sz0: i32 = 8;
|
||||
let sz1: i32 = 8;
|
||||
if (s0_is_str) { sz0 = primtypesize("str"): i32; };
|
||||
if (s1_is_str) { sz1 = primtypesize("str"): i32; };
|
||||
let off0: i32 = localadd(c, l0.str, sz0, t0);
|
||||
let off1: i32 = localadd(c, l1.str, sz1, t1);
|
||||
if (s0_is_str) {
|
||||
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off0 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off1 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
let total: i32 = 0;
|
||||
let l: *node = n.list;
|
||||
let pt: *node = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = l.lhs;
|
||||
if (tn == nil) {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
total = total + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
// fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
if (l0 != nil) {
|
||||
let off: i32 = localadd(c, l0.str, 8, t0);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
if (l1 != nil) {
|
||||
let off: i32 = localadd(c, l1.str, 8, t1);
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
let cur: i32 = 0;
|
||||
l = n.list;
|
||||
pt = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = l.lhs;
|
||||
if (tn == nil) {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let sz: i32 = 8;
|
||||
if (wide) { sz = tyslicesize(): i32; };
|
||||
let off: i32 = localadd(c, l.str, sz, tn);
|
||||
tupstore(cur, off, wide);
|
||||
cur = cur + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
|
||||
@@ -107,47 +107,134 @@ fn rundefers(c: *cgen) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// #83: positional tuple register-return ABI. Tuple elements ride
|
||||
// consecutive eightbytes over [AX,DX,CX,R8] (tupreg by index); a
|
||||
// slice/str rides its 3-word {ptr,len,cap} header (tyslicesize SSoT,
|
||||
// ref/hare/rt/ensure.ha:4-8), a scalar rides 1. SEND (cgreturn) and
|
||||
// RECEIVE (cgmlet/cgmassign) walk the SAME widths so element->register
|
||||
// agrees — mirrors harec create_unpack_bindings
|
||||
// (ref/harec/src/check.c:1354-1416). Capacity is 4 (AX,DX,CX,R8).
|
||||
fn tupreg(i: i32) str = {
|
||||
if (i == 0) { return "AX"; };
|
||||
if (i == 1) { return "DX"; };
|
||||
if (i == 2) { return "CX"; };
|
||||
return "R8";
|
||||
};
|
||||
|
||||
fn tupebytes(wide: bool) i32 = {
|
||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||
return 1;
|
||||
};
|
||||
|
||||
// rettupleof — the N_TTUPLE return-type node of an N_CALL rhs (else nil).
|
||||
// wwstage has no checker, so the receive sites read each tuple element's
|
||||
// width from the called fn's declared return type. Mirrors the callee
|
||||
// resolution shared by cgmlet/cgmassign.
|
||||
fn rettupleof(c: *cgen, rhs: *node) *node = {
|
||||
if (rhs == nil) { return nil; };
|
||||
if (rhs.kind != nkind.N_CALL) { return nil; };
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len == 0) { return nil; };
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp == nil) { return nil; };
|
||||
if (rtyp.kind != nkind.N_TTUPLE) { return nil; };
|
||||
return rtyp;
|
||||
};
|
||||
|
||||
// tupstore — store the tuple element at register-cursor `cur` into the
|
||||
// BP-relative slot at `off`. A slice/str stores its 3-word {ptr,len,cap}
|
||||
// header (ref/hare/rt/ensure.ha:4-8) at off/+8/+16 from consecutive
|
||||
// cursor registers; a scalar stores 1 word. Byte-identical to the cstage
|
||||
// N_MLET/N_MASSIGN store (cmd/w6c/cgen.c).
|
||||
fn tupstore(cur: i32, off: i32, wide: bool) void = {
|
||||
if (wide) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 0));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 1));
|
||||
emitline(", ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 2));
|
||||
emitline(", ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
|
||||
fn cgreturn(c: *cgen, n: *node) void = {
|
||||
rundefers(c);
|
||||
let rhs: *node = n.lhs;
|
||||
if (rhs != nil) {
|
||||
// Tuple return `return a, b;`, word-indexed AX→DX→CX→R8 (the
|
||||
// SAME register sequence as the tagged-union return below; the
|
||||
// tuple just fills it positionally):
|
||||
// (scalar, scalar) — AX = v0, DX = v1.
|
||||
// (scalar, str) / (str, scalar) — AX = scalar elem,
|
||||
// DX = str.ptr, CX = str.len, R8 = str.cap.
|
||||
// str IS []u8 (24B) → 32B tuple; cap rides R8, matching the
|
||||
// tagged-union return that already uses R8 for slot+24
|
||||
// (#1/Phase 3, task #5). Receive sites destructure off the
|
||||
// same regs regardless of position.
|
||||
// #83: positional per-element register-return (harec
|
||||
// create_unpack_bindings, ref/harec/src/check.c:1354-1416). Each
|
||||
// element rides consecutive eightbytes over [AX,DX,CX,R8]
|
||||
// (tupreg); a slice/str rides its 3-word {ptr,len,cap} header
|
||||
// (ref/hare/rt/ensure.ha:4-8), cgexpr leaving it in (AX,BX,CX); a
|
||||
// scalar rides 1 word in AX. Spill each element L->R, then pop
|
||||
// into the cursor's registers in reverse so positional slot i
|
||||
// lands in tupreg(i) — (scalar,str) keeps the historical AX +
|
||||
// DX,CX,R8. The SAME cursor drives the receive sites. Over-
|
||||
// capacity is a loud stop (return-ABI #10), never a silent drop.
|
||||
if (rhs.kind == nkind.N_TUPLE) {
|
||||
let v: *node = rhs.list;
|
||||
if (v != nil) {
|
||||
let v2: *node = v.next;
|
||||
if (v2 != nil) {
|
||||
let v0_is_str: bool = nodeisstr(c, v);
|
||||
let v1_is_str: bool = nodeisstr(c, v2);
|
||||
if ((v0_is_str || v1_is_str) && !(v0_is_str && v1_is_str)) {
|
||||
let strn: *node = v;
|
||||
let scaln: *node = v2;
|
||||
if (v1_is_str) { strn = v2; scaln = v; };
|
||||
cgexpr(c, scaln);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, strn);
|
||||
emitline("\tMOVQ\tCX, R8\n");
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
} else {
|
||||
cgexpr(c, v2);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, v);
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
let total: i32 = 0;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
total = total + tupebytes(wide);
|
||||
e = e.next;
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
||||
// uses fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple return exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
e = rhs.list;
|
||||
for (e != nil) {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
cgexpr(c, e);
|
||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||
if (wide) {
|
||||
emitline("\tPUSHQ\tBX\n"); // .len
|
||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
let i: i32 = total - 1;
|
||||
for (i >= 0) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(tupreg(i));
|
||||
emitline("\n");
|
||||
i = i - 1;
|
||||
};
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
@@ -1197,131 +1284,58 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
|
||||
// as C — no fixture uses >2 today).
|
||||
fn cgmassign(c: *cgen, n: *node) void = {
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
// one-str only; two-str destructure is gap (task #22). The str
|
||||
// tuple element's 24B slot already exists (reassignment), so
|
||||
// localfind it and mirror cgmlet's (DX,CX,R8)->(.ptr,.len,.cap)
|
||||
// routing; the bare scalar fallback below would store only DX and
|
||||
// drop len/cap. wwstage has no checker, so str-ness comes from the
|
||||
// called fn's return-type tuple element (as in cgmlet).
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs != nil) {
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len > 0) {
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp != nil) {
|
||||
if (rtyp.kind == nkind.N_TTUPLE) {
|
||||
let pp: *node = rtyp.list;
|
||||
if (pp != nil) {
|
||||
p0t = pp.lhs;
|
||||
if (pp.next != nil) {
|
||||
p1t = pp.next.lhs;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let s0_is_str: bool = isstrtype(c, p0t);
|
||||
let s1_is_str: bool = isstrtype(c, p1t);
|
||||
// #83: positional per-element destructure REASSIGN. Same cursor as
|
||||
// cgmlet (and cgreturn; harec create_unpack_bindings,
|
||||
// ref/harec/src/check.c:1354-1416), but the slots already exist
|
||||
// (reassignment) so localfind them. wwstage has no checker, so each
|
||||
// element's width comes from the called fn's return-type tuple
|
||||
// element (N_TTUPLE param) walked in lockstep with the bindings; a
|
||||
// slice/str rides its 3-word {ptr,len,cap} header
|
||||
// (ref/hare/rt/ensure.ha:4-8). A missing/non-ident binding consumes
|
||||
// its register slot without storing (mirrors harec `_`). This bare-
|
||||
// comma `a, s = f()` multi-assign is a retained ww-EXTENSION beyond
|
||||
// Hare (Hare tuple-unpack is binding-only); ww keeps the Go/rob-pike
|
||||
// multi-assign idiom — rule-9 carve-out. Over-capacity loud-stops.
|
||||
let rettuple: *node = rettupleof(c, n.rhs);
|
||||
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (l0.kind == nkind.N_IDENT) {
|
||||
if (l1.kind == nkind.N_IDENT) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let off0: i32 = localfind(c, l0.str);
|
||||
let off1: i32 = localfind(c, l1.str);
|
||||
if (off0 != 0) {
|
||||
if (off1 != 0) {
|
||||
if (s0_is_str) {
|
||||
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off0 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off1 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let total: i32 = 0;
|
||||
let l: *node = n.list;
|
||||
let pt: *node = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
total = total + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
// fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
emitline("\tPUSHQ\tDX\n");
|
||||
if (l0 != nil) {
|
||||
if (l0.kind == nkind.N_IDENT) {
|
||||
let off: i32 = localfind(c, l0.str);
|
||||
if (off != 0) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
if (l1 != nil) {
|
||||
if (l1.kind == nkind.N_IDENT) {
|
||||
let off: i32 = localfind(c, l1.str);
|
||||
if (off != 0) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
let cur: i32 = 0;
|
||||
l = n.list;
|
||||
pt = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let off: i32 = 0;
|
||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||
if (off != 0) {
|
||||
tupstore(cur, off, wide);
|
||||
};
|
||||
cur = cur + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
@@ -1342,115 +1356,57 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs == nil) { return; };
|
||||
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len > 0) {
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp != nil) {
|
||||
if (rtyp.kind == nkind.N_TTUPLE) {
|
||||
let pp: *node = rtyp.list;
|
||||
if (pp != nil) {
|
||||
p0t = pp.lhs;
|
||||
if (pp.next != nil) {
|
||||
p1t = pp.next.lhs;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
let t0: *node = nil;
|
||||
let t1: *node = nil;
|
||||
if (l0 != nil) { t0 = l0.lhs; };
|
||||
if (l1 != nil) { t1 = l1.lhs; };
|
||||
if (t0 == nil) { t0 = p0t; };
|
||||
if (t1 == nil) { t1 = p1t; };
|
||||
|
||||
let s0_is_str: bool = isstrtype(c, t0);
|
||||
let s1_is_str: bool = isstrtype(c, t1);
|
||||
// #83: positional per-element destructure let-binding. Same cursor
|
||||
// as cgmassign (and cgreturn; harec create_unpack_bindings,
|
||||
// ref/harec/src/check.c:1354-1416). wwstage has no checker, so each
|
||||
// binding's type is its explicit annotation (l.lhs) when present,
|
||||
// else the called fn's return-type tuple element (N_TTUPLE param)
|
||||
// walked in lockstep. A slice/str rides its 3-word {ptr,len,cap}
|
||||
// header (ref/hare/rt/ensure.ha:4-8) into a header-sized slot; a
|
||||
// scalar rides 1 word into an 8B slot. Over-capacity loud-stops.
|
||||
let rettuple: *node = rettupleof(c, rhs);
|
||||
|
||||
cgexpr(c, rhs);
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let sz0: i32 = 8;
|
||||
let sz1: i32 = 8;
|
||||
if (s0_is_str) { sz0 = primtypesize("str"): i32; };
|
||||
if (s1_is_str) { sz1 = primtypesize("str"): i32; };
|
||||
let off0: i32 = localadd(c, l0.str, sz0, t0);
|
||||
let off1: i32 = localadd(c, l1.str, sz1, t1);
|
||||
if (s0_is_str) {
|
||||
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off0 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off1 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
let total: i32 = 0;
|
||||
let l: *node = n.list;
|
||||
let pt: *node = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = l.lhs;
|
||||
if (tn == nil) {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
total = total + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
// fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
if (l0 != nil) {
|
||||
let off: i32 = localadd(c, l0.str, 8, t0);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
if (l1 != nil) {
|
||||
let off: i32 = localadd(c, l1.str, 8, t1);
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
let cur: i32 = 0;
|
||||
l = n.list;
|
||||
pt = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = l.lhs;
|
||||
if (tn == nil) {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let sz: i32 = 8;
|
||||
if (wide) { sz = tyslicesize(): i32; };
|
||||
let off: i32 = localadd(c, l.str, sz, tn);
|
||||
tupstore(cur, off, wide);
|
||||
cur = cur + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
|
||||
@@ -20062,47 +20062,134 @@ fn rundefers(c: *cgen) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// #83: positional tuple register-return ABI. Tuple elements ride
|
||||
// consecutive eightbytes over [AX,DX,CX,R8] (tupreg by index); a
|
||||
// slice/str rides its 3-word {ptr,len,cap} header (tyslicesize SSoT,
|
||||
// ref/hare/rt/ensure.ha:4-8), a scalar rides 1. SEND (cgreturn) and
|
||||
// RECEIVE (cgmlet/cgmassign) walk the SAME widths so element->register
|
||||
// agrees — mirrors harec create_unpack_bindings
|
||||
// (ref/harec/src/check.c:1354-1416). Capacity is 4 (AX,DX,CX,R8).
|
||||
fn tupreg(i: i32) str = {
|
||||
if (i == 0) { return "AX"; };
|
||||
if (i == 1) { return "DX"; };
|
||||
if (i == 2) { return "CX"; };
|
||||
return "R8";
|
||||
};
|
||||
|
||||
fn tupebytes(wide: bool) i32 = {
|
||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||
return 1;
|
||||
};
|
||||
|
||||
// rettupleof — the N_TTUPLE return-type node of an N_CALL rhs (else nil).
|
||||
// wwstage has no checker, so the receive sites read each tuple element's
|
||||
// width from the called fn's declared return type. Mirrors the callee
|
||||
// resolution shared by cgmlet/cgmassign.
|
||||
fn rettupleof(c: *cgen, rhs: *node) *node = {
|
||||
if (rhs == nil) { return nil; };
|
||||
if (rhs.kind != nkind.N_CALL) { return nil; };
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len == 0) { return nil; };
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp == nil) { return nil; };
|
||||
if (rtyp.kind != nkind.N_TTUPLE) { return nil; };
|
||||
return rtyp;
|
||||
};
|
||||
|
||||
// tupstore — store the tuple element at register-cursor `cur` into the
|
||||
// BP-relative slot at `off`. A slice/str stores its 3-word {ptr,len,cap}
|
||||
// header (ref/hare/rt/ensure.ha:4-8) at off/+8/+16 from consecutive
|
||||
// cursor registers; a scalar stores 1 word. Byte-identical to the cstage
|
||||
// N_MLET/N_MASSIGN store (cmd/w6c/cgen.c).
|
||||
fn tupstore(cur: i32, off: i32, wide: bool) void = {
|
||||
if (wide) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 0));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 1));
|
||||
emitline(", ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur + 2));
|
||||
emitline(", ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(cur));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
|
||||
fn cgreturn(c: *cgen, n: *node) void = {
|
||||
rundefers(c);
|
||||
let rhs: *node = n.lhs;
|
||||
if (rhs != nil) {
|
||||
// Tuple return `return a, b;`, word-indexed AX→DX→CX→R8 (the
|
||||
// SAME register sequence as the tagged-union return below; the
|
||||
// tuple just fills it positionally):
|
||||
// (scalar, scalar) — AX = v0, DX = v1.
|
||||
// (scalar, str) / (str, scalar) — AX = scalar elem,
|
||||
// DX = str.ptr, CX = str.len, R8 = str.cap.
|
||||
// str IS []u8 (24B) → 32B tuple; cap rides R8, matching the
|
||||
// tagged-union return that already uses R8 for slot+24
|
||||
// (#1/Phase 3, task #5). Receive sites destructure off the
|
||||
// same regs regardless of position.
|
||||
// #83: positional per-element register-return (harec
|
||||
// create_unpack_bindings, ref/harec/src/check.c:1354-1416). Each
|
||||
// element rides consecutive eightbytes over [AX,DX,CX,R8]
|
||||
// (tupreg); a slice/str rides its 3-word {ptr,len,cap} header
|
||||
// (ref/hare/rt/ensure.ha:4-8), cgexpr leaving it in (AX,BX,CX); a
|
||||
// scalar rides 1 word in AX. Spill each element L->R, then pop
|
||||
// into the cursor's registers in reverse so positional slot i
|
||||
// lands in tupreg(i) — (scalar,str) keeps the historical AX +
|
||||
// DX,CX,R8. The SAME cursor drives the receive sites. Over-
|
||||
// capacity is a loud stop (return-ABI #10), never a silent drop.
|
||||
if (rhs.kind == nkind.N_TUPLE) {
|
||||
let v: *node = rhs.list;
|
||||
if (v != nil) {
|
||||
let v2: *node = v.next;
|
||||
if (v2 != nil) {
|
||||
let v0_is_str: bool = nodeisstr(c, v);
|
||||
let v1_is_str: bool = nodeisstr(c, v2);
|
||||
if ((v0_is_str || v1_is_str) && !(v0_is_str && v1_is_str)) {
|
||||
let strn: *node = v;
|
||||
let scaln: *node = v2;
|
||||
if (v1_is_str) { strn = v2; scaln = v; };
|
||||
cgexpr(c, scaln);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, strn);
|
||||
emitline("\tMOVQ\tCX, R8\n");
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
} else {
|
||||
cgexpr(c, v2);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, v);
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
let total: i32 = 0;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
total = total + tupebytes(wide);
|
||||
e = e.next;
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
||||
// uses fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple return exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
e = rhs.list;
|
||||
for (e != nil) {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
cgexpr(c, e);
|
||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||
if (wide) {
|
||||
emitline("\tPUSHQ\tBX\n"); // .len
|
||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
let i: i32 = total - 1;
|
||||
for (i >= 0) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(tupreg(i));
|
||||
emitline("\n");
|
||||
i = i - 1;
|
||||
};
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
@@ -21152,131 +21239,58 @@ fn cgfor(c: *cgen, n: *node) void = {
|
||||
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
|
||||
// as C — no fixture uses >2 today).
|
||||
fn cgmassign(c: *cgen, n: *node) void = {
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
// one-str only; two-str destructure is gap (task #22). The str
|
||||
// tuple element's 24B slot already exists (reassignment), so
|
||||
// localfind it and mirror cgmlet's (DX,CX,R8)->(.ptr,.len,.cap)
|
||||
// routing; the bare scalar fallback below would store only DX and
|
||||
// drop len/cap. wwstage has no checker, so str-ness comes from the
|
||||
// called fn's return-type tuple element (as in cgmlet).
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs != nil) {
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len > 0) {
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp != nil) {
|
||||
if (rtyp.kind == nkind.N_TTUPLE) {
|
||||
let pp: *node = rtyp.list;
|
||||
if (pp != nil) {
|
||||
p0t = pp.lhs;
|
||||
if (pp.next != nil) {
|
||||
p1t = pp.next.lhs;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let s0_is_str: bool = isstrtype(c, p0t);
|
||||
let s1_is_str: bool = isstrtype(c, p1t);
|
||||
// #83: positional per-element destructure REASSIGN. Same cursor as
|
||||
// cgmlet (and cgreturn; harec create_unpack_bindings,
|
||||
// ref/harec/src/check.c:1354-1416), but the slots already exist
|
||||
// (reassignment) so localfind them. wwstage has no checker, so each
|
||||
// element's width comes from the called fn's return-type tuple
|
||||
// element (N_TTUPLE param) walked in lockstep with the bindings; a
|
||||
// slice/str rides its 3-word {ptr,len,cap} header
|
||||
// (ref/hare/rt/ensure.ha:4-8). A missing/non-ident binding consumes
|
||||
// its register slot without storing (mirrors harec `_`). This bare-
|
||||
// comma `a, s = f()` multi-assign is a retained ww-EXTENSION beyond
|
||||
// Hare (Hare tuple-unpack is binding-only); ww keeps the Go/rob-pike
|
||||
// multi-assign idiom — rule-9 carve-out. Over-capacity loud-stops.
|
||||
let rettuple: *node = rettupleof(c, n.rhs);
|
||||
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (l0.kind == nkind.N_IDENT) {
|
||||
if (l1.kind == nkind.N_IDENT) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let off0: i32 = localfind(c, l0.str);
|
||||
let off1: i32 = localfind(c, l1.str);
|
||||
if (off0 != 0) {
|
||||
if (off1 != 0) {
|
||||
if (s0_is_str) {
|
||||
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off0 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off1 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let total: i32 = 0;
|
||||
let l: *node = n.list;
|
||||
let pt: *node = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
total = total + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
// fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
emitline("\tPUSHQ\tDX\n");
|
||||
if (l0 != nil) {
|
||||
if (l0.kind == nkind.N_IDENT) {
|
||||
let off: i32 = localfind(c, l0.str);
|
||||
if (off != 0) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
if (l1 != nil) {
|
||||
if (l1.kind == nkind.N_IDENT) {
|
||||
let off: i32 = localfind(c, l1.str);
|
||||
if (off != 0) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
let cur: i32 = 0;
|
||||
l = n.list;
|
||||
pt = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let off: i32 = 0;
|
||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||
if (off != 0) {
|
||||
tupstore(cur, off, wide);
|
||||
};
|
||||
cur = cur + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
@@ -21297,115 +21311,57 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs == nil) { return; };
|
||||
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
let cmod: str;
|
||||
cmod.ptr = nil; cmod.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
cnm = callee.str;
|
||||
cmod = c.curmod;
|
||||
};
|
||||
if (callee.kind == nkind.N_DOT) {
|
||||
cnm = callee.str;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
cmod = callee.lhs.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (cnm.len > 0) {
|
||||
let rtyp: *node = fnretlookupmod(c, cnm, cmod);
|
||||
if (rtyp != nil) {
|
||||
if (rtyp.kind == nkind.N_TTUPLE) {
|
||||
let pp: *node = rtyp.list;
|
||||
if (pp != nil) {
|
||||
p0t = pp.lhs;
|
||||
if (pp.next != nil) {
|
||||
p1t = pp.next.lhs;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
let t0: *node = nil;
|
||||
let t1: *node = nil;
|
||||
if (l0 != nil) { t0 = l0.lhs; };
|
||||
if (l1 != nil) { t1 = l1.lhs; };
|
||||
if (t0 == nil) { t0 = p0t; };
|
||||
if (t1 == nil) { t1 = p1t; };
|
||||
|
||||
let s0_is_str: bool = isstrtype(c, t0);
|
||||
let s1_is_str: bool = isstrtype(c, t1);
|
||||
// #83: positional per-element destructure let-binding. Same cursor
|
||||
// as cgmassign (and cgreturn; harec create_unpack_bindings,
|
||||
// ref/harec/src/check.c:1354-1416). wwstage has no checker, so each
|
||||
// binding's type is its explicit annotation (l.lhs) when present,
|
||||
// else the called fn's return-type tuple element (N_TTUPLE param)
|
||||
// walked in lockstep. A slice/str rides its 3-word {ptr,len,cap}
|
||||
// header (ref/hare/rt/ensure.ha:4-8) into a header-sized slot; a
|
||||
// scalar rides 1 word into an 8B slot. Over-capacity loud-stops.
|
||||
let rettuple: *node = rettupleof(c, rhs);
|
||||
|
||||
cgexpr(c, rhs);
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let sz0: i32 = 8;
|
||||
let sz1: i32 = 8;
|
||||
if (s0_is_str) { sz0 = primtypesize("str"): i32; };
|
||||
if (s1_is_str) { sz1 = primtypesize("str"): i32; };
|
||||
let off0: i32 = localadd(c, l0.str, sz0, t0);
|
||||
let off1: i32 = localadd(c, l1.str, sz1, t1);
|
||||
if (s0_is_str) {
|
||||
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off0 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off1 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
let total: i32 = 0;
|
||||
let l: *node = n.list;
|
||||
let pt: *node = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = l.lhs;
|
||||
if (tn == nil) {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
total = total + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
// fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
if (l0 != nil) {
|
||||
let off: i32 = localadd(c, l0.str, 8, t0);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
if (l1 != nil) {
|
||||
let off: i32 = localadd(c, l1.str, 8, t1);
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
let cur: i32 = 0;
|
||||
l = n.list;
|
||||
pt = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = l.lhs;
|
||||
if (tn == nil) {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let sz: i32 = 8;
|
||||
if (wide) { sz = tyslicesize(): i32; };
|
||||
let off: i32 = localadd(c, l.str, sz, tn);
|
||||
tupstore(cur, off, wide);
|
||||
cur = cur + tupebytes(wide);
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user