A multi-float tuple return mis-routed: SEND pushed a stale AX leaving the float stranded in X0, while RECV (#105) read every float from X0 — so a (f64,f64) return collided both floats. Add an SSE cursor [X0,X1] parallel to the GP cursor [AX,DX,CX,R8], placing each element by its SysV class + within-class index (ref/qbe/amd64/sysv.c retr), symmetric send/recv across both stages, via a generic tuple_store/tupstore+tupsse helper that #171 will reuse for struct-return convergence. (f64,f64,f64) = 3 SSE eightbytes exceeds the 2-register cap and now fails loud (rule 7) rather than colliding. Unifying the 16B and 32B whole-tuple-single-var branches onto the dual cursor was required for f64+str coexistence; it also fixes a latent str-first single-var bug (the old 32B branch read .ptr from DX while the send placed it in AX). No str-first or 32B tuple exists in-tree, so integer paths stay byte-identical (990-997 green).
This commit is contained in:
348
cmd/w6c/cgen.c
348
cmd/w6c/cgen.c
@@ -39,6 +39,13 @@ static int *cg_frame;
|
|||||||
* semantics for synthetic scratches). 0 means "not yet allocated";
|
* semantics for synthetic scratches). 0 means "not yet allocated";
|
||||||
* negative offsets returned by local_alloc are the live value. */
|
* negative offsets returned by local_alloc are the live value. */
|
||||||
static int cg_retscr;
|
static int cg_retscr;
|
||||||
|
/* Per-fn @tupfscr offset (single-slot SSoT). A multi-float tuple return
|
||||||
|
* (#164/#107) spills each float out of X0 to this scratch as the L→R
|
||||||
|
* element walk clobbers X0, then reloads X0/X1 by SSE index after the
|
||||||
|
* integer POPQ dance. Sized to the SSE register cap (X0,X1). Mirrors the
|
||||||
|
* @retscr single-slot convention + wwstage's `@tupfscr` '@'-prefix dedup;
|
||||||
|
* 0 means "not yet allocated". */
|
||||||
|
static int cg_tupfscr;
|
||||||
/* Per-fn @-prefix scratch SSoT (task #26, follow-up to #15-cstage's
|
/* Per-fn @-prefix scratch SSoT (task #26, follow-up to #15-cstage's
|
||||||
* @retscr). Pre-#26 each site allocated a labelseq-stamped fresh slot
|
* @retscr). Pre-#26 each site allocated a labelseq-stamped fresh slot
|
||||||
* per call (mklabel "tagbase" / "tagscr" / "argscr" / "idxscr"); the
|
* per call (mklabel "tagbase" / "tagscr" / "argscr" / "idxscr"); the
|
||||||
@@ -192,6 +199,17 @@ node_isslice(Node *n)
|
|||||||
* create_unpack_bindings element walk (ref/harec/src/check.c:1354-1416). */
|
* 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 const int tuple_rseq[] = { D_AX, D_DX, D_CX, D_R8 };
|
||||||
|
|
||||||
|
/* #164 (#107): SysV dual register-class return. A tuple (and, per #171,
|
||||||
|
* a struct) return places each element by SysV class — a float rides the
|
||||||
|
* SSE row [X0,X1], everything else the INTEGER row [AX,DX,CX,R8]
|
||||||
|
* (tuple_rseq) — with the two rows advancing on INDEPENDENT counters, so
|
||||||
|
* a float lands in the next XMM regardless of its positional slot
|
||||||
|
* (ref/qbe/amd64/sysv.c retr L95-108, retreg={{RAX,RDX},{XMM0,XMM1}}).
|
||||||
|
* ww extends the INTEGER row to 4 eightbytes; the SSE row keeps SysV's 2.
|
||||||
|
* tuple_store is the shared per-element receive lowering so the struct-
|
||||||
|
* return convergence (#171) is a call-site swap, not a redesign. */
|
||||||
|
static const int tuple_sse_seq[] = { D_X0, D_X1 };
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tuple_ebytes(int wide)
|
tuple_ebytes(int wide)
|
||||||
{
|
{
|
||||||
@@ -1242,6 +1260,36 @@ ins1(Cg *c, int op, Adr to)
|
|||||||
emit(c, p);
|
emit(c, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* tuple_store — store one received tuple element at BP-relative `off`
|
||||||
|
* from its SysV-class register. A slice/str rides its 3-word
|
||||||
|
* {ptr,len,cap} header from the INTEGER cursor tuple_rseq[gp..]; a float
|
||||||
|
* rides tuple_sse_seq[sse] via MOVSD/MOVSS (#105 single-float widened to
|
||||||
|
* the SSE cursor for #164/#107 multi-float); a scalar rides one INTEGER
|
||||||
|
* word from tuple_rseq[gp]. The caller owns the dual cursor (validated +
|
||||||
|
* advanced); this just emits the store. Shared by N_LET/N_MLET/N_MASSIGN
|
||||||
|
* and, per #171, struct unpack — mirrors wwstage cgenstmt.ww tupstore. */
|
||||||
|
static void
|
||||||
|
tuple_store(Cg *c, Type *t, int wide, int gp, int sse, int off)
|
||||||
|
{
|
||||||
|
int f32 = 0;
|
||||||
|
|
||||||
|
if (wide) {
|
||||||
|
ins2(c, A_MOVQ, areg(tuple_rseq[gp + 0]),
|
||||||
|
amem(D_BP, off + 0)); /* .ptr */
|
||||||
|
ins2(c, A_MOVQ, areg(tuple_rseq[gp + 1]),
|
||||||
|
amem(D_BP, off + 8)); /* .len */
|
||||||
|
ins2(c, A_MOVQ, areg(tuple_rseq[gp + 2]),
|
||||||
|
amem(D_BP, off + 16)); /* .cap */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fld_isfloat(t, &f32)) {
|
||||||
|
ins2(c, f32 ? A_MOVSS : A_MOVSD, areg(tuple_sse_seq[sse]),
|
||||||
|
amem(D_BP, off));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ins2(c, A_MOVQ, areg(tuple_rseq[gp]), amem(D_BP, off));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ins0(Cg *c, int op)
|
ins0(Cg *c, int op)
|
||||||
{
|
{
|
||||||
@@ -7326,69 +7374,39 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
|
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* 2-tuple initialiser from a function call. An integer word
|
/* Tuple initialiser from a function call (#105 / #164/#107),
|
||||||
* rides its tuple_rseq[] reg (AX, DX); a single f64/f32 word
|
* 16B (two eightbytes) or 32B (scalar/float + slice/str header).
|
||||||
* rides X0, the SSE return reg — the RETURN leaves the float
|
* Each element rides its SysV class: a float its SSE cursor reg
|
||||||
* in X0 and pushes garbage through that word's integer slot,
|
* (X0,X1 = tuple_sse_seq), an integer/ptr word its INTEGER cursor
|
||||||
* so a blanket MOVQ-from-integer spill stores garbage and the
|
* reg (tuple_rseq), a slice/str its 3-word {ptr,len,cap} header
|
||||||
* #103-FACE-Z field read (MOVSD-from-slot) reads it (#105).
|
* over consecutive INTEGER cursor regs — INDEPENDENT counters,
|
||||||
* Spill each word from its real class. Multi-float tuples
|
* so the RETURN leaves floats in X0/X1 and integer words in
|
||||||
* collide on X0 at the RETURN (#107), out of scope here. */
|
* AX/DX/CX/R8. A blanket MOVQ spill would store garbage where a
|
||||||
if (n->rhs && lu && lu->kind == TY_TUPLE && sz == 16) {
|
* float rode and the #103-FACE-Z field read (MOVSD-from-slot)
|
||||||
Tparam *p0 = lu->params;
|
* would see it. tuple_store routes each element from its real
|
||||||
Tparam *p1 = p0 ? p0->next : NULL;
|
* class into its positional slot (eoff steps by the element's
|
||||||
int f0_f32 = 0, f1_f32 = 0;
|
* slot size: a slice/str takes its 24B header); the same split
|
||||||
int e0_f = p0 && fld_isfloat(p0->type, &f0_f32);
|
* drives the destructure / reassign sites. */
|
||||||
int e1_f = p1 && fld_isfloat(p1->type, &f1_f32);
|
if (n->rhs && lu && lu->kind == TY_TUPLE
|
||||||
|
&& (sz == 16 || sz == 32)) {
|
||||||
cgexpr(c, n->rhs, *locals);
|
cgexpr(c, n->rhs, *locals);
|
||||||
if (e0_f)
|
int gpcur = 0, ssecur = 0, eoff = 0, ef32;
|
||||||
ins2(c, f0_f32 ? A_MOVSS : A_MOVSD,
|
for (Tparam *p = lu->params; p; p = p->next) {
|
||||||
areg(D_X0), amem(D_BP, off + 0));
|
Type *pu = (p->type && p->type->kind == TY_NAMED)
|
||||||
|
? p->type->under : p->type;
|
||||||
|
int wide = pu && (pu->kind == TY_SLICE
|
||||||
|
|| pu->kind == TY_STR);
|
||||||
|
int isflt = fld_isfloat(p->type, &ef32);
|
||||||
|
tuple_store(c, p->type, wide, gpcur, ssecur,
|
||||||
|
off + eoff);
|
||||||
|
if (isflt)
|
||||||
|
ssecur++;
|
||||||
else
|
else
|
||||||
ins2(c, A_MOVQ, areg(tuple_rseq[0]),
|
gpcur += tuple_ebytes(wide);
|
||||||
amem(D_BP, off + 0));
|
eoff += wide ? (int)pu->size : 8;
|
||||||
if (e1_f)
|
|
||||||
ins2(c, f1_f32 ? A_MOVSS : A_MOVSD,
|
|
||||||
areg(D_X0), amem(D_BP, off + 8));
|
|
||||||
else
|
|
||||||
ins2(c, A_MOVQ, areg(tuple_rseq[1]),
|
|
||||||
amem(D_BP, off + 8));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* 32B tuple initialiser for `(scalar, str)` / `(str, scalar)`.
|
|
||||||
* Per the AX:DX:CX:R8 return convention: AX = scalar elem,
|
|
||||||
* DX = str.ptr, CX = str.len, R8 = str.cap. The slot is laid
|
|
||||||
* out positionally (str takes 24B at its position), so we route
|
|
||||||
* each register to the slot dictated by the element's type, not
|
|
||||||
* by AX/DX position. str IS []u8 (24B) → 32B tuple (#1/Phase 3,
|
|
||||||
* task #5). */
|
|
||||||
if (n->rhs && lu && lu->kind == TY_TUPLE && sz == 32) {
|
|
||||||
Tparam *p0 = lu->params;
|
|
||||||
Tparam *p1 = p0 ? p0->next : NULL;
|
|
||||||
Type *t0 = p0 ? p0->type : NULL;
|
|
||||||
Type *t1 = p1 ? p1->type : NULL;
|
|
||||||
Type *u0 = (t0 && t0->kind == TY_NAMED) ? t0->under : t0;
|
|
||||||
Type *u1 = (t1 && t1->kind == TY_NAMED) ? t1->under : t1;
|
|
||||||
int e0_str = u0 && u0->kind == TY_STR;
|
|
||||||
int e1_str = u1 && u1->kind == TY_STR;
|
|
||||||
if (e0_str ^ e1_str) {
|
|
||||||
cgexpr(c, n->rhs, *locals);
|
|
||||||
if (e0_str) {
|
|
||||||
/* layout: str@+0 (24B), scalar@+24. */
|
|
||||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 0));
|
|
||||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 8));
|
|
||||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off + 16));
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 24));
|
|
||||||
} else {
|
|
||||||
/* layout: scalar@+0 (8B), str@+8 (24B). */
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
||||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
|
||||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
|
|
||||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off + 24));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
/* Tagged-union initialiser. Delegates to cg_widen_tagged_store,
|
/* Tagged-union initialiser. Delegates to cg_widen_tagged_store,
|
||||||
* which handles nullable fold, tagged→tagged (with tag remap
|
* which handles nullable fold, tagged→tagged (with tag remap
|
||||||
* when variant indices differ), struct payload (ident or
|
* when variant indices differ), struct payload (ident or
|
||||||
@@ -8050,37 +8068,79 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (n->lhs && n->lhs->kind == N_TUPLE) {
|
if (n->lhs && n->lhs->kind == N_TUPLE) {
|
||||||
/* #83: positional per-element register-return. Walk the
|
/* #83 / #164 (#107): positional register-return over a SysV
|
||||||
* tuple's elements (harec create_unpack_bindings,
|
* dual class cursor. Each element rides its SysV class
|
||||||
* ref/harec/src/check.c:1354-1416); each rides consecutive
|
* (harec create_unpack_bindings, ref/harec/src/check.c:1354-
|
||||||
* eightbytes over tuple_rseq[]. A slice/str rides its 3-word
|
* 1416): a float takes one SSE eightbyte (X0,X1 = tuple_sse_
|
||||||
* {ptr,len,cap} header (ref/hare/rt/ensure.ha:4-8), cgexpr
|
* seq), everything else INTEGER eightbytes over tuple_rseq —
|
||||||
* leaving it in (AX,BX,CX); a scalar rides 1 word in AX.
|
* a slice/str its 3-word {ptr,len,cap} header (ref/hare/rt/
|
||||||
* Spill each element's word(s) L→R, then pop into the
|
* ensure.ha:4-8) cgexpr leaves in (AX,BX,CX), a scalar 1 word
|
||||||
* cursor's registers in reverse so positional slot i lands in
|
* in AX. Integer words spill L→R to the stack and pop into the
|
||||||
* tuple_rseq[i] — (scalar,str) keeps the historical AX +
|
* INTEGER cursor in reverse so positional slot i lands in
|
||||||
* DX,CX,R8 layout, and the SAME cursor drives the receive
|
* tuple_rseq[i] (byte-id with #83 when no float is present).
|
||||||
* sites. Over-capacity is a loud stop (return-ABI #10), never
|
* Each float must spill X0 to @tupfscr as we walk, because a
|
||||||
* a silent drop. */
|
* later element's cgexpr clobbers X0; after the integer pops
|
||||||
int cap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
* the saved floats reload into X0/X1 by SSE index — INDEPENDENT
|
||||||
int total = 0;
|
* of the integer cursor (ref/qbe/amd64/sysv.c retr L95-108).
|
||||||
for (Node *e = n->lhs->list; e; e = e->next)
|
* Both rows are loud-stopped at their cap (rule-7, never a
|
||||||
total += tuple_ebytes(node_isstr(e) || node_isslice(e));
|
* silent collide): INTEGER 4, SSE 2. The SAME class split
|
||||||
if (total > cap)
|
* drives the receive sites. */
|
||||||
fatal("tuple return exceeds register-return ABI "
|
int gpcap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||||
"capacity (%d eightbytes); see return-ABI #10",
|
int ssecap = (int)(sizeof tuple_sse_seq
|
||||||
cap);
|
/ sizeof tuple_sse_seq[0]);
|
||||||
|
int gptotal = 0, ssecount = 0, f32;
|
||||||
for (Node *e = n->lhs->list; e; e = e->next) {
|
for (Node *e = n->lhs->list; e; e = e->next) {
|
||||||
int wide = node_isstr(e) || node_isslice(e);
|
if (fld_isfloat(e->type, &f32))
|
||||||
cgexpr(c, e, *locals); /* scalar=AX; slice/str=AX,BX,CX */
|
ssecount++;
|
||||||
|
else
|
||||||
|
gptotal += tuple_ebytes(node_isstr(e)
|
||||||
|
|| node_isslice(e));
|
||||||
|
}
|
||||||
|
if (gptotal > gpcap)
|
||||||
|
fatal("tuple return exceeds integer register-return "
|
||||||
|
"ABI capacity (%d eightbytes: AX,DX,CX,R8); "
|
||||||
|
"see return-ABI #10", gpcap);
|
||||||
|
if (ssecount > ssecap)
|
||||||
|
fatal("tuple return exceeds SSE register-return ABI "
|
||||||
|
"capacity (%d eightbytes: X0,X1); "
|
||||||
|
"see return-ABI #10", ssecap);
|
||||||
|
int fscr = 0;
|
||||||
|
if (ssecount > 0) {
|
||||||
|
if (cg_tupfscr != 0)
|
||||||
|
fscr = cg_tupfscr;
|
||||||
|
else {
|
||||||
|
fscr = local_alloc(c, locals, "@tupfscr",
|
||||||
|
ssecap * 8, cg_frame);
|
||||||
|
cg_tupfscr = fscr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int sseidx = 0;
|
||||||
|
for (Node *e = n->lhs->list; e; e = e->next) {
|
||||||
|
int isflt = fld_isfloat(e->type, &f32);
|
||||||
|
cgexpr(c, e, *locals); /* scalar=AX; slice/str=AX,BX,CX; float=X0 */
|
||||||
|
if (isflt) {
|
||||||
|
ins2(c, f32 ? A_MOVSS : A_MOVSD, areg(D_X0),
|
||||||
|
amem(D_BP, fscr + sseidx * 8));
|
||||||
|
sseidx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ins1(c, A_PUSHQ, areg(D_AX)); /* scalar / .ptr */
|
ins1(c, A_PUSHQ, areg(D_AX)); /* scalar / .ptr */
|
||||||
if (wide) {
|
if (node_isstr(e) || node_isslice(e)) {
|
||||||
ins1(c, A_PUSHQ, areg(D_BX)); /* .len */
|
ins1(c, A_PUSHQ, areg(D_BX)); /* .len */
|
||||||
ins1(c, A_PUSHQ, areg(D_CX)); /* .cap */
|
ins1(c, A_PUSHQ, areg(D_CX)); /* .cap */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = total - 1; i >= 0; i--)
|
for (int i = gptotal - 1; i >= 0; i--)
|
||||||
ins1(c, A_POPQ, areg(tuple_rseq[i]));
|
ins1(c, A_POPQ, areg(tuple_rseq[i]));
|
||||||
|
int j = 0;
|
||||||
|
for (Node *e = n->lhs->list; e; e = e->next) {
|
||||||
|
if (!fld_isfloat(e->type, &f32))
|
||||||
|
continue;
|
||||||
|
ins2(c, f32 ? A_MOVSS : A_MOVSD,
|
||||||
|
amem(D_BP, fscr + j * 8),
|
||||||
|
areg(tuple_sse_seq[j]));
|
||||||
|
j++;
|
||||||
|
}
|
||||||
} else if (n->lhs) {
|
} else if (n->lhs) {
|
||||||
cgexpr(c, n->lhs, *locals);
|
cgexpr(c, n->lhs, *locals);
|
||||||
} else {
|
} else {
|
||||||
@@ -8263,55 +8323,51 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case N_MLET: {
|
case N_MLET: {
|
||||||
/* #83: positional per-element destructure store. The rhs left
|
/* #83 / #164 (#107): positional per-element destructure store.
|
||||||
* each tuple element in the register-return cursor (see N_RETURN
|
* The rhs left each tuple element in its SysV-class register
|
||||||
* / harec create_unpack_bindings, ref/harec/src/check.c:1354-1416);
|
* (see N_RETURN / harec create_unpack_bindings, ref/harec/src/
|
||||||
* walk the bindings over the SAME cursor and store each at its
|
* check.c:1354-1416); walk the bindings over the SAME dual
|
||||||
* own width — a slice/str's 3-word {ptr,len,cap} header
|
* cursor and store each at its own width — a slice/str's 3-word
|
||||||
* (ref/hare/rt/ensure.ha:4-8) into a header-sized slot (sized
|
* {ptr,len,cap} header (ref/hare/rt/ensure.ha:4-8) into a
|
||||||
* from u->size so #1 propagates), a scalar's 1 word into an 8B
|
* header-sized slot (sized from u->size so #1 propagates), a
|
||||||
* slot. Over-capacity is a loud stop, not a silent drop. */
|
* float from X0/X1 (SSE cursor), a scalar's 1 word from the
|
||||||
|
* INTEGER cursor into an 8B slot. Both rows loud-stop at their
|
||||||
|
* cap. */
|
||||||
cgexpr(c, n->rhs, *locals);
|
cgexpr(c, n->rhs, *locals);
|
||||||
int cap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
int gpcap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||||
int total = 0;
|
int ssecap = (int)(sizeof tuple_sse_seq
|
||||||
|
/ sizeof tuple_sse_seq[0]);
|
||||||
|
int gptotal = 0, ssetotal = 0, lf32;
|
||||||
for (Node *l = n->list; l; l = l->next) {
|
for (Node *l = n->list; l; l = l->next) {
|
||||||
Type *t = l->type;
|
Type *t = l->type;
|
||||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||||
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
if (fld_isfloat(t, &lf32))
|
||||||
total += tuple_ebytes(wide);
|
ssetotal++;
|
||||||
|
else
|
||||||
|
gptotal += tuple_ebytes(u && (u->kind == TY_SLICE
|
||||||
|
|| u->kind == TY_STR));
|
||||||
}
|
}
|
||||||
if (total > cap)
|
if (gptotal > gpcap)
|
||||||
fatal("tuple destructure exceeds register-return ABI "
|
fatal("tuple destructure exceeds integer register-return "
|
||||||
"capacity (%d eightbytes); see return-ABI #10", cap);
|
"ABI capacity (%d eightbytes: AX,DX,CX,R8); "
|
||||||
int cur = 0;
|
"see return-ABI #10", gpcap);
|
||||||
|
if (ssetotal > ssecap)
|
||||||
|
fatal("tuple destructure exceeds SSE register-return ABI "
|
||||||
|
"capacity (%d eightbytes: X0,X1); see return-ABI #10",
|
||||||
|
ssecap);
|
||||||
|
int gpcur = 0, ssecur = 0;
|
||||||
for (Node *l = n->list; l; l = l->next) {
|
for (Node *l = n->list; l; l = l->next) {
|
||||||
Type *t = l->type;
|
Type *t = l->type;
|
||||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||||
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
||||||
|
int isflt = fld_isfloat(t, &lf32);
|
||||||
int sz = wide ? (int)u->size : 8;
|
int sz = wide ? (int)u->size : 8;
|
||||||
int off = localoff(c, locals, l->str, sz, frame);
|
int off = localoff(c, locals, l->str, sz, frame);
|
||||||
if (wide) {
|
tuple_store(c, t, wide, gpcur, ssecur, off);
|
||||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 0]),
|
if (isflt)
|
||||||
amem(D_BP, off + 0)); /* .ptr */
|
ssecur++;
|
||||||
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 {
|
|
||||||
/* #105: an f64/f32 element rides X0, not its
|
|
||||||
* integer cursor reg — MOVSD/MOVSS it, else
|
|
||||||
* the slot gets garbage and the FACE-Z field
|
|
||||||
* read sees it. X0 survives the reg->mem
|
|
||||||
* stores. Single-float scope; #107 is multi. */
|
|
||||||
int e_f32 = 0;
|
|
||||||
if (fld_isfloat(t, &e_f32))
|
|
||||||
ins2(c, e_f32 ? A_MOVSS : A_MOVSD,
|
|
||||||
areg(D_X0), amem(D_BP, off));
|
|
||||||
else
|
else
|
||||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur]),
|
gpcur += tuple_ebytes(wide);
|
||||||
amem(D_BP, off));
|
|
||||||
}
|
|
||||||
cur += tuple_ebytes(wide);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -8337,47 +8393,44 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
Type *rt = n->rhs ? n->rhs->type : NULL;
|
Type *rt = n->rhs ? n->rhs->type : NULL;
|
||||||
Type *ru = (rt && rt->kind == TY_NAMED) ? rt->under : rt;
|
Type *ru = (rt && rt->kind == TY_NAMED) ? rt->under : rt;
|
||||||
Tparam *tp0 = (ru && ru->kind == TY_TUPLE) ? ru->params : NULL;
|
Tparam *tp0 = (ru && ru->kind == TY_TUPLE) ? ru->params : NULL;
|
||||||
int cap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
int gpcap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||||
int total = 0;
|
int ssecap = (int)(sizeof tuple_sse_seq
|
||||||
|
/ sizeof tuple_sse_seq[0]);
|
||||||
|
int gptotal = 0, ssetotal = 0, mf32;
|
||||||
for (Tparam *tp = tp0; tp; tp = tp->next) {
|
for (Tparam *tp = tp0; tp; tp = tp->next) {
|
||||||
Type *u = (tp->type && tp->type->kind == TY_NAMED)
|
Type *u = (tp->type && tp->type->kind == TY_NAMED)
|
||||||
? tp->type->under : tp->type;
|
? tp->type->under : tp->type;
|
||||||
total += tuple_ebytes(u && (u->kind == TY_SLICE
|
if (fld_isfloat(tp->type, &mf32))
|
||||||
|
ssetotal++;
|
||||||
|
else
|
||||||
|
gptotal += tuple_ebytes(u && (u->kind == TY_SLICE
|
||||||
|| u->kind == TY_STR));
|
|| u->kind == TY_STR));
|
||||||
}
|
}
|
||||||
if (total > cap)
|
if (gptotal > gpcap)
|
||||||
fatal("tuple destructure exceeds register-return ABI "
|
fatal("tuple destructure exceeds integer register-return "
|
||||||
"capacity (%d eightbytes); see return-ABI #10", cap);
|
"ABI capacity (%d eightbytes: AX,DX,CX,R8); "
|
||||||
int cur = 0;
|
"see return-ABI #10", gpcap);
|
||||||
|
if (ssetotal > ssecap)
|
||||||
|
fatal("tuple destructure exceeds SSE register-return ABI "
|
||||||
|
"capacity (%d eightbytes: X0,X1); see return-ABI #10",
|
||||||
|
ssecap);
|
||||||
|
int gpcur = 0, ssecur = 0;
|
||||||
Tparam *tp = tp0;
|
Tparam *tp = tp0;
|
||||||
for (Node *l = n->list; l; l = l->next) {
|
for (Node *l = n->list; l; l = l->next) {
|
||||||
Type *et = tp ? tp->type : NULL;
|
Type *et = tp ? tp->type : NULL;
|
||||||
Type *u = (et && et->kind == TY_NAMED) ? et->under : et;
|
Type *u = (et && et->kind == TY_NAMED) ? et->under : et;
|
||||||
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
||||||
|
int isflt = fld_isfloat(et, &mf32);
|
||||||
int off = (l->kind == N_IDENT)
|
int off = (l->kind == N_IDENT)
|
||||||
? localfind(*locals, l->str) : 0;
|
? localfind(*locals, l->str) : 0;
|
||||||
if (off != 0) {
|
/* harec `_` (off==0): skip the store but CONSUME the
|
||||||
if (wide) {
|
* cursor slot so the next element stays aligned. */
|
||||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 0]),
|
if (off != 0)
|
||||||
amem(D_BP, off + 0)); /* .ptr */
|
tuple_store(c, et, wide, gpcur, ssecur, off);
|
||||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 1]),
|
if (isflt)
|
||||||
amem(D_BP, off + 8)); /* .len */
|
ssecur++;
|
||||||
ins2(c, A_MOVQ, areg(tuple_rseq[cur + 2]),
|
|
||||||
amem(D_BP, off + 16)); /* .cap */
|
|
||||||
} else {
|
|
||||||
/* #105: f64/f32 element rides X0 (SSE),
|
|
||||||
* not its integer cursor reg — see N_MLET. */
|
|
||||||
int e_f32 = 0;
|
|
||||||
if (fld_isfloat(et, &e_f32))
|
|
||||||
ins2(c, e_f32 ? A_MOVSS : A_MOVSD,
|
|
||||||
areg(D_X0), amem(D_BP, off));
|
|
||||||
else
|
else
|
||||||
ins2(c, A_MOVQ,
|
gpcur += tuple_ebytes(wide);
|
||||||
areg(tuple_rseq[cur]),
|
|
||||||
amem(D_BP, off));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cur += tuple_ebytes(wide);
|
|
||||||
if (tp) tp = tp->next;
|
if (tp) tp = tp->next;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -8459,6 +8512,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
|||||||
nloops = 0;
|
nloops = 0;
|
||||||
cg_ret_type = fn->type ? fn->type->ret : NULL;
|
cg_ret_type = fn->type ? fn->type->ret : NULL;
|
||||||
cg_retscr = 0;
|
cg_retscr = 0;
|
||||||
|
cg_tupfscr = 0;
|
||||||
cg_tagbase = 0;
|
cg_tagbase = 0;
|
||||||
cg_tagbase_sz = 0;
|
cg_tagbase_sz = 0;
|
||||||
cg_tagscr = 0;
|
cg_tagscr = 0;
|
||||||
|
|||||||
@@ -24189,6 +24189,17 @@ fn tupreg(i: i32) str = {
|
|||||||
return "R8";
|
return "R8";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// #164 (#107): SSE half of the SysV dual register-class return. A float
|
||||||
|
// element rides the SSE row [X0,X1] on a counter INDEPENDENT of the
|
||||||
|
// INTEGER row tupreg — a float lands in the next XMM regardless of its
|
||||||
|
// positional slot (ref/qbe/amd64/sysv.c retr L95-108, retreg={{RAX,RDX},
|
||||||
|
// {XMM0,XMM1}}). SysV caps SSE returns at 2 eightbytes. Mirror of cstage
|
||||||
|
// tuple_sse_seq (cmd/w6c/cgen.c).
|
||||||
|
fn tupsse(i: i32) str = {
|
||||||
|
if (i == 0) { return "X0"; };
|
||||||
|
return "X1";
|
||||||
|
};
|
||||||
|
|
||||||
fn tupebytes(wide: bool) i32 = {
|
fn tupebytes(wide: bool) i32 = {
|
||||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||||
return 1;
|
return 1;
|
||||||
@@ -24229,31 +24240,34 @@ fn rettupleof(c: *cgen, rhs: *node) *node = {
|
|||||||
// tupstore — store the tuple element at register-cursor `cur` into the
|
// 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}
|
// 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
|
// 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
|
// INTEGER cursor registers; a float rides the SSE cursor (X0,X1); a
|
||||||
// N_MLET/N_MASSIGN store (cmd/w6c/cgen.c).
|
// scalar stores 1 INTEGER word. The caller owns the dual cursor
|
||||||
fn tupstore(c: *cgen, cur: i32, off: i32, wide: bool, tn: *node) void = {
|
// (validated + advanced). Byte-identical to the cstage tuple_store
|
||||||
|
// (cmd/w6c/cgen.c).
|
||||||
|
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node) void = {
|
||||||
if (wide) {
|
if (wide) {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 0));
|
emitline(tupreg(gpcur + 0));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 1));
|
emitline(tupreg(gpcur + 1));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 2));
|
emitline(tupreg(gpcur + 2));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff((off + 16): i64);
|
emitoff((off + 16): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// #105: an f64/f32 element rides X0 (the SSE return reg), not its
|
// #105 / #164 (#107): an f64/f32 element rides the SSE cursor reg
|
||||||
// integer cursor reg — MOVSD/MOVSS it, else the slot gets garbage and
|
// (X0,X1 = tupsse), not its INTEGER cursor reg — MOVSD/MOVSS it, else
|
||||||
// the FACE-Z field read sees it. X0 survives the reg->mem stores.
|
// the slot gets garbage and the FACE-Z field read sees it. The SSE
|
||||||
// Single-float scope; multi-float collides on X0 at RETURN (#107).
|
// regs survive the reg->mem stores. SSE-idx0=X0 keeps the #105
|
||||||
|
// single-float byte-id; idx1=X1 is the #107 multi-float extension.
|
||||||
if (isfloattype(c, tn)) {
|
if (isfloattype(c, tn)) {
|
||||||
// #121 (Package B) RESIDUAL sibling-evidence guard, pin form.
|
// #121 (Package B) RESIDUAL sibling-evidence guard, pin form.
|
||||||
// In destructure mode tn IS the tuple-element-type-AST node
|
// In destructure mode tn IS the tuple-element-type-AST node
|
||||||
@@ -24286,13 +24300,15 @@ fn tupstore(c: *cgen, cur: i32, off: i32, wide: bool, tn: *node) void = {
|
|||||||
if (isf32type(c, tn)) { mov = "MOVSS"; };
|
if (isf32type(c, tn)) { mov = "MOVSS"; };
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\t");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur));
|
emitline(tupreg(gpcur));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
@@ -24302,49 +24318,98 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
rundefers(c);
|
rundefers(c);
|
||||||
let rhs: *node = n.lhs;
|
let rhs: *node = n.lhs;
|
||||||
if (rhs != nil) {
|
if (rhs != nil) {
|
||||||
// #83: positional per-element register-return (harec
|
// #83 / #164 (#107): positional register-return over a SysV
|
||||||
// create_unpack_bindings, ref/harec/src/check.c:1354-1416). Each
|
// dual class cursor (harec create_unpack_bindings, ref/harec/src/
|
||||||
// element rides consecutive eightbytes over [AX,DX,CX,R8]
|
// check.c:1354-1416). A float takes one SSE eightbyte (X0,X1 =
|
||||||
// (tupreg); a slice/str rides its 3-word {ptr,len,cap} header
|
// tupsse), everything else INTEGER eightbytes over [AX,DX,CX,R8]
|
||||||
// (ref/hare/rt/ensure.ha:4-8), cgexpr leaving it in (AX,BX,CX); a
|
// (tupreg) — a slice/str its 3-word {ptr,len,cap} header
|
||||||
// scalar rides 1 word in AX. Spill each element L->R, then pop
|
// (ref/hare/rt/ensure.ha:4-8) cgexpr leaves in (AX,BX,CX), a
|
||||||
// into the cursor's registers in reverse so positional slot i
|
// scalar 1 word in AX. Integer words spill L->R to the stack and
|
||||||
// lands in tupreg(i) — (scalar,str) keeps the historical AX +
|
// pop into the INTEGER cursor in reverse so positional slot i
|
||||||
// DX,CX,R8. The SAME cursor drives the receive sites. Over-
|
// lands in tupreg(i) (byte-id with #83 when no float is present).
|
||||||
// capacity is a loud stop (return-ABI #10), never a silent drop.
|
// Each float must spill X0 to @tupfscr as we walk, since a later
|
||||||
|
// element's cgexpr clobbers X0; after the integer pops the saved
|
||||||
|
// floats reload into X0/X1 by SSE index — INDEPENDENT of the
|
||||||
|
// INTEGER cursor (ref/qbe/amd64/sysv.c retr L95-108). Both rows
|
||||||
|
// loud-stop at their cap (rule-7): INTEGER 4, SSE 2. The SAME
|
||||||
|
// class split drives the receive sites.
|
||||||
if (rhs.kind == nkind.N_TUPLE) {
|
if (rhs.kind == nkind.N_TUPLE) {
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssecount: i32 = 0;
|
||||||
let e: *node = rhs.list;
|
let e: *node = rhs.list;
|
||||||
for (e != nil) {
|
for (e != nil) {
|
||||||
|
if (isfloattype(c, e)) {
|
||||||
|
ssecount = ssecount + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
||||||
// uses fatal(), err.c) — surface, don't corrupt.
|
// 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";
|
let msg: str = "tuple return exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
if (ssecount > ssecap) {
|
||||||
|
let msg: str = "tuple return exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
let fscr: i32 = 0;
|
||||||
|
if (ssecount > 0) {
|
||||||
|
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
|
||||||
|
};
|
||||||
|
let sseidx: i32 = 0;
|
||||||
e = rhs.list;
|
e = rhs.list;
|
||||||
for (e != nil) {
|
for (e != nil) {
|
||||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
let isflt: bool = isfloattype(c, e);
|
||||||
cgexpr(c, e);
|
cgexpr(c, e);
|
||||||
|
if (isflt) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\tX0, ");
|
||||||
|
emitoff((fscr + sseidx * 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
sseidx = sseidx + 1;
|
||||||
|
} else {
|
||||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||||
if (wide) {
|
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||||
emitline("\tPUSHQ\tBX\n"); // .len
|
emitline("\tPUSHQ\tBX\n"); // .len
|
||||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||||
};
|
};
|
||||||
|
};
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
let i: i32 = total - 1;
|
let i: i32 = gptotal - 1;
|
||||||
for (i >= 0) {
|
for (i >= 0) {
|
||||||
emitline("\tPOPQ\t");
|
emitline("\tPOPQ\t");
|
||||||
emitline(tupreg(i));
|
emitline(tupreg(i));
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
i = i - 1;
|
i = i - 1;
|
||||||
};
|
};
|
||||||
|
let j: i32 = 0;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
if (isfloattype(c, e)) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\t");
|
||||||
|
emitoff((fscr + j * 8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupsse(j));
|
||||||
|
emitline("\n");
|
||||||
|
j = j + 1;
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
};
|
||||||
emitline("\tMOVQ\tBP, SP\n");
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -24943,12 +25008,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// 32B tuple init for `let t: (scalar, str) = call()` /
|
// 32B tuple init for `let t: (scalar, str) = call()` /
|
||||||
// `let t: (str, scalar) = call()`. Per the AX:DX:CX:R8 return
|
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
|
||||||
// convention: AX = scalar elem, DX = str.ptr, CX = str.len,
|
// element rides its SysV class — a float its SSE cursor reg
|
||||||
// R8 = str.cap. Layout is positional (str takes 24B at its
|
// (X0,X1 = tupsse), an integer/ptr word its INTEGER cursor reg
|
||||||
// position), so we route each register to the slot dictated by
|
// (tupreg), a slice/str its 3-word {ptr,len,cap} header over
|
||||||
// element type, not by AX/DX position. str IS []u8 (24B) → 32B
|
// consecutive INTEGER cursor regs — on INDEPENDENT counters.
|
||||||
// tuple (#1/Phase 3, task #5).
|
// tupstore routes each element from its real class into its
|
||||||
|
// positional slot (eoff steps by the element's slot size: a
|
||||||
|
// slice/str takes its 24B header). str IS []u8 (24B) → 32B tuple
|
||||||
|
// (#1/Phase 3, task #5). Mirror of the cstage unified branch.
|
||||||
if (n.lhs != nil) {
|
if (n.lhs != nil) {
|
||||||
if (n.lhs.kind == nkind.N_TTUPLE) {
|
if (n.lhs.kind == nkind.N_TTUPLE) {
|
||||||
let p0: *node = n.lhs.list;
|
let p0: *node = n.lhs.list;
|
||||||
@@ -24958,38 +25026,36 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let p1t: *node = nil;
|
let p1t: *node = nil;
|
||||||
if (p0 != nil) { p0t = p0.lhs; };
|
if (p0 != nil) { p0t = p0.lhs; };
|
||||||
if (p1 != nil) { p1t = p1.lhs; };
|
if (p1 != nil) { p1t = p1.lhs; };
|
||||||
let s0_is_str: bool = isstrtype(c, p0t);
|
let s0_is_str: bool = isstrtype(c, p0t)
|
||||||
let s1_is_str: bool = isstrtype(c, p1t);
|
|| isslicetype(c, p0t);
|
||||||
|
let s1_is_str: bool = isstrtype(c, p1t)
|
||||||
|
|| isslicetype(c, p1t);
|
||||||
if (p0 != nil) {
|
if (p0 != nil) {
|
||||||
if (p1 != nil) {
|
if (p1 != nil) {
|
||||||
if (s0_is_str != s1_is_str) {
|
if (s0_is_str != s1_is_str) {
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
if (s0_is_str) {
|
let gpcur: i32 = 0;
|
||||||
emitline("\tMOVQ\tDX, ");
|
let ssecur: i32 = 0;
|
||||||
emitoff(off: i64);
|
let eoff: i32 = 0;
|
||||||
emitline("(BP)\n");
|
let q: *node = n.lhs.list;
|
||||||
emitline("\tMOVQ\tCX, ");
|
for (q != nil) {
|
||||||
emitoff((off + 8): i64);
|
let qt: *node = q.lhs;
|
||||||
emitline("(BP)\n");
|
let isflt: bool = isfloattype(c, qt);
|
||||||
emitline("\tMOVQ\tR8, ");
|
let wide: bool = isstrtype(c, qt)
|
||||||
emitoff((off + 16): i64);
|
|| isslicetype(c, qt);
|
||||||
emitline("(BP)\n");
|
tupstore(c, gpcur, ssecur,
|
||||||
emitline("\tMOVQ\tAX, ");
|
off + eoff, wide, qt);
|
||||||
emitoff((off + 24): i64);
|
if (isflt) {
|
||||||
emitline("(BP)\n");
|
ssecur = ssecur + 1;
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
gpcur = gpcur + tupebytes(wide);
|
||||||
emitoff(off: i64);
|
};
|
||||||
emitline("(BP)\n");
|
if (wide) {
|
||||||
emitline("\tMOVQ\tDX, ");
|
eoff = eoff + (tyslicesize(): i32);
|
||||||
emitoff((off + 8): i64);
|
} else {
|
||||||
emitline("(BP)\n");
|
eoff = eoff + 8;
|
||||||
emitline("\tMOVQ\tCX, ");
|
};
|
||||||
emitoff((off + 16): i64);
|
q = q.next;
|
||||||
emitline("(BP)\n");
|
|
||||||
emitline("\tMOVQ\tR8, ");
|
|
||||||
emitoff((off + 24): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
@@ -24998,54 +25064,35 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// 16B tuple init from a function call. An integer word rides
|
// 16B tuple init from a function call (#105 / #164/#107). Each
|
||||||
// its integer cursor reg (AX, DX); a single f64/f32 word rides
|
// eightbyte rides its SysV class: a float its SSE cursor reg
|
||||||
// X0, the SSE return reg — the RETURN leaves the float in X0
|
// (X0,X1 = tupsse), an integer/ptr word its INTEGER cursor reg
|
||||||
// and pushes garbage through that word's integer slot, so a
|
// (AX,DX = tupreg), INDEPENDENT counters — the RETURN leaves
|
||||||
// blanket MOVQ-from-integer spill stores garbage and the #103-
|
// floats in X0/X1 and integer words in AX/DX, so a blanket MOVQ
|
||||||
// FACE-Z field read (MOVSD-from-slot) reads it (#105). Spill
|
// spill would store garbage where a float rode and the #103-
|
||||||
// each word from its real class. Multi-float tuples collide on
|
// FACE-Z field read (MOVSD-from-slot) would see it. tupstore
|
||||||
// X0 at the RETURN (#107), out of scope. Mirror of cstage
|
// routes each word from its real class; the same split drives
|
||||||
// cgen.c. Without this branch a 16B tuple receive (any element
|
// the destructure / reassign sites. Without this branch a 16B
|
||||||
// mix) fell to the generic single-word store below and dropped
|
// tuple receive fell to the generic single-word store below and
|
||||||
// word1 — silent loss of t.1 (#102).
|
// dropped word1 — silent loss of t.1 (#102).
|
||||||
let rt16: *node = rettupleof(c, rhs);
|
let rt16: *node = rettupleof(c, rhs);
|
||||||
if (rt16 != nil && sz == 16) {
|
if (rt16 != nil && sz == 16) {
|
||||||
let q0: *node = rt16.list;
|
|
||||||
let q1: *node = nil;
|
|
||||||
if (q0 != nil) { q1 = q0.next; };
|
|
||||||
let q0t: *node = nil;
|
|
||||||
let q1t: *node = nil;
|
|
||||||
if (q0 != nil) { q0t = q0.lhs; };
|
|
||||||
if (q1 != nil) { q1t = q1.lhs; };
|
|
||||||
let e0f: bool = isfloattype(c, q0t);
|
|
||||||
let e1f: bool = isfloattype(c, q1t);
|
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
if (e0f) {
|
let gpcur: i32 = 0;
|
||||||
let mov: str = "MOVSD";
|
let ssecur: i32 = 0;
|
||||||
if (isf32type(c, q0t)) { mov = "MOVSS"; };
|
let eoff: i32 = 0;
|
||||||
emitline("\t");
|
let q: *node = rt16.list;
|
||||||
emitline(mov);
|
for (q != nil) {
|
||||||
emitline("\tX0, ");
|
let qt: *node = q.lhs;
|
||||||
emitoff(off: i64);
|
let isflt: bool = isfloattype(c, qt);
|
||||||
emitline("(BP)\n");
|
tupstore(c, gpcur, ssecur, off + eoff, false, qt);
|
||||||
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
gpcur = gpcur + 1;
|
||||||
emitoff(off: i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
if (e1f) {
|
eoff = eoff + 8;
|
||||||
let mov: str = "MOVSD";
|
q = q.next;
|
||||||
if (isf32type(c, q1t)) { mov = "MOVSS"; };
|
|
||||||
emitline("\t");
|
|
||||||
emitline(mov);
|
|
||||||
emitline("\tX0, ");
|
|
||||||
emitoff((off + 8): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
} else {
|
|
||||||
emitline("\tMOVQ\tDX, ");
|
|
||||||
emitoff((off + 8): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
@@ -25517,40 +25564,59 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
|||||||
|
|
||||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||||
|
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssetotal: i32 = 0;
|
||||||
let l: *node = n.list;
|
let l: *node = n.list;
|
||||||
let pt: *node = nil;
|
let pt: *node = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
for (l != nil) {
|
for (l != nil) {
|
||||||
let tn: *node = nil;
|
let tn: *node = nil;
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
|
if (isfloattype(c, tn)) {
|
||||||
|
ssetotal = ssetotal + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||||
// fatal(), err.c) — surface, don't corrupt.
|
// fatal(), err.c) — surface, don't corrupt.
|
||||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
let msg: str = "tuple destructure exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
if (ssetotal > ssecap) {
|
||||||
|
let msg: str = "tuple destructure exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur: i32 = 0;
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
l = n.list;
|
l = n.list;
|
||||||
pt = nil;
|
pt = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
for (l != nil) {
|
for (l != nil) {
|
||||||
let tn: *node = nil;
|
let tn: *node = nil;
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
|
let isflt: bool = isfloattype(c, tn);
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
let off: i32 = 0;
|
let off: i32 = 0;
|
||||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||||
|
// harec `_` (off==0): skip the store but CONSUME the cursor
|
||||||
|
// slot so the next element stays aligned.
|
||||||
if (off != 0) {
|
if (off != 0) {
|
||||||
tupstore(c, cur, off, wide, tn);
|
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||||
|
};
|
||||||
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
|
} else {
|
||||||
|
gpcur = gpcur + tupebytes(wide);
|
||||||
};
|
};
|
||||||
cur = cur + tupebytes(wide);
|
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
@@ -25585,7 +25651,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
|
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
|
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssetotal: i32 = 0;
|
||||||
let l: *node = n.list;
|
let l: *node = n.list;
|
||||||
let pt: *node = nil;
|
let pt: *node = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
@@ -25594,20 +25662,30 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
if (tn == nil) {
|
if (tn == nil) {
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
};
|
};
|
||||||
|
if (isfloattype(c, tn)) {
|
||||||
|
ssetotal = ssetotal + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||||
// fatal(), err.c) — surface, don't corrupt.
|
// fatal(), err.c) — surface, don't corrupt.
|
||||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
let msg: str = "tuple destructure exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
if (ssetotal > ssecap) {
|
||||||
|
let msg: str = "tuple destructure exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur: i32 = 0;
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
l = n.list;
|
l = n.list;
|
||||||
pt = nil;
|
pt = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
@@ -25616,12 +25694,17 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
if (tn == nil) {
|
if (tn == nil) {
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
};
|
};
|
||||||
|
let isflt: bool = isfloattype(c, tn);
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
let sz: i32 = 8;
|
let sz: i32 = 8;
|
||||||
if (wide) { sz = tyslicesize(): i32; };
|
if (wide) { sz = tyslicesize(): i32; };
|
||||||
let off: i32 = localadd(c, l.str, sz, tn);
|
let off: i32 = localadd(c, l.str, sz, tn);
|
||||||
tupstore(c, cur, off, wide, tn);
|
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||||
cur = cur + tupebytes(wide);
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
|
} else {
|
||||||
|
gpcur = gpcur + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -121,6 +121,17 @@ fn tupreg(i: i32) str = {
|
|||||||
return "R8";
|
return "R8";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// #164 (#107): SSE half of the SysV dual register-class return. A float
|
||||||
|
// element rides the SSE row [X0,X1] on a counter INDEPENDENT of the
|
||||||
|
// INTEGER row tupreg — a float lands in the next XMM regardless of its
|
||||||
|
// positional slot (ref/qbe/amd64/sysv.c retr L95-108, retreg={{RAX,RDX},
|
||||||
|
// {XMM0,XMM1}}). SysV caps SSE returns at 2 eightbytes. Mirror of cstage
|
||||||
|
// tuple_sse_seq (cmd/w6c/cgen.c).
|
||||||
|
fn tupsse(i: i32) str = {
|
||||||
|
if (i == 0) { return "X0"; };
|
||||||
|
return "X1";
|
||||||
|
};
|
||||||
|
|
||||||
fn tupebytes(wide: bool) i32 = {
|
fn tupebytes(wide: bool) i32 = {
|
||||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||||
return 1;
|
return 1;
|
||||||
@@ -161,31 +172,34 @@ fn rettupleof(c: *cgen, rhs: *node) *node = {
|
|||||||
// tupstore — store the tuple element at register-cursor `cur` into the
|
// 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}
|
// 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
|
// 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
|
// INTEGER cursor registers; a float rides the SSE cursor (X0,X1); a
|
||||||
// N_MLET/N_MASSIGN store (cmd/w6c/cgen.c).
|
// scalar stores 1 INTEGER word. The caller owns the dual cursor
|
||||||
fn tupstore(c: *cgen, cur: i32, off: i32, wide: bool, tn: *node) void = {
|
// (validated + advanced). Byte-identical to the cstage tuple_store
|
||||||
|
// (cmd/w6c/cgen.c).
|
||||||
|
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node) void = {
|
||||||
if (wide) {
|
if (wide) {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 0));
|
emitline(tupreg(gpcur + 0));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 1));
|
emitline(tupreg(gpcur + 1));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 2));
|
emitline(tupreg(gpcur + 2));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff((off + 16): i64);
|
emitoff((off + 16): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// #105: an f64/f32 element rides X0 (the SSE return reg), not its
|
// #105 / #164 (#107): an f64/f32 element rides the SSE cursor reg
|
||||||
// integer cursor reg — MOVSD/MOVSS it, else the slot gets garbage and
|
// (X0,X1 = tupsse), not its INTEGER cursor reg — MOVSD/MOVSS it, else
|
||||||
// the FACE-Z field read sees it. X0 survives the reg->mem stores.
|
// the slot gets garbage and the FACE-Z field read sees it. The SSE
|
||||||
// Single-float scope; multi-float collides on X0 at RETURN (#107).
|
// regs survive the reg->mem stores. SSE-idx0=X0 keeps the #105
|
||||||
|
// single-float byte-id; idx1=X1 is the #107 multi-float extension.
|
||||||
if (isfloattype(c, tn)) {
|
if (isfloattype(c, tn)) {
|
||||||
// #121 (Package B) RESIDUAL sibling-evidence guard, pin form.
|
// #121 (Package B) RESIDUAL sibling-evidence guard, pin form.
|
||||||
// In destructure mode tn IS the tuple-element-type-AST node
|
// In destructure mode tn IS the tuple-element-type-AST node
|
||||||
@@ -218,13 +232,15 @@ fn tupstore(c: *cgen, cur: i32, off: i32, wide: bool, tn: *node) void = {
|
|||||||
if (isf32type(c, tn)) { mov = "MOVSS"; };
|
if (isf32type(c, tn)) { mov = "MOVSS"; };
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\t");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur));
|
emitline(tupreg(gpcur));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
@@ -234,49 +250,98 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
rundefers(c);
|
rundefers(c);
|
||||||
let rhs: *node = n.lhs;
|
let rhs: *node = n.lhs;
|
||||||
if (rhs != nil) {
|
if (rhs != nil) {
|
||||||
// #83: positional per-element register-return (harec
|
// #83 / #164 (#107): positional register-return over a SysV
|
||||||
// create_unpack_bindings, ref/harec/src/check.c:1354-1416). Each
|
// dual class cursor (harec create_unpack_bindings, ref/harec/src/
|
||||||
// element rides consecutive eightbytes over [AX,DX,CX,R8]
|
// check.c:1354-1416). A float takes one SSE eightbyte (X0,X1 =
|
||||||
// (tupreg); a slice/str rides its 3-word {ptr,len,cap} header
|
// tupsse), everything else INTEGER eightbytes over [AX,DX,CX,R8]
|
||||||
// (ref/hare/rt/ensure.ha:4-8), cgexpr leaving it in (AX,BX,CX); a
|
// (tupreg) — a slice/str its 3-word {ptr,len,cap} header
|
||||||
// scalar rides 1 word in AX. Spill each element L->R, then pop
|
// (ref/hare/rt/ensure.ha:4-8) cgexpr leaves in (AX,BX,CX), a
|
||||||
// into the cursor's registers in reverse so positional slot i
|
// scalar 1 word in AX. Integer words spill L->R to the stack and
|
||||||
// lands in tupreg(i) — (scalar,str) keeps the historical AX +
|
// pop into the INTEGER cursor in reverse so positional slot i
|
||||||
// DX,CX,R8. The SAME cursor drives the receive sites. Over-
|
// lands in tupreg(i) (byte-id with #83 when no float is present).
|
||||||
// capacity is a loud stop (return-ABI #10), never a silent drop.
|
// Each float must spill X0 to @tupfscr as we walk, since a later
|
||||||
|
// element's cgexpr clobbers X0; after the integer pops the saved
|
||||||
|
// floats reload into X0/X1 by SSE index — INDEPENDENT of the
|
||||||
|
// INTEGER cursor (ref/qbe/amd64/sysv.c retr L95-108). Both rows
|
||||||
|
// loud-stop at their cap (rule-7): INTEGER 4, SSE 2. The SAME
|
||||||
|
// class split drives the receive sites.
|
||||||
if (rhs.kind == nkind.N_TUPLE) {
|
if (rhs.kind == nkind.N_TUPLE) {
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssecount: i32 = 0;
|
||||||
let e: *node = rhs.list;
|
let e: *node = rhs.list;
|
||||||
for (e != nil) {
|
for (e != nil) {
|
||||||
|
if (isfloattype(c, e)) {
|
||||||
|
ssecount = ssecount + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
||||||
// uses fatal(), err.c) — surface, don't corrupt.
|
// 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";
|
let msg: str = "tuple return exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
if (ssecount > ssecap) {
|
||||||
|
let msg: str = "tuple return exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
let fscr: i32 = 0;
|
||||||
|
if (ssecount > 0) {
|
||||||
|
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
|
||||||
|
};
|
||||||
|
let sseidx: i32 = 0;
|
||||||
e = rhs.list;
|
e = rhs.list;
|
||||||
for (e != nil) {
|
for (e != nil) {
|
||||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
let isflt: bool = isfloattype(c, e);
|
||||||
cgexpr(c, e);
|
cgexpr(c, e);
|
||||||
|
if (isflt) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\tX0, ");
|
||||||
|
emitoff((fscr + sseidx * 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
sseidx = sseidx + 1;
|
||||||
|
} else {
|
||||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||||
if (wide) {
|
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||||
emitline("\tPUSHQ\tBX\n"); // .len
|
emitline("\tPUSHQ\tBX\n"); // .len
|
||||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||||
};
|
};
|
||||||
|
};
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
let i: i32 = total - 1;
|
let i: i32 = gptotal - 1;
|
||||||
for (i >= 0) {
|
for (i >= 0) {
|
||||||
emitline("\tPOPQ\t");
|
emitline("\tPOPQ\t");
|
||||||
emitline(tupreg(i));
|
emitline(tupreg(i));
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
i = i - 1;
|
i = i - 1;
|
||||||
};
|
};
|
||||||
|
let j: i32 = 0;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
if (isfloattype(c, e)) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\t");
|
||||||
|
emitoff((fscr + j * 8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupsse(j));
|
||||||
|
emitline("\n");
|
||||||
|
j = j + 1;
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
};
|
||||||
emitline("\tMOVQ\tBP, SP\n");
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -875,12 +940,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// 32B tuple init for `let t: (scalar, str) = call()` /
|
// 32B tuple init for `let t: (scalar, str) = call()` /
|
||||||
// `let t: (str, scalar) = call()`. Per the AX:DX:CX:R8 return
|
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
|
||||||
// convention: AX = scalar elem, DX = str.ptr, CX = str.len,
|
// element rides its SysV class — a float its SSE cursor reg
|
||||||
// R8 = str.cap. Layout is positional (str takes 24B at its
|
// (X0,X1 = tupsse), an integer/ptr word its INTEGER cursor reg
|
||||||
// position), so we route each register to the slot dictated by
|
// (tupreg), a slice/str its 3-word {ptr,len,cap} header over
|
||||||
// element type, not by AX/DX position. str IS []u8 (24B) → 32B
|
// consecutive INTEGER cursor regs — on INDEPENDENT counters.
|
||||||
// tuple (#1/Phase 3, task #5).
|
// tupstore routes each element from its real class into its
|
||||||
|
// positional slot (eoff steps by the element's slot size: a
|
||||||
|
// slice/str takes its 24B header). str IS []u8 (24B) → 32B tuple
|
||||||
|
// (#1/Phase 3, task #5). Mirror of the cstage unified branch.
|
||||||
if (n.lhs != nil) {
|
if (n.lhs != nil) {
|
||||||
if (n.lhs.kind == nkind.N_TTUPLE) {
|
if (n.lhs.kind == nkind.N_TTUPLE) {
|
||||||
let p0: *node = n.lhs.list;
|
let p0: *node = n.lhs.list;
|
||||||
@@ -890,38 +958,36 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let p1t: *node = nil;
|
let p1t: *node = nil;
|
||||||
if (p0 != nil) { p0t = p0.lhs; };
|
if (p0 != nil) { p0t = p0.lhs; };
|
||||||
if (p1 != nil) { p1t = p1.lhs; };
|
if (p1 != nil) { p1t = p1.lhs; };
|
||||||
let s0_is_str: bool = isstrtype(c, p0t);
|
let s0_is_str: bool = isstrtype(c, p0t)
|
||||||
let s1_is_str: bool = isstrtype(c, p1t);
|
|| isslicetype(c, p0t);
|
||||||
|
let s1_is_str: bool = isstrtype(c, p1t)
|
||||||
|
|| isslicetype(c, p1t);
|
||||||
if (p0 != nil) {
|
if (p0 != nil) {
|
||||||
if (p1 != nil) {
|
if (p1 != nil) {
|
||||||
if (s0_is_str != s1_is_str) {
|
if (s0_is_str != s1_is_str) {
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
if (s0_is_str) {
|
let gpcur: i32 = 0;
|
||||||
emitline("\tMOVQ\tDX, ");
|
let ssecur: i32 = 0;
|
||||||
emitoff(off: i64);
|
let eoff: i32 = 0;
|
||||||
emitline("(BP)\n");
|
let q: *node = n.lhs.list;
|
||||||
emitline("\tMOVQ\tCX, ");
|
for (q != nil) {
|
||||||
emitoff((off + 8): i64);
|
let qt: *node = q.lhs;
|
||||||
emitline("(BP)\n");
|
let isflt: bool = isfloattype(c, qt);
|
||||||
emitline("\tMOVQ\tR8, ");
|
let wide: bool = isstrtype(c, qt)
|
||||||
emitoff((off + 16): i64);
|
|| isslicetype(c, qt);
|
||||||
emitline("(BP)\n");
|
tupstore(c, gpcur, ssecur,
|
||||||
emitline("\tMOVQ\tAX, ");
|
off + eoff, wide, qt);
|
||||||
emitoff((off + 24): i64);
|
if (isflt) {
|
||||||
emitline("(BP)\n");
|
ssecur = ssecur + 1;
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
gpcur = gpcur + tupebytes(wide);
|
||||||
emitoff(off: i64);
|
};
|
||||||
emitline("(BP)\n");
|
if (wide) {
|
||||||
emitline("\tMOVQ\tDX, ");
|
eoff = eoff + (tyslicesize(): i32);
|
||||||
emitoff((off + 8): i64);
|
} else {
|
||||||
emitline("(BP)\n");
|
eoff = eoff + 8;
|
||||||
emitline("\tMOVQ\tCX, ");
|
};
|
||||||
emitoff((off + 16): i64);
|
q = q.next;
|
||||||
emitline("(BP)\n");
|
|
||||||
emitline("\tMOVQ\tR8, ");
|
|
||||||
emitoff((off + 24): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
@@ -930,54 +996,35 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// 16B tuple init from a function call. An integer word rides
|
// 16B tuple init from a function call (#105 / #164/#107). Each
|
||||||
// its integer cursor reg (AX, DX); a single f64/f32 word rides
|
// eightbyte rides its SysV class: a float its SSE cursor reg
|
||||||
// X0, the SSE return reg — the RETURN leaves the float in X0
|
// (X0,X1 = tupsse), an integer/ptr word its INTEGER cursor reg
|
||||||
// and pushes garbage through that word's integer slot, so a
|
// (AX,DX = tupreg), INDEPENDENT counters — the RETURN leaves
|
||||||
// blanket MOVQ-from-integer spill stores garbage and the #103-
|
// floats in X0/X1 and integer words in AX/DX, so a blanket MOVQ
|
||||||
// FACE-Z field read (MOVSD-from-slot) reads it (#105). Spill
|
// spill would store garbage where a float rode and the #103-
|
||||||
// each word from its real class. Multi-float tuples collide on
|
// FACE-Z field read (MOVSD-from-slot) would see it. tupstore
|
||||||
// X0 at the RETURN (#107), out of scope. Mirror of cstage
|
// routes each word from its real class; the same split drives
|
||||||
// cgen.c. Without this branch a 16B tuple receive (any element
|
// the destructure / reassign sites. Without this branch a 16B
|
||||||
// mix) fell to the generic single-word store below and dropped
|
// tuple receive fell to the generic single-word store below and
|
||||||
// word1 — silent loss of t.1 (#102).
|
// dropped word1 — silent loss of t.1 (#102).
|
||||||
let rt16: *node = rettupleof(c, rhs);
|
let rt16: *node = rettupleof(c, rhs);
|
||||||
if (rt16 != nil && sz == 16) {
|
if (rt16 != nil && sz == 16) {
|
||||||
let q0: *node = rt16.list;
|
|
||||||
let q1: *node = nil;
|
|
||||||
if (q0 != nil) { q1 = q0.next; };
|
|
||||||
let q0t: *node = nil;
|
|
||||||
let q1t: *node = nil;
|
|
||||||
if (q0 != nil) { q0t = q0.lhs; };
|
|
||||||
if (q1 != nil) { q1t = q1.lhs; };
|
|
||||||
let e0f: bool = isfloattype(c, q0t);
|
|
||||||
let e1f: bool = isfloattype(c, q1t);
|
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
if (e0f) {
|
let gpcur: i32 = 0;
|
||||||
let mov: str = "MOVSD";
|
let ssecur: i32 = 0;
|
||||||
if (isf32type(c, q0t)) { mov = "MOVSS"; };
|
let eoff: i32 = 0;
|
||||||
emitline("\t");
|
let q: *node = rt16.list;
|
||||||
emitline(mov);
|
for (q != nil) {
|
||||||
emitline("\tX0, ");
|
let qt: *node = q.lhs;
|
||||||
emitoff(off: i64);
|
let isflt: bool = isfloattype(c, qt);
|
||||||
emitline("(BP)\n");
|
tupstore(c, gpcur, ssecur, off + eoff, false, qt);
|
||||||
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
gpcur = gpcur + 1;
|
||||||
emitoff(off: i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
if (e1f) {
|
eoff = eoff + 8;
|
||||||
let mov: str = "MOVSD";
|
q = q.next;
|
||||||
if (isf32type(c, q1t)) { mov = "MOVSS"; };
|
|
||||||
emitline("\t");
|
|
||||||
emitline(mov);
|
|
||||||
emitline("\tX0, ");
|
|
||||||
emitoff((off + 8): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
} else {
|
|
||||||
emitline("\tMOVQ\tDX, ");
|
|
||||||
emitoff((off + 8): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
@@ -1449,40 +1496,59 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
|||||||
|
|
||||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||||
|
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssetotal: i32 = 0;
|
||||||
let l: *node = n.list;
|
let l: *node = n.list;
|
||||||
let pt: *node = nil;
|
let pt: *node = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
for (l != nil) {
|
for (l != nil) {
|
||||||
let tn: *node = nil;
|
let tn: *node = nil;
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
|
if (isfloattype(c, tn)) {
|
||||||
|
ssetotal = ssetotal + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||||
// fatal(), err.c) — surface, don't corrupt.
|
// fatal(), err.c) — surface, don't corrupt.
|
||||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
let msg: str = "tuple destructure exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
if (ssetotal > ssecap) {
|
||||||
|
let msg: str = "tuple destructure exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur: i32 = 0;
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
l = n.list;
|
l = n.list;
|
||||||
pt = nil;
|
pt = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
for (l != nil) {
|
for (l != nil) {
|
||||||
let tn: *node = nil;
|
let tn: *node = nil;
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
|
let isflt: bool = isfloattype(c, tn);
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
let off: i32 = 0;
|
let off: i32 = 0;
|
||||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||||
|
// harec `_` (off==0): skip the store but CONSUME the cursor
|
||||||
|
// slot so the next element stays aligned.
|
||||||
if (off != 0) {
|
if (off != 0) {
|
||||||
tupstore(c, cur, off, wide, tn);
|
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||||
|
};
|
||||||
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
|
} else {
|
||||||
|
gpcur = gpcur + tupebytes(wide);
|
||||||
};
|
};
|
||||||
cur = cur + tupebytes(wide);
|
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
@@ -1517,7 +1583,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
|
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
|
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssetotal: i32 = 0;
|
||||||
let l: *node = n.list;
|
let l: *node = n.list;
|
||||||
let pt: *node = nil;
|
let pt: *node = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
@@ -1526,20 +1594,30 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
if (tn == nil) {
|
if (tn == nil) {
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
};
|
};
|
||||||
|
if (isfloattype(c, tn)) {
|
||||||
|
ssetotal = ssetotal + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||||
// fatal(), err.c) — surface, don't corrupt.
|
// fatal(), err.c) — surface, don't corrupt.
|
||||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
let msg: str = "tuple destructure exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
if (ssetotal > ssecap) {
|
||||||
|
let msg: str = "tuple destructure exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur: i32 = 0;
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
l = n.list;
|
l = n.list;
|
||||||
pt = nil;
|
pt = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
@@ -1548,12 +1626,17 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
if (tn == nil) {
|
if (tn == nil) {
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
};
|
};
|
||||||
|
let isflt: bool = isfloattype(c, tn);
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
let sz: i32 = 8;
|
let sz: i32 = 8;
|
||||||
if (wide) { sz = tyslicesize(): i32; };
|
if (wide) { sz = tyslicesize(): i32; };
|
||||||
let off: i32 = localadd(c, l.str, sz, tn);
|
let off: i32 = localadd(c, l.str, sz, tn);
|
||||||
tupstore(c, cur, off, wide, tn);
|
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||||
cur = cur + tupebytes(wide);
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
|
} else {
|
||||||
|
gpcur = gpcur + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24189,6 +24189,17 @@ fn tupreg(i: i32) str = {
|
|||||||
return "R8";
|
return "R8";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// #164 (#107): SSE half of the SysV dual register-class return. A float
|
||||||
|
// element rides the SSE row [X0,X1] on a counter INDEPENDENT of the
|
||||||
|
// INTEGER row tupreg — a float lands in the next XMM regardless of its
|
||||||
|
// positional slot (ref/qbe/amd64/sysv.c retr L95-108, retreg={{RAX,RDX},
|
||||||
|
// {XMM0,XMM1}}). SysV caps SSE returns at 2 eightbytes. Mirror of cstage
|
||||||
|
// tuple_sse_seq (cmd/w6c/cgen.c).
|
||||||
|
fn tupsse(i: i32) str = {
|
||||||
|
if (i == 0) { return "X0"; };
|
||||||
|
return "X1";
|
||||||
|
};
|
||||||
|
|
||||||
fn tupebytes(wide: bool) i32 = {
|
fn tupebytes(wide: bool) i32 = {
|
||||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||||
return 1;
|
return 1;
|
||||||
@@ -24229,31 +24240,34 @@ fn rettupleof(c: *cgen, rhs: *node) *node = {
|
|||||||
// tupstore — store the tuple element at register-cursor `cur` into the
|
// 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}
|
// 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
|
// 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
|
// INTEGER cursor registers; a float rides the SSE cursor (X0,X1); a
|
||||||
// N_MLET/N_MASSIGN store (cmd/w6c/cgen.c).
|
// scalar stores 1 INTEGER word. The caller owns the dual cursor
|
||||||
fn tupstore(c: *cgen, cur: i32, off: i32, wide: bool, tn: *node) void = {
|
// (validated + advanced). Byte-identical to the cstage tuple_store
|
||||||
|
// (cmd/w6c/cgen.c).
|
||||||
|
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node) void = {
|
||||||
if (wide) {
|
if (wide) {
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 0));
|
emitline(tupreg(gpcur + 0));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 1));
|
emitline(tupreg(gpcur + 1));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff((off + 8): i64);
|
emitoff((off + 8): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur + 2));
|
emitline(tupreg(gpcur + 2));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff((off + 16): i64);
|
emitoff((off + 16): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// #105: an f64/f32 element rides X0 (the SSE return reg), not its
|
// #105 / #164 (#107): an f64/f32 element rides the SSE cursor reg
|
||||||
// integer cursor reg — MOVSD/MOVSS it, else the slot gets garbage and
|
// (X0,X1 = tupsse), not its INTEGER cursor reg — MOVSD/MOVSS it, else
|
||||||
// the FACE-Z field read sees it. X0 survives the reg->mem stores.
|
// the slot gets garbage and the FACE-Z field read sees it. The SSE
|
||||||
// Single-float scope; multi-float collides on X0 at RETURN (#107).
|
// regs survive the reg->mem stores. SSE-idx0=X0 keeps the #105
|
||||||
|
// single-float byte-id; idx1=X1 is the #107 multi-float extension.
|
||||||
if (isfloattype(c, tn)) {
|
if (isfloattype(c, tn)) {
|
||||||
// #121 (Package B) RESIDUAL sibling-evidence guard, pin form.
|
// #121 (Package B) RESIDUAL sibling-evidence guard, pin form.
|
||||||
// In destructure mode tn IS the tuple-element-type-AST node
|
// In destructure mode tn IS the tuple-element-type-AST node
|
||||||
@@ -24286,13 +24300,15 @@ fn tupstore(c: *cgen, cur: i32, off: i32, wide: bool, tn: *node) void = {
|
|||||||
if (isf32type(c, tn)) { mov = "MOVSS"; };
|
if (isf32type(c, tn)) { mov = "MOVSS"; };
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(mov);
|
emitline(mov);
|
||||||
emitline("\tX0, ");
|
emitline("\t");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitline(tupreg(cur));
|
emitline(tupreg(gpcur));
|
||||||
emitline(", ");
|
emitline(", ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
@@ -24302,49 +24318,98 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
rundefers(c);
|
rundefers(c);
|
||||||
let rhs: *node = n.lhs;
|
let rhs: *node = n.lhs;
|
||||||
if (rhs != nil) {
|
if (rhs != nil) {
|
||||||
// #83: positional per-element register-return (harec
|
// #83 / #164 (#107): positional register-return over a SysV
|
||||||
// create_unpack_bindings, ref/harec/src/check.c:1354-1416). Each
|
// dual class cursor (harec create_unpack_bindings, ref/harec/src/
|
||||||
// element rides consecutive eightbytes over [AX,DX,CX,R8]
|
// check.c:1354-1416). A float takes one SSE eightbyte (X0,X1 =
|
||||||
// (tupreg); a slice/str rides its 3-word {ptr,len,cap} header
|
// tupsse), everything else INTEGER eightbytes over [AX,DX,CX,R8]
|
||||||
// (ref/hare/rt/ensure.ha:4-8), cgexpr leaving it in (AX,BX,CX); a
|
// (tupreg) — a slice/str its 3-word {ptr,len,cap} header
|
||||||
// scalar rides 1 word in AX. Spill each element L->R, then pop
|
// (ref/hare/rt/ensure.ha:4-8) cgexpr leaves in (AX,BX,CX), a
|
||||||
// into the cursor's registers in reverse so positional slot i
|
// scalar 1 word in AX. Integer words spill L->R to the stack and
|
||||||
// lands in tupreg(i) — (scalar,str) keeps the historical AX +
|
// pop into the INTEGER cursor in reverse so positional slot i
|
||||||
// DX,CX,R8. The SAME cursor drives the receive sites. Over-
|
// lands in tupreg(i) (byte-id with #83 when no float is present).
|
||||||
// capacity is a loud stop (return-ABI #10), never a silent drop.
|
// Each float must spill X0 to @tupfscr as we walk, since a later
|
||||||
|
// element's cgexpr clobbers X0; after the integer pops the saved
|
||||||
|
// floats reload into X0/X1 by SSE index — INDEPENDENT of the
|
||||||
|
// INTEGER cursor (ref/qbe/amd64/sysv.c retr L95-108). Both rows
|
||||||
|
// loud-stop at their cap (rule-7): INTEGER 4, SSE 2. The SAME
|
||||||
|
// class split drives the receive sites.
|
||||||
if (rhs.kind == nkind.N_TUPLE) {
|
if (rhs.kind == nkind.N_TUPLE) {
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssecount: i32 = 0;
|
||||||
let e: *node = rhs.list;
|
let e: *node = rhs.list;
|
||||||
for (e != nil) {
|
for (e != nil) {
|
||||||
|
if (isfloattype(c, e)) {
|
||||||
|
ssecount = ssecount + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
||||||
// uses fatal(), err.c) — surface, don't corrupt.
|
// 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";
|
let msg: str = "tuple return exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
if (ssecount > ssecap) {
|
||||||
|
let msg: str = "tuple return exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
let fscr: i32 = 0;
|
||||||
|
if (ssecount > 0) {
|
||||||
|
fscr = localadd(c, "@tupfscr", ssecap * 8, nil);
|
||||||
|
};
|
||||||
|
let sseidx: i32 = 0;
|
||||||
e = rhs.list;
|
e = rhs.list;
|
||||||
for (e != nil) {
|
for (e != nil) {
|
||||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
let isflt: bool = isfloattype(c, e);
|
||||||
cgexpr(c, e);
|
cgexpr(c, e);
|
||||||
|
if (isflt) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\tX0, ");
|
||||||
|
emitoff((fscr + sseidx * 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
sseidx = sseidx + 1;
|
||||||
|
} else {
|
||||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||||
if (wide) {
|
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||||
emitline("\tPUSHQ\tBX\n"); // .len
|
emitline("\tPUSHQ\tBX\n"); // .len
|
||||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||||
};
|
};
|
||||||
|
};
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
let i: i32 = total - 1;
|
let i: i32 = gptotal - 1;
|
||||||
for (i >= 0) {
|
for (i >= 0) {
|
||||||
emitline("\tPOPQ\t");
|
emitline("\tPOPQ\t");
|
||||||
emitline(tupreg(i));
|
emitline(tupreg(i));
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
i = i - 1;
|
i = i - 1;
|
||||||
};
|
};
|
||||||
|
let j: i32 = 0;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
if (isfloattype(c, e)) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\t");
|
||||||
|
emitoff((fscr + j * 8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupsse(j));
|
||||||
|
emitline("\n");
|
||||||
|
j = j + 1;
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
};
|
||||||
emitline("\tMOVQ\tBP, SP\n");
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -24943,12 +25008,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// 32B tuple init for `let t: (scalar, str) = call()` /
|
// 32B tuple init for `let t: (scalar, str) = call()` /
|
||||||
// `let t: (str, scalar) = call()`. Per the AX:DX:CX:R8 return
|
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
|
||||||
// convention: AX = scalar elem, DX = str.ptr, CX = str.len,
|
// element rides its SysV class — a float its SSE cursor reg
|
||||||
// R8 = str.cap. Layout is positional (str takes 24B at its
|
// (X0,X1 = tupsse), an integer/ptr word its INTEGER cursor reg
|
||||||
// position), so we route each register to the slot dictated by
|
// (tupreg), a slice/str its 3-word {ptr,len,cap} header over
|
||||||
// element type, not by AX/DX position. str IS []u8 (24B) → 32B
|
// consecutive INTEGER cursor regs — on INDEPENDENT counters.
|
||||||
// tuple (#1/Phase 3, task #5).
|
// tupstore routes each element from its real class into its
|
||||||
|
// positional slot (eoff steps by the element's slot size: a
|
||||||
|
// slice/str takes its 24B header). str IS []u8 (24B) → 32B tuple
|
||||||
|
// (#1/Phase 3, task #5). Mirror of the cstage unified branch.
|
||||||
if (n.lhs != nil) {
|
if (n.lhs != nil) {
|
||||||
if (n.lhs.kind == nkind.N_TTUPLE) {
|
if (n.lhs.kind == nkind.N_TTUPLE) {
|
||||||
let p0: *node = n.lhs.list;
|
let p0: *node = n.lhs.list;
|
||||||
@@ -24958,38 +25026,36 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let p1t: *node = nil;
|
let p1t: *node = nil;
|
||||||
if (p0 != nil) { p0t = p0.lhs; };
|
if (p0 != nil) { p0t = p0.lhs; };
|
||||||
if (p1 != nil) { p1t = p1.lhs; };
|
if (p1 != nil) { p1t = p1.lhs; };
|
||||||
let s0_is_str: bool = isstrtype(c, p0t);
|
let s0_is_str: bool = isstrtype(c, p0t)
|
||||||
let s1_is_str: bool = isstrtype(c, p1t);
|
|| isslicetype(c, p0t);
|
||||||
|
let s1_is_str: bool = isstrtype(c, p1t)
|
||||||
|
|| isslicetype(c, p1t);
|
||||||
if (p0 != nil) {
|
if (p0 != nil) {
|
||||||
if (p1 != nil) {
|
if (p1 != nil) {
|
||||||
if (s0_is_str != s1_is_str) {
|
if (s0_is_str != s1_is_str) {
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
if (s0_is_str) {
|
let gpcur: i32 = 0;
|
||||||
emitline("\tMOVQ\tDX, ");
|
let ssecur: i32 = 0;
|
||||||
emitoff(off: i64);
|
let eoff: i32 = 0;
|
||||||
emitline("(BP)\n");
|
let q: *node = n.lhs.list;
|
||||||
emitline("\tMOVQ\tCX, ");
|
for (q != nil) {
|
||||||
emitoff((off + 8): i64);
|
let qt: *node = q.lhs;
|
||||||
emitline("(BP)\n");
|
let isflt: bool = isfloattype(c, qt);
|
||||||
emitline("\tMOVQ\tR8, ");
|
let wide: bool = isstrtype(c, qt)
|
||||||
emitoff((off + 16): i64);
|
|| isslicetype(c, qt);
|
||||||
emitline("(BP)\n");
|
tupstore(c, gpcur, ssecur,
|
||||||
emitline("\tMOVQ\tAX, ");
|
off + eoff, wide, qt);
|
||||||
emitoff((off + 24): i64);
|
if (isflt) {
|
||||||
emitline("(BP)\n");
|
ssecur = ssecur + 1;
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
gpcur = gpcur + tupebytes(wide);
|
||||||
emitoff(off: i64);
|
};
|
||||||
emitline("(BP)\n");
|
if (wide) {
|
||||||
emitline("\tMOVQ\tDX, ");
|
eoff = eoff + (tyslicesize(): i32);
|
||||||
emitoff((off + 8): i64);
|
} else {
|
||||||
emitline("(BP)\n");
|
eoff = eoff + 8;
|
||||||
emitline("\tMOVQ\tCX, ");
|
};
|
||||||
emitoff((off + 16): i64);
|
q = q.next;
|
||||||
emitline("(BP)\n");
|
|
||||||
emitline("\tMOVQ\tR8, ");
|
|
||||||
emitoff((off + 24): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
@@ -24998,54 +25064,35 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// 16B tuple init from a function call. An integer word rides
|
// 16B tuple init from a function call (#105 / #164/#107). Each
|
||||||
// its integer cursor reg (AX, DX); a single f64/f32 word rides
|
// eightbyte rides its SysV class: a float its SSE cursor reg
|
||||||
// X0, the SSE return reg — the RETURN leaves the float in X0
|
// (X0,X1 = tupsse), an integer/ptr word its INTEGER cursor reg
|
||||||
// and pushes garbage through that word's integer slot, so a
|
// (AX,DX = tupreg), INDEPENDENT counters — the RETURN leaves
|
||||||
// blanket MOVQ-from-integer spill stores garbage and the #103-
|
// floats in X0/X1 and integer words in AX/DX, so a blanket MOVQ
|
||||||
// FACE-Z field read (MOVSD-from-slot) reads it (#105). Spill
|
// spill would store garbage where a float rode and the #103-
|
||||||
// each word from its real class. Multi-float tuples collide on
|
// FACE-Z field read (MOVSD-from-slot) would see it. tupstore
|
||||||
// X0 at the RETURN (#107), out of scope. Mirror of cstage
|
// routes each word from its real class; the same split drives
|
||||||
// cgen.c. Without this branch a 16B tuple receive (any element
|
// the destructure / reassign sites. Without this branch a 16B
|
||||||
// mix) fell to the generic single-word store below and dropped
|
// tuple receive fell to the generic single-word store below and
|
||||||
// word1 — silent loss of t.1 (#102).
|
// dropped word1 — silent loss of t.1 (#102).
|
||||||
let rt16: *node = rettupleof(c, rhs);
|
let rt16: *node = rettupleof(c, rhs);
|
||||||
if (rt16 != nil && sz == 16) {
|
if (rt16 != nil && sz == 16) {
|
||||||
let q0: *node = rt16.list;
|
|
||||||
let q1: *node = nil;
|
|
||||||
if (q0 != nil) { q1 = q0.next; };
|
|
||||||
let q0t: *node = nil;
|
|
||||||
let q1t: *node = nil;
|
|
||||||
if (q0 != nil) { q0t = q0.lhs; };
|
|
||||||
if (q1 != nil) { q1t = q1.lhs; };
|
|
||||||
let e0f: bool = isfloattype(c, q0t);
|
|
||||||
let e1f: bool = isfloattype(c, q1t);
|
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
if (e0f) {
|
let gpcur: i32 = 0;
|
||||||
let mov: str = "MOVSD";
|
let ssecur: i32 = 0;
|
||||||
if (isf32type(c, q0t)) { mov = "MOVSS"; };
|
let eoff: i32 = 0;
|
||||||
emitline("\t");
|
let q: *node = rt16.list;
|
||||||
emitline(mov);
|
for (q != nil) {
|
||||||
emitline("\tX0, ");
|
let qt: *node = q.lhs;
|
||||||
emitoff(off: i64);
|
let isflt: bool = isfloattype(c, qt);
|
||||||
emitline("(BP)\n");
|
tupstore(c, gpcur, ssecur, off + eoff, false, qt);
|
||||||
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tAX, ");
|
gpcur = gpcur + 1;
|
||||||
emitoff(off: i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
if (e1f) {
|
eoff = eoff + 8;
|
||||||
let mov: str = "MOVSD";
|
q = q.next;
|
||||||
if (isf32type(c, q1t)) { mov = "MOVSS"; };
|
|
||||||
emitline("\t");
|
|
||||||
emitline(mov);
|
|
||||||
emitline("\tX0, ");
|
|
||||||
emitoff((off + 8): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
} else {
|
|
||||||
emitline("\tMOVQ\tDX, ");
|
|
||||||
emitoff((off + 8): i64);
|
|
||||||
emitline("(BP)\n");
|
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
@@ -25517,40 +25564,59 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
|||||||
|
|
||||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||||
|
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssetotal: i32 = 0;
|
||||||
let l: *node = n.list;
|
let l: *node = n.list;
|
||||||
let pt: *node = nil;
|
let pt: *node = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
for (l != nil) {
|
for (l != nil) {
|
||||||
let tn: *node = nil;
|
let tn: *node = nil;
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
|
if (isfloattype(c, tn)) {
|
||||||
|
ssetotal = ssetotal + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||||
// fatal(), err.c) — surface, don't corrupt.
|
// fatal(), err.c) — surface, don't corrupt.
|
||||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
let msg: str = "tuple destructure exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
if (ssetotal > ssecap) {
|
||||||
|
let msg: str = "tuple destructure exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur: i32 = 0;
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
l = n.list;
|
l = n.list;
|
||||||
pt = nil;
|
pt = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
for (l != nil) {
|
for (l != nil) {
|
||||||
let tn: *node = nil;
|
let tn: *node = nil;
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
|
let isflt: bool = isfloattype(c, tn);
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
let off: i32 = 0;
|
let off: i32 = 0;
|
||||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||||
|
// harec `_` (off==0): skip the store but CONSUME the cursor
|
||||||
|
// slot so the next element stays aligned.
|
||||||
if (off != 0) {
|
if (off != 0) {
|
||||||
tupstore(c, cur, off, wide, tn);
|
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||||
|
};
|
||||||
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
|
} else {
|
||||||
|
gpcur = gpcur + tupebytes(wide);
|
||||||
};
|
};
|
||||||
cur = cur + tupebytes(wide);
|
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
@@ -25585,7 +25651,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
|
|
||||||
cgexpr(c, rhs);
|
cgexpr(c, rhs);
|
||||||
|
|
||||||
let total: i32 = 0;
|
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||||
|
let gptotal: i32 = 0;
|
||||||
|
let ssetotal: i32 = 0;
|
||||||
let l: *node = n.list;
|
let l: *node = n.list;
|
||||||
let pt: *node = nil;
|
let pt: *node = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
@@ -25594,20 +25662,30 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
if (tn == nil) {
|
if (tn == nil) {
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
};
|
};
|
||||||
|
if (isfloattype(c, tn)) {
|
||||||
|
ssetotal = ssetotal + 1;
|
||||||
|
} else {
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
total = total + tupebytes(wide);
|
gptotal = gptotal + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
if (total > 4) { // AX,DX,CX,R8 capacity
|
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||||
// fatal(), err.c) — surface, don't corrupt.
|
// fatal(), err.c) — surface, don't corrupt.
|
||||||
let msg: str = "tuple destructure exceeds register-return ABI capacity (4 eightbytes); see return-ABI #10\n";
|
let msg: str = "tuple destructure exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||||
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
if (ssetotal > ssecap) {
|
||||||
|
let msg: str = "tuple destructure exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||||
os.write(2, msg.ptr, msg.len: u64);
|
os.write(2, msg.ptr, msg.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur: i32 = 0;
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
l = n.list;
|
l = n.list;
|
||||||
pt = nil;
|
pt = nil;
|
||||||
if (rettuple != nil) { pt = rettuple.list; };
|
if (rettuple != nil) { pt = rettuple.list; };
|
||||||
@@ -25616,12 +25694,17 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
|||||||
if (tn == nil) {
|
if (tn == nil) {
|
||||||
if (pt != nil) { tn = pt.lhs; };
|
if (pt != nil) { tn = pt.lhs; };
|
||||||
};
|
};
|
||||||
|
let isflt: bool = isfloattype(c, tn);
|
||||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||||
let sz: i32 = 8;
|
let sz: i32 = 8;
|
||||||
if (wide) { sz = tyslicesize(): i32; };
|
if (wide) { sz = tyslicesize(): i32; };
|
||||||
let off: i32 = localadd(c, l.str, sz, tn);
|
let off: i32 = localadd(c, l.str, sz, tn);
|
||||||
tupstore(c, cur, off, wide, tn);
|
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||||
cur = cur + tupebytes(wide);
|
if (isflt) {
|
||||||
|
ssecur = ssecur + 1;
|
||||||
|
} else {
|
||||||
|
gpcur = gpcur + tupebytes(wide);
|
||||||
|
};
|
||||||
l = l.next;
|
l = l.next;
|
||||||
if (pt != nil) { pt = pt.next; };
|
if (pt != nil) { pt = pt.next; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* 956_tuprecv_f64_run — runtime + byte-id regression net for #105: a
|
* 956_tuprecv_f64_run — runtime + byte-id regression net for #105 and
|
||||||
* tuple-from-call receive corrupts the f64 word when the callee is
|
* the #164 (#107) multi-float extension.
|
||||||
* BRANCHED. Covers ALL THREE receive forms, which share the #83
|
*
|
||||||
* tuple_rseq cursor and all carried the same defect:
|
* #164 (#107): a multi-float tuple return (e.g. (f64,f64)) mis-routes —
|
||||||
|
* the SEND emitted every float through X0 (cgexpr clobbers X0 per
|
||||||
|
* element), so two floats collided on X0 and the receive read both from
|
||||||
|
* X0. The fix gives the tuple return a SysV SSE cursor [X0,X1] parallel
|
||||||
|
* to the integer cursor [AX,DX,CX,R8]: a float rides the next XMM on an
|
||||||
|
* INDEPENDENT counter (ref/qbe/amd64/sysv.c retr). The SEND spills each
|
||||||
|
* float to @tupfscr as it walks (X0 is clobbered by later elements) and
|
||||||
|
* reloads X0/X1 by SSE index after the integer pops; every receive site
|
||||||
|
* (single-var 16B/32B, destructure, reassign) reads the float from its
|
||||||
|
* SSE-cursor reg. SSE caps at 2 (X0,X1) — (f64,f64,f64) loud-stops at
|
||||||
|
* compile (f64x3_loudstop row asserts the compiler ERRORS, both stages).
|
||||||
|
*
|
||||||
|
* #105 (original): a tuple-from-call receive corrupts the f64 word when
|
||||||
|
* the callee is BRANCHED. Covers ALL THREE receive forms, which share the
|
||||||
|
* #83 tuple_rseq cursor and all carried the same defect:
|
||||||
* 1. SINGLE-VAR `let r = norm(); ...r.0` (cglet 16B-tuple branch)
|
* 1. SINGLE-VAR `let r = norm(); ...r.0` (cglet 16B-tuple branch)
|
||||||
* 2. DESTRUCTURE `let (m,i) = norm()` (N_MLET / cgmlet+tupstore)
|
* 2. DESTRUCTURE `let (m,i) = norm()` (N_MLET / cgmlet+tupstore)
|
||||||
* 3. REASSIGN `m,i = norm()` (N_MASSIGN / cgmassign+tupstore)
|
* 3. REASSIGN `m,i = norm()` (N_MASSIGN / cgmassign+tupstore)
|
||||||
@@ -60,7 +74,13 @@ runwait(const char *cmd)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct row { const char *label; const char *src; int want_exit; int chk_stamped; };
|
struct row {
|
||||||
|
const char *label;
|
||||||
|
const char *src;
|
||||||
|
int want_exit;
|
||||||
|
int chk_stamped;
|
||||||
|
int want_compile_fail; /* #164: loud-stop rows must NOT compile */
|
||||||
|
};
|
||||||
|
|
||||||
static const struct row rows[] = {
|
static const struct row rows[] = {
|
||||||
/* BUG — minimal repro. norm is BRANCHED (inner issub() CALL clobbers
|
/* BUG — minimal repro. norm is BRANCHED (inner issub() CALL clobbers
|
||||||
@@ -184,6 +204,175 @@ static const struct row rows[] = {
|
|||||||
"\tif (i != 0) { return 2; };\n"
|
"\tif (i != 0) { return 2; };\n"
|
||||||
"\treturn 0;\n"
|
"\treturn 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0 },
|
||||||
|
/* #164 (#107) HEADLINE — multi-float (f64, f64), destructure. The
|
||||||
|
* callee is BRANCHED (issub CALL clobbers X0), so on master BOTH
|
||||||
|
* elements collide on X0: cgexpr(a) leaves a in X0, cgexpr(b)
|
||||||
|
* overwrites it, and every receive read spills from X0 -> x==y==b
|
||||||
|
* (5.0). Post-fix a rides the SSE cursor X0, b rides X1; the receive
|
||||||
|
* splits them. x=3.0, y=5.0. Pre-fix: x==5.0 -> return 1. */
|
||||||
|
{ "f64f64_destr_br",
|
||||||
|
"package main;\n"
|
||||||
|
"fn issub(n: f64) bool = { return false; };\n"
|
||||||
|
"fn pair(a: f64, b: f64) (f64, f64) = {\n"
|
||||||
|
"\tif (issub(a)) { return (a*2.0, b*2.0); };\n"
|
||||||
|
"\treturn (a, b);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet (x, y) = pair(3.0, 5.0);\n"
|
||||||
|
"\tif (x != 3.0) { return 1; };\n"
|
||||||
|
"\tif (y != 5.0) { return 2; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 1 },
|
||||||
|
/* #164 HEADLINE — multi-float (f64, f64), SINGLE-VAR whole-tuple
|
||||||
|
* receive (`let r = pair(); r.0 / r.1`, the 16B rt16 branch). Same
|
||||||
|
* X0-collision on master; post-fix r.0 from X0, r.1 from X1. */
|
||||||
|
{ "f64f64_single_br",
|
||||||
|
"package main;\n"
|
||||||
|
"fn issub(n: f64) bool = { return false; };\n"
|
||||||
|
"fn pair(a: f64, b: f64) (f64, f64) = {\n"
|
||||||
|
"\tif (issub(a)) { return (a*2.0, b*2.0); };\n"
|
||||||
|
"\treturn (a, b);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tconst r = pair(3.0, 5.0);\n"
|
||||||
|
"\tconst x: f64 = r.0;\n"
|
||||||
|
"\tconst y: f64 = r.1;\n"
|
||||||
|
"\tif (x != 3.0) { return 1; };\n"
|
||||||
|
"\tif (y != 5.0) { return 2; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0 },
|
||||||
|
/* #164 HEADLINE — multi-float (f64, f64), REASSIGN into pre-declared
|
||||||
|
* slots (N_MASSIGN / cgmassign+tupstore). x=3.0, y=5.0. */
|
||||||
|
{ "f64f64_massign_br",
|
||||||
|
"package main;\n"
|
||||||
|
"fn issub(n: f64) bool = { return false; };\n"
|
||||||
|
"fn pair(a: f64, b: f64) (f64, f64) = {\n"
|
||||||
|
"\tif (issub(a)) { return (a*2.0, b*2.0); };\n"
|
||||||
|
"\treturn (a, b);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet x: f64 = 0.0;\n"
|
||||||
|
"\tlet y: f64 = 0.0;\n"
|
||||||
|
"\tx, y = pair(3.0, 5.0);\n"
|
||||||
|
"\tif (x != 3.0) { return 1; };\n"
|
||||||
|
"\tif (y != 5.0) { return 2; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0 },
|
||||||
|
/* #164 — INTERLEAVED (i64, f64, i64): kills naive position->reg. The
|
||||||
|
* two i64s ride the INTEGER cursor (AX,DX), the f64 the SSE cursor
|
||||||
|
* (X0) on an independent counter — so x=AX, z=DX, y=X0. 3-element
|
||||||
|
* destructure. x=3, y=2.0, z=7. */
|
||||||
|
{ "i64_f64_i64_destr",
|
||||||
|
"package main;\n"
|
||||||
|
"fn issub(n: i64) bool = { return false; };\n"
|
||||||
|
"fn tri(a: i64, b: f64, c: i64) (i64, f64, i64) = {\n"
|
||||||
|
"\tif (issub(a)) { return (a*2, b*2.0, c*2); };\n"
|
||||||
|
"\treturn (a, b, c);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet (x, y, z) = tri(3, 2.0, 7);\n"
|
||||||
|
"\tif (x != 3) { return 1; };\n"
|
||||||
|
"\tif (y != 2.0) { return 2; };\n"
|
||||||
|
"\tif (z != 7) { return 3; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 1 },
|
||||||
|
/* #164 — (f64, str): SSE + wide (24B {ptr,len,cap} header) coexist.
|
||||||
|
* The f64 rides the SSE cursor (X0); the str rides the INTEGER
|
||||||
|
* cursor (AX,DX,CX) since the float consumes no GP slot. Destructure
|
||||||
|
* form. f=4.0, s.len=5 ("hello"). */
|
||||||
|
{ "f64_str_destr",
|
||||||
|
"package main;\n"
|
||||||
|
"fn issub(n: f64) bool = { return false; };\n"
|
||||||
|
"fn fs(n: f64) (f64, str) = {\n"
|
||||||
|
"\tif (issub(n)) { return (n*2.0, \"x\"); };\n"
|
||||||
|
"\treturn (n, \"hello\");\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet (f, s) = fs(4.0);\n"
|
||||||
|
"\tif (f != 4.0) { return 1; };\n"
|
||||||
|
"\tif (s.len != 5) { return 2; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 1 },
|
||||||
|
/* #164 STR-FIRST destructure (str, f64): the unification's new-
|
||||||
|
* coverage shape with the wide header in slot 0. The str rides the
|
||||||
|
* INTEGER cursor (AX,DX,CX = ptr,len,cap), the f64 the SSE cursor (X0)
|
||||||
|
* — independent counters, the float consuming no GP slot. The
|
||||||
|
* destructure path's cursor handled str-first on master too, so this
|
||||||
|
* pins the dual-cursor restructure PRESERVED it (byte-id both ways)
|
||||||
|
* AND that the f64 coexists. s.len=5 ("hello"), f=4.0. */
|
||||||
|
{ "str_f64_destr",
|
||||||
|
"package main;\n"
|
||||||
|
"fn issub(n: f64) bool = { return false; };\n"
|
||||||
|
"fn sf(n: f64) (str, f64) = {\n"
|
||||||
|
"\tif (issub(n)) { return (\"x\", n*2.0); };\n"
|
||||||
|
"\treturn (\"hello\", n);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet (s, f) = sf(4.0);\n"
|
||||||
|
"\tif (s.len != 5) { return 1; };\n"
|
||||||
|
"\tif (f != 4.0) { return 2; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 1 },
|
||||||
|
/* #164 STR-FIRST destructure (str, i64): pure-integer str-first.
|
||||||
|
* str@AX,DX,CX then i64@R8. Correct on master too (single-cursor
|
||||||
|
* destructure already routed ptr=AX) — byte-id regression guard that
|
||||||
|
* the dual cursor left the integer str-first mapping intact. s.len=5,
|
||||||
|
* k=7. */
|
||||||
|
{ "str_i64_destr",
|
||||||
|
"package main;\n"
|
||||||
|
"fn issub(n: i64) bool = { return false; };\n"
|
||||||
|
"fn si(n: i64) (str, i64) = {\n"
|
||||||
|
"\tif (issub(n)) { return (\"x\", n*2); };\n"
|
||||||
|
"\treturn (\"hello\", n);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet (s, k) = si(7);\n"
|
||||||
|
"\tif (s.len != 5) { return 1; };\n"
|
||||||
|
"\tif (k != 7) { return 2; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0 },
|
||||||
|
/* #164 STR-FIRST SINGLE-VAR (str, i64), annotated `let t: (str,i64) =
|
||||||
|
* si(); t.0/t.1`: the shape the OLD 32B single-var branch got WRONG —
|
||||||
|
* it read .ptr from DX while the send placed .ptr in AX (self-
|
||||||
|
* inconsistent), scrambling the slot so t.1 read the str.ptr word.
|
||||||
|
* DISCRIMINATES: pre-fix t.1 = a large address != 7; post-fix the
|
||||||
|
* unified dual cursor lands str@AX,DX,CX + i64@R8 so t.1=7. Both
|
||||||
|
* stages were wrong IDENTICALLY pre-fix (byte-id held, runtime broke),
|
||||||
|
* right identically post-fix. Annotated (not bare `const r=`) because
|
||||||
|
* the wwstage 32B single-var branch keys on the N_TTUPLE type node —
|
||||||
|
* a bare 32B single-var is a pre-existing stage asymmetry out of #164
|
||||||
|
* scope (16B bare single-var rows above cover the inferred path). */
|
||||||
|
{ "str_i64_single",
|
||||||
|
"package main;\n"
|
||||||
|
"fn issub(n: i64) bool = { return false; };\n"
|
||||||
|
"fn si(n: i64) (str, i64) = {\n"
|
||||||
|
"\tif (issub(n)) { return (\"x\", n*2); };\n"
|
||||||
|
"\treturn (\"hello\", n);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet t: (str, i64) = si(7);\n"
|
||||||
|
"\tif (t.1 != 7) { return 1; };\n"
|
||||||
|
"\tif (t.0.len != 5) { return 2; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0 },
|
||||||
|
/* #164 LOUD-STOP — three f64 = 0 GP / 3 SSE exceeds the SSE return
|
||||||
|
* cap (X0,X1 only). Must FAIL TO COMPILE in BOTH stages (rule-7:
|
||||||
|
* surface, never silently collide). Master has no SSE cap and
|
||||||
|
* miscompiles (build succeeds), so want_compile_fail discriminates:
|
||||||
|
* pre-fix the build succeeds (test fails), post-fix both stages
|
||||||
|
* error (test passes). */
|
||||||
|
{ "f64x3_loudstop",
|
||||||
|
"package main;\n"
|
||||||
|
"fn tri(a: f64, b: f64, c: f64) (f64, f64, f64) = {\n"
|
||||||
|
"\treturn (a, b, c);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet (x, y, z) = tri(1.0, 2.0, 3.0);\n"
|
||||||
|
"\tif (x != 1.0) { return 1; };\n"
|
||||||
|
"\tif (y != 2.0) { return 2; };\n"
|
||||||
|
"\tif (z != 3.0) { return 3; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 0, 1 },
|
||||||
/* CONTROL — all-integer branched 2-tuple. The integer-cursor MOVQ
|
/* CONTROL — all-integer branched 2-tuple. The integer-cursor MOVQ
|
||||||
* path is untouched by the fix (e0/e1 not float), so this is correct
|
* path is untouched by the fix (e0/e1 not float), so this is correct
|
||||||
* pre- and post-fix and byte-id both ways. a=5, b=7 -> 12. */
|
* pre- and post-fix and byte-id both ways. a=5, b=7 -> 12. */
|
||||||
@@ -289,13 +478,51 @@ main(void)
|
|||||||
fputs(rows[i].src, f);
|
fputs(rows[i].src, f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
|
char cmd[2048];
|
||||||
|
|
||||||
|
/* #164 LOUD-STOP rows: the SSE-cap overflow must FAIL TO COMPILE
|
||||||
|
* in BOTH stages (rule-7). Assert (a) cstage `ww build` errors,
|
||||||
|
* and (b) w6c AND w6c_ww each return non-zero — proving the
|
||||||
|
* loud-stop fires symmetrically. No .s is produced, so the
|
||||||
|
* byte-id cmp is skipped. Discriminates against master, which
|
||||||
|
* has no SSE cap and builds the (mis)compile. */
|
||||||
|
if (rows[i].want_compile_fail) {
|
||||||
|
char ldir[64];
|
||||||
|
snprintf(ldir, sizeof ldir, "/tmp/wwtupf_%d_l_%d",
|
||||||
|
getpid(), i);
|
||||||
|
mkdir(ldir, 0755);
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"cd %s && %s/ww build %s >/dev/null 2>&1",
|
||||||
|
ldir, bin, src);
|
||||||
|
if (runwait(cmd) == 0) {
|
||||||
|
fprintf(stderr, "row[%s]: cstage build SUCCEEDED, "
|
||||||
|
"want loud-stop (SSE cap)\n", rows[i].label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null",
|
||||||
|
w6c, src);
|
||||||
|
if (runwait(cmd) == 0) {
|
||||||
|
fprintf(stderr, "row[%s]: w6c emitted .s, want "
|
||||||
|
"loud-stop\n", rows[i].label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null",
|
||||||
|
w6c_ww, src);
|
||||||
|
if (runwait(cmd) == 0) {
|
||||||
|
fprintf(stderr, "row[%s]: w6c_ww emitted .s, want "
|
||||||
|
"loud-stop\n", rows[i].label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
unlink(src); rmdir(ldir);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* (a) cstage build + run in a scratch dir. */
|
/* (a) cstage build + run in a scratch dir. */
|
||||||
char tmpdir[64];
|
char tmpdir[64];
|
||||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwtupf_%d_d_%d",
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwtupf_%d_d_%d",
|
||||||
getpid(), i);
|
getpid(), i);
|
||||||
mkdir(tmpdir, 0755);
|
mkdir(tmpdir, 0755);
|
||||||
|
|
||||||
char cmd[2048];
|
|
||||||
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
|
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
|
||||||
tmpdir, bin, src);
|
tmpdir, bin, src);
|
||||||
if (runwait(cmd) != 0) {
|
if (runwait(cmd) != 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user