w6c+wwstage: cgexpr materializes tuple rvalues + unwrap-shift for tuple-payload destructure (#241)
cgexpr could not produce a tuple VALUE, so a destructure / let bind of an
RVALUE tuple read garbage past the first element (cstage) or left an untyped
binder aborting wwstage's asserttyped gate — a DANGEROUS gate-blind cs!=ww,
and the strconv-int blocker (Hare's stoi64/stou64 require
`let (sign, u) = parseint(s, base)?`). Three feeders, all routed at the same
SysV register-return cursor the cgmlet/cgmassign consumers already read:
- an N_TUPLE literal fell to the `cgexpr_int(0)` / `MOVQ $0, AX` default;
- a tuple-typed IDENT loaded only word0 into AX (`yield t`, `return t`,
`let q = t`), leaving DX/CX stale;
- the `?`/`!` unwrap of a tuple-in-union payload lifted only word0->AX,
stranding word1 in CX (the scalar/str success ABI).
Fix (both stages, byte-identical per rule 10):
- cgexpr packs an N_TUPLE literal into the cursor (cg_tuple_lit_to_cursor /
cgtuplelittocursor — a byte-identical reuse of cgreturn's in-register
N_TUPLE arm) and a tuple IDENT from its slot at the register-ABI stride
(cg_tuple_slot_to_cursor / cgtupleslottocursor);
- the ?/! unwrap shifts a tuple success payload down one integer reg past
the tag (cg_tagged_tuple_payload_shift / cgtaggedtuplepayloadshift),
loud-stopping a float/slice/str payload element (the SysV per-eightbyte
tagged-tuple-payload classification is #243);
- wwstage's checker recovers the popped match-arm binder type for a
`yield <binder>` operand (matchyieldtype's scope-free fallback to the
arm's declared type), so the destructured binders stamp — cstage reads
the operand's already-stamped ->type, wwstage caches only a tinfo.
Over-cap rvalue-tuple materialisation (no slot to sret a bare expression
value into) loud-stops both stages — the #10 follow-up.
NOT closed (distinct root, deferred to #238/task #6): single-var
`let q = (true, 9u64)` then `q.N` — the N_LET tuple-init sz==16||32 gate
drops a narrow-first mixed tuple, and the N_DOT tuple-field PACKED-offset
reader disagrees with tuple_store's 8B stride. Not the rvalue-into-cursor
fix and not a strconv blocker (strconv destructures); documented at the test
header.
Test 945_rvalue_tuple_destructure_run: literal destructure, match-yield
destructure, and the ?-call strconv shape, each run + cs==ww byte-id on both
drivers (9 checks). Embedded w6c/wwdump combined.ww regenerated.
This commit is contained in:
191
cmd/w6c/cgen.c
191
cmd/w6c/cgen.c
@@ -2568,6 +2568,163 @@ cg_structlit_fill_bp(Cg *c, Local **locals_p, Type *lu, Node *lit, int bp_off)
|
||||
cg_structlit_fill(c, locals_p, lu, lit, DST_BP, 0, NULL, bp_off);
|
||||
}
|
||||
|
||||
/* cg_tuple_lit_to_cursor — #241: materialise an N_TUPLE literal's elements
|
||||
* into the SysV register-return cursor — integer words L→R over tuple_rseq
|
||||
* (AX,DX,CX,R8), floats over tuple_sse_seq (X0,X1), a slice/str's
|
||||
* {ptr,len,cap} header over three consecutive INTEGER regs — the SAME ABI a
|
||||
* tuple-returning CALL leaves, which every tuple consumer (tuple_store at
|
||||
* the N_LET/N_MLET sites) already reads. cgexpr otherwise can't make a tuple
|
||||
* value (the default arm zeroed AX), so a literal/yield rvalue tuple bound
|
||||
* or destructured read garbage past word0. Each element's cgexpr clobbers
|
||||
* AX/X0, so integer words spill L→R and pop into the cursor reversed, floats
|
||||
* spill to @tupfscr and reload by SSE index — INDEPENDENT counters (ref/qbe/
|
||||
* amd64/sysv.c retr). Byte-identical extraction of cgreturn's N_TUPLE arm,
|
||||
* now shared with cgexpr. Over-cap loud-stops (rule 7); a bare expression
|
||||
* value can't sret, so the >cap rvalue-tuple materialisation is the #10
|
||||
* follow-up. */
|
||||
static void
|
||||
cg_tuple_lit_to_cursor(Cg *c, Local **locals, Node *tuple)
|
||||
{
|
||||
int f32;
|
||||
int gptotal = 0, ssecount = 0;
|
||||
for (Node *e = tuple->list; e; e = e->next) {
|
||||
if (fld_isfloat(e->type, &f32))
|
||||
ssecount++;
|
||||
else
|
||||
gptotal += tuple_ebytes(node_isstr(e)
|
||||
|| node_isslice(e));
|
||||
}
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > TUPLE_SSECAP)
|
||||
fatal("tuple literal exceeds register-return ABI capacity "
|
||||
"(integer %d/%d, SSE %d/%d); over-cap rvalue-tuple "
|
||||
"materialisation is the #10 sret follow-up",
|
||||
gptotal, TUPLE_GPCAP, ssecount, TUPLE_SSECAP);
|
||||
int fscr = 0;
|
||||
if (ssecount > 0) {
|
||||
if (cg_tupfscr != 0)
|
||||
fscr = cg_tupfscr;
|
||||
else {
|
||||
fscr = local_alloc(c, locals, "@tupfscr",
|
||||
TUPLE_SSECAP * 8, cg_frame);
|
||||
cg_tupfscr = fscr;
|
||||
}
|
||||
}
|
||||
int sseidx = 0;
|
||||
for (Node *e = tuple->list; e; e = e->next) {
|
||||
int isflt = fld_isfloat(e->type, &f32);
|
||||
cgexpr(c, e, *locals);
|
||||
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));
|
||||
if (node_isstr(e) || node_isslice(e)) {
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
}
|
||||
}
|
||||
for (int i = gptotal - 1; i >= 0; i--)
|
||||
ins1(c, A_POPQ, areg(tuple_rseq[i]));
|
||||
int j = 0;
|
||||
for (Node *e = tuple->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++;
|
||||
}
|
||||
}
|
||||
|
||||
/* cg_tuple_slot_to_cursor — #241: load a tuple already materialised in a
|
||||
* BP-relative slot (a tuple-typed IDENT: a let-bound tuple, a match-bound
|
||||
* union payload) into the SAME register-return cursor. The slot uses the
|
||||
* register-ABI stride the tuple-init / #242 destructure write (a scalar 8B,
|
||||
* a slice/str its 3-word header), NOT the packed t.N field layout (#238).
|
||||
* All sources are memory, so each word loads straight into its cursor reg —
|
||||
* no spill dance (unlike the literal arm whose element cgexpr clobbers). So
|
||||
* `yield t` / `return t` / `let q = t` over a tuple ident leave the whole
|
||||
* tuple in the cursor, not just word0 in AX. Over-cap loud-stops (rule 7;
|
||||
* the #10 sret follow-up). */
|
||||
static void
|
||||
cg_tuple_slot_to_cursor(Cg *c, int srcoff, Type *tu)
|
||||
{
|
||||
int f32;
|
||||
int gptotal = 0, ssecount = 0;
|
||||
for (Tparam *p = tu->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
int wide = pu && (pu->kind == TY_SLICE || pu->kind == TY_STR);
|
||||
if (fld_isfloat(p->type, &f32))
|
||||
ssecount++;
|
||||
else
|
||||
gptotal += tuple_ebytes(wide);
|
||||
}
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > TUPLE_SSECAP)
|
||||
fatal("tuple ident exceeds register-return ABI capacity "
|
||||
"(integer %d/%d, SSE %d/%d); over-cap rvalue-tuple "
|
||||
"materialisation is the #10 sret follow-up",
|
||||
gptotal, TUPLE_GPCAP, ssecount, TUPLE_SSECAP);
|
||||
int gp = 0, sse = 0, foff = 0;
|
||||
for (Tparam *p = tu->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
int wide = pu && (pu->kind == TY_SLICE || pu->kind == TY_STR);
|
||||
int isflt = fld_isfloat(p->type, &f32);
|
||||
if (isflt) {
|
||||
ins2(c, f32 ? A_MOVSS : A_MOVSD,
|
||||
amem(D_BP, srcoff + foff),
|
||||
areg(tuple_sse_seq[sse]));
|
||||
sse++;
|
||||
foff += 8;
|
||||
} else if (wide) {
|
||||
for (int k = 0; k < 3; k++)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, srcoff + foff + k * 8),
|
||||
areg(tuple_rseq[gp + k]));
|
||||
gp += 3;
|
||||
foff += (int)pu->size;
|
||||
} else {
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff + foff),
|
||||
areg(tuple_rseq[gp]));
|
||||
gp += 1;
|
||||
foff += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* cg_tagged_tuple_payload_shift — #241: a `?`-unwrapped tuple payload is an
|
||||
* rvalue tuple that must fill the register cursor the let/destructure
|
||||
* consumer reads. A tagged return leaves AX=tag, DX=word0, CX=word1,
|
||||
* R8=word2; the scalar/str unwrap lifts only word0->AX, stranding word1+ in
|
||||
* CX/R8. Shift the whole payload DOWN one INTEGER reg so element i lands in
|
||||
* tuple_rseq[i]. A float/slice/str payload element rides a different SysV
|
||||
* class (X regs / 3-word header) the flat down-shift can't place — loud-stop
|
||||
* (rule 7); the per-eightbyte tagged-tuple-payload classification is the
|
||||
* #243 follow-up. */
|
||||
static void
|
||||
cg_tagged_tuple_payload_shift(Cg *c, Type *tup)
|
||||
{
|
||||
static const int seq[] = { D_AX, D_DX, D_CX, D_R8 };
|
||||
int f32;
|
||||
int words = 0;
|
||||
for (Tparam *p = tup->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
int wide = pu && (pu->kind == TY_SLICE || pu->kind == TY_STR);
|
||||
if (fld_isfloat(p->type, &f32) || wide)
|
||||
fatal("tuple-in-union ? unwrap: float/slice/str payload "
|
||||
"element needs SysV per-eightbyte classification "
|
||||
"(see #243); only integer tuple payloads supported");
|
||||
words += tuple_ebytes(0);
|
||||
}
|
||||
/* tag occupies AX, so only DX/CX/R8 carry payload words. */
|
||||
if (words > (int)nelem(seq) - 1)
|
||||
fatal("tuple-in-union ? unwrap payload exceeds the 3 integer "
|
||||
"return regs past the tag (%d words); see #10/#243", words);
|
||||
for (int i = 0; i < words; i++)
|
||||
ins2(c, A_MOVQ, areg(seq[i + 1]), areg(seq[i]));
|
||||
}
|
||||
|
||||
static void
|
||||
cgexpr(Cg *c, Node *n, Local *locals)
|
||||
{
|
||||
@@ -2612,7 +2769,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
case N_IDENT: {
|
||||
int off = localfind(locals, n->str);
|
||||
if (off != 0) {
|
||||
if (node_isfloat(n)) {
|
||||
Type *itu = type_chase_named(n->type);
|
||||
if (itu && itu->kind == TY_TUPLE) {
|
||||
/* #241: a tuple ident is a value — leave the whole
|
||||
* tuple in the register cursor (`yield t` / `return
|
||||
* t` / `let q = t`), not just word0 in AX. */
|
||||
cg_tuple_slot_to_cursor(c, off, itu);
|
||||
} else if (node_isfloat(n)) {
|
||||
int op = op_for(n, A_MOVSD, A_MOVSS);
|
||||
ins2(c, op, amem(D_BP, off), areg(D_X0));
|
||||
} else if (node_isstr(n)) {
|
||||
@@ -6635,6 +6798,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_POPQ, areg(D_BP));
|
||||
ins0(c, A_RET);
|
||||
label(c, cont);
|
||||
{
|
||||
/* #241: a tuple success payload is an rvalue tuple — fill
|
||||
* the cursor (shift past the tag) so the destructure /
|
||||
* let consumer reads every element, not just word0. */
|
||||
Type *stu = type_chase_named(succ_t);
|
||||
if (stu && stu->kind == TY_TUPLE) {
|
||||
cg_tagged_tuple_payload_shift(c, stu);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (success_is_str) {
|
||||
/* str IS []u8: success value arrives in the tagged
|
||||
* ABI as DX=ptr, CX=len, R8=cap (slot 32B). Move len
|
||||
@@ -6677,6 +6850,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
||||
ins0(c, A_SYSCALL);
|
||||
label(c, cont);
|
||||
{
|
||||
/* #241: tuple success payload fills the cursor (shift past
|
||||
* the tag) — same rvalue-tuple-into-cursor story. */
|
||||
Type *stu = type_chase_named(succ_t);
|
||||
if (stu && stu->kind == TY_TUPLE) {
|
||||
cg_tagged_tuple_payload_shift(c, stu);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (success_is_str) {
|
||||
/* str IS []u8: success arrives DX=ptr, CX=len, R8=cap
|
||||
* (slot 32B). Move len out before cap clobbers CX
|
||||
@@ -7965,6 +8147,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case N_TUPLE:
|
||||
/* #241: a literal tuple rvalue `(a, b)` is a value — pack its
|
||||
* elements into the register cursor (mirror cgreturn's N_TUPLE
|
||||
* arm) so a let-bind / destructure consumer reads every element,
|
||||
* not just AX = 0 from the default arm below. */
|
||||
cg_tuple_lit_to_cursor(c, &locals, n);
|
||||
break;
|
||||
default:
|
||||
cgexpr_int(c, 0);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user