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:
255
cmd/w6c/cgen.c
255
cmd/w6c/cgen.c
@@ -184,6 +184,20 @@ node_isslice(Node *n)
|
||||
return n && type_isslice(n->type);
|
||||
}
|
||||
|
||||
/* #83: positional tuple register-return ABI. Tuple elements ride
|
||||
* consecutive eightbytes over tuple_rseq[]; a slice/str rides its 3-word
|
||||
* {ptr,len,cap} header (ref/hare/rt/ensure.ha:4-8, ty_str->size SSoT), a
|
||||
* scalar rides 1. SEND (N_RETURN) and RECEIVE (N_MLET/N_MASSIGN) walk the
|
||||
* SAME widths so element->register agrees — mirrors harec's
|
||||
* create_unpack_bindings element walk (ref/harec/src/check.c:1354-1416). */
|
||||
static const int tuple_rseq[] = { D_AX, D_DX, D_CX, D_R8 };
|
||||
|
||||
static int
|
||||
tuple_ebytes(int wide)
|
||||
{
|
||||
return wide ? (int)(ty_str->size / 8) : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
type_isf32(Type *t)
|
||||
{
|
||||
@@ -7256,45 +7270,37 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
if (n->lhs && n->lhs->kind == N_TUPLE) {
|
||||
/* 2-tuple ABI, word-indexed AX→DX→CX→R8 (the SAME
|
||||
* register sequence as the tagged-union return; the
|
||||
* tuple just fills it positionally):
|
||||
* (scalar, scalar) — AX = e0, DX = e1. (16B, fits SysV.)
|
||||
* (scalar, str) — AX = scalar elem, DX = str.ptr,
|
||||
* CX = str.len, R8 = str.cap. (32B.)
|
||||
* (str, scalar) — same regs, type-keyed not position-keyed.
|
||||
*
|
||||
* str IS []u8 (24B), so a (scalar, str) tuple is 32B and
|
||||
* rides AX:DX:CX:R8 — the cap is the 4th word, matching the
|
||||
* tagged-union return that already uses R8 for slot+24
|
||||
* (#1/Phase 3, task #5). Receive sites destructure off the
|
||||
* same regs. */
|
||||
Node *e0 = n->lhs->list;
|
||||
Node *e1 = e0 ? e0->next : NULL;
|
||||
if (e1 && e1->next == NULL) {
|
||||
int e0_is_str = node_isstr(e0);
|
||||
int e1_is_str = node_isstr(e1);
|
||||
if (e0_is_str ^ e1_is_str) {
|
||||
Node *strn = e0_is_str ? e0 : e1;
|
||||
Node *scaln = e0_is_str ? e1 : e0;
|
||||
cgexpr(c, scaln, *locals); /* AX = scalar */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, strn, *locals); /* AX=ptr, BX=len, CX=cap */
|
||||
ins2(c, A_MOVQ, areg(D_CX), areg(D_R8));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_DX));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
} else {
|
||||
cgexpr(c, e1, *locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, e0, *locals);
|
||||
ins1(c, A_POPQ, areg(D_DX));
|
||||
/* #83: positional per-element register-return. Walk the
|
||||
* tuple's elements (harec create_unpack_bindings,
|
||||
* ref/harec/src/check.c:1354-1416); each rides consecutive
|
||||
* eightbytes over tuple_rseq[]. 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's word(s) L→R, then pop into the
|
||||
* cursor's registers in reverse so positional slot i lands in
|
||||
* tuple_rseq[i] — (scalar,str) keeps the historical AX +
|
||||
* DX,CX,R8 layout, and the SAME cursor drives the receive
|
||||
* sites. Over-capacity is a loud stop (return-ABI #10), never
|
||||
* a silent drop. */
|
||||
int cap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||
int total = 0;
|
||||
for (Node *e = n->lhs->list; e; e = e->next)
|
||||
total += tuple_ebytes(node_isstr(e) || node_isslice(e));
|
||||
if (total > cap)
|
||||
fatal("tuple return exceeds register-return ABI "
|
||||
"capacity (%d eightbytes); see return-ABI #10",
|
||||
cap);
|
||||
for (Node *e = n->lhs->list; e; e = e->next) {
|
||||
int wide = node_isstr(e) || node_isslice(e);
|
||||
cgexpr(c, e, *locals); /* scalar=AX; slice/str=AX,BX,CX */
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* scalar / .ptr */
|
||||
if (wide) {
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* .len */
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* .cap */
|
||||
}
|
||||
} else {
|
||||
/* >2-tuple not yet implemented; fall back to first elem */
|
||||
if (e0) cgexpr(c, e0, *locals);
|
||||
else cgexpr_int(c, 0);
|
||||
}
|
||||
for (int i = total - 1; i >= 0; i--)
|
||||
ins1(c, A_POPQ, areg(tuple_rseq[i]));
|
||||
} else if (n->lhs) {
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
} else {
|
||||
@@ -7458,105 +7464,104 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
case N_MLET: {
|
||||
/* eval rhs; consume the per-type return-ABI registers.
|
||||
* (scalar, scalar) — AX → l0, DX → l1.
|
||||
* (scalar, str) — AX → scalar slot, (DX, CX, R8) → str slot
|
||||
* as (.ptr, .len, .cap). Position-agnostic.
|
||||
* Local sizing comes from each l->type so the str slot gets
|
||||
* the full 24B; without this, only DX would land and the
|
||||
* len/cap halves (CX/R8) would have nowhere to go.
|
||||
* str IS []u8 (24B): the cap rides R8 (#1/Phase 3, task #5).
|
||||
* one-str only; two-str destructure is gap (task #22). */
|
||||
/* #83: positional per-element destructure store. The rhs left
|
||||
* each tuple element in the register-return cursor (see N_RETURN
|
||||
* / harec create_unpack_bindings, ref/harec/src/check.c:1354-1416);
|
||||
* walk the bindings over the SAME cursor and store each at its
|
||||
* own width — a slice/str's 3-word {ptr,len,cap} header
|
||||
* (ref/hare/rt/ensure.ha:4-8) into a header-sized slot (sized
|
||||
* from u->size so #1 propagates), a scalar's 1 word into an 8B
|
||||
* slot. Over-capacity is a loud stop, not a silent drop. */
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
Node *l0 = n->list;
|
||||
Node *l1 = l0 ? l0->next : NULL;
|
||||
Type *t0 = l0 ? l0->type : NULL;
|
||||
Type *t1 = l1 ? l1->type : NULL;
|
||||
Type *u0 = (t0 && t0->kind == TY_NAMED) ? t0->under : t0;
|
||||
Type *u1 = (t1 && t1->kind == TY_NAMED) ? t1->under : t1;
|
||||
int s0_is_str = u0 && u0->kind == TY_STR;
|
||||
int s1_is_str = u1 && u1->kind == TY_STR;
|
||||
if (l0 && l1 && (s0_is_str ^ s1_is_str)) {
|
||||
/* #60: route str-slot width through ty_str->size so #1
|
||||
* propagates here. Scalar side keeps the 8B slot. */
|
||||
int sz0 = s0_is_str ? (int)u0->size : 8;
|
||||
int sz1 = s1_is_str ? (int)u1->size : 8;
|
||||
int off0 = localoff(c, locals, l0->str, sz0, frame);
|
||||
int off1 = localoff(c, locals, l1->str, sz1, frame);
|
||||
if (s0_is_str) {
|
||||
/* l0 is str: ptr=DX, len=CX, cap=R8. l1 scalar = AX. */
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off0 + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off0 + 8));
|
||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off0 + 16));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off1));
|
||||
int cap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||
int total = 0;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *t = l->type;
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
||||
total += tuple_ebytes(wide);
|
||||
}
|
||||
if (total > cap)
|
||||
fatal("tuple destructure exceeds register-return ABI "
|
||||
"capacity (%d eightbytes); see return-ABI #10", cap);
|
||||
int cur = 0;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *t = l->type;
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
||||
int sz = wide ? (int)u->size : 8;
|
||||
int off = localoff(c, locals, l->str, sz, frame);
|
||||
if (wide) {
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 0]),
|
||||
amem(D_BP, off + 0)); /* .ptr */
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 1]),
|
||||
amem(D_BP, off + 8)); /* .len */
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 2]),
|
||||
amem(D_BP, off + 16)); /* .cap */
|
||||
} else {
|
||||
/* l0 is scalar; l1 is str. */
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off0));
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off1 + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off1 + 8));
|
||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off1 + 16));
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur]),
|
||||
amem(D_BP, off));
|
||||
}
|
||||
break;
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_DX)); /* save 2nd while we store 1st */
|
||||
if (l0) {
|
||||
int off = localoff(c, locals, l0->str, 8, frame);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
||||
}
|
||||
ins1(c, A_POPQ, areg(D_DX));
|
||||
if (l1) {
|
||||
int off = localoff(c, locals, l1->str, 8, frame);
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off));
|
||||
cur += tuple_ebytes(wide);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case N_MASSIGN: {
|
||||
/* #83: positional per-element destructure REASSIGN. Same cursor
|
||||
* as N_MLET (and N_RETURN; harec create_unpack_bindings,
|
||||
* ref/harec/src/check.c:1354-1416), but the slots already exist
|
||||
* (reassignment) so localfind them. Element WIDTH comes from the
|
||||
* rhs tuple's element types (n->rhs->type->params) — the SAME
|
||||
* producer source the SEND site walks and wwstage reads via the
|
||||
* callee return type — NOT the binding type: a `_` lvalue is an
|
||||
* N_IDENT with empty str the checker never type-stamps (it skips
|
||||
* cexpr on `_`, cmd/wcc/check.c N_MASSIGN), so a binding-typed
|
||||
* width would mis-size a wide `_` and DESYNC the cursor for the
|
||||
* next element. harec `_` skips the store but CONSUMES its tuple
|
||||
* offset; the cursor advance below honours that. A wide element's
|
||||
* 3-word {ptr,len,cap} header (ref/hare/rt/ensure.ha:4-8) is stored
|
||||
* at its slot. 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 is a loud stop, not a silent drop. */
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
Node *l0 = n->list;
|
||||
Node *l1 = l0 ? l0->next : NULL;
|
||||
/* 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 N_MLET's (DX,CX,R8)→(.ptr,.len,.cap)
|
||||
* routing; the bare scalar fallback below would store only DX
|
||||
* and drop len/cap. Scalar side stays AX→slot. */
|
||||
Type *t0 = l0 ? l0->type : NULL;
|
||||
Type *t1 = l1 ? l1->type : NULL;
|
||||
Type *u0 = (t0 && t0->kind == TY_NAMED) ? t0->under : t0;
|
||||
Type *u1 = (t1 && t1->kind == TY_NAMED) ? t1->under : t1;
|
||||
int s0_is_str = u0 && u0->kind == TY_STR;
|
||||
int s1_is_str = u1 && u1->kind == TY_STR;
|
||||
if (l0 && l1 && l0->kind == N_IDENT && l1->kind == N_IDENT
|
||||
&& (s0_is_str ^ s1_is_str)) {
|
||||
int off0 = localfind(*locals, l0->str);
|
||||
int off1 = localfind(*locals, l1->str);
|
||||
if (off0 != 0 && off1 != 0) {
|
||||
if (s0_is_str) {
|
||||
/* l0 is str: ptr=DX, len=CX, cap=R8. l1 scalar = AX. */
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off0 + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off0 + 8));
|
||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off0 + 16));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off1));
|
||||
Type *rt = n->rhs ? n->rhs->type : NULL;
|
||||
Type *ru = (rt && rt->kind == TY_NAMED) ? rt->under : rt;
|
||||
Tparam *tp0 = (ru && ru->kind == TY_TUPLE) ? ru->params : NULL;
|
||||
int cap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||
int total = 0;
|
||||
for (Tparam *tp = tp0; tp; tp = tp->next) {
|
||||
Type *u = (tp->type && tp->type->kind == TY_NAMED)
|
||||
? tp->type->under : tp->type;
|
||||
total += tuple_ebytes(u && (u->kind == TY_SLICE
|
||||
|| u->kind == TY_STR));
|
||||
}
|
||||
if (total > cap)
|
||||
fatal("tuple destructure exceeds register-return ABI "
|
||||
"capacity (%d eightbytes); see return-ABI #10", cap);
|
||||
int cur = 0;
|
||||
Tparam *tp = tp0;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *et = tp ? tp->type : NULL;
|
||||
Type *u = (et && et->kind == TY_NAMED) ? et->under : et;
|
||||
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
||||
int off = (l->kind == N_IDENT)
|
||||
? localfind(*locals, l->str) : 0;
|
||||
if (off != 0) {
|
||||
if (wide) {
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 0]),
|
||||
amem(D_BP, off + 0)); /* .ptr */
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 1]),
|
||||
amem(D_BP, off + 8)); /* .len */
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 2]),
|
||||
amem(D_BP, off + 16)); /* .cap */
|
||||
} else {
|
||||
/* l0 is scalar; l1 is str. */
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off0));
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off1 + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off1 + 8));
|
||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off1 + 16));
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur]),
|
||||
amem(D_BP, off));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_DX));
|
||||
if (l0 && l0->kind == N_IDENT) {
|
||||
int off = localfind(*locals, l0->str);
|
||||
if (off != 0)
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
||||
}
|
||||
ins1(c, A_POPQ, areg(D_DX));
|
||||
if (l1 && l1->kind == N_IDENT) {
|
||||
int off = localfind(*locals, l1->str);
|
||||
if (off != 0)
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off));
|
||||
cur += tuple_ebytes(wide);
|
||||
if (tp) tp = tp->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user