w6c+wwstage: construct + bind tuple-in-union payload (#242)
A mixed-scalar tuple WRAPPED IN A TAGGED UNION (the (neg, n) shape Hare's
strconv parseint returns, ((bool,u64)|invalid|overflow)) miscompiled three
ways, all gate-blind (no bootstrap tuple-in-union):
(a) cstage CONSTRUCTION: a tuple variant fell through the N_RETURN scalar
shuffle, which ZEROED tag + payload — the operands were never packed.
Route the tuple variant through the scratch-slot widen path; add a
TY_TUPLE arm to cg_widen_tagged_store that packs each element into the
union payload at the register-ABI 8B stride + sets the variant tag.
(b) wwstage CHECKER: `let (a,b)=t` over a plain tuple ident (the match-
bound union payload) left the un-annotated binders UNTYPED, so the bin
node reading them was untyped -> asserttyped abort. The element-type
distribution only fired for an N_CALL rhs. Consume the rhs tuple type
for ANY rhs (mirror cstage check.c:2017).
(c) BOTH stages DESTRUCTURE: the register-cursor receive assumes the rhs
left every element in AX/DX/CX (a call's tuple-return ABI). For a tuple
IDENT cgexpr loads only word0->AX, so the 2nd binder read a STALE DX.
Copy each element from the ident's slot at the 8B stride.
Construction is correct at ANY variant position (the resolved tag, not a
default 0); wwstage resolves it via the typeeq core (flatvariantidxt), not
taggedvariantindext whose str/slice shape-fallback would mask a mismatch.
Two rule-7 loud-stops cover shapes this slotted packing can't yet handle,
on BOTH stages, so neither silently miscompiles:
- a tuple with a SysV-eightbyte-sharing narrow pair (e.g. (i32,i32,u64)),
caught by the 8+payload > slot-size guard (the eightbyte tuple
classification is #243);
- a tuple built from a BARE LITERAL element (`true`/`false`, suffix-less
`7`). cstage's cg_tag_for_variant can't type the literal (#241), returns
-1, and loud-stops. wwstage types `true` as bool and `7` as untyped_int,
so flatvariantidxt WOULD resolve the variant — a program cstage rejects
but wwstage accepts is the cs!=ww divergence rule 10 forbids. wwstage
mirrors cstage's CONDITION (a bare-literal element), not its -1
mechanism, with an explicit guard that aligns the richer side DOWN. Lift
BOTH guards together when #241 lands cstage literal typing -> symmetric
accept.
Test 940_tuple_in_union: 4 K_RUN rows (variant 0, void arm, tuple at
variant 1 two ways) x cstage-run + wwstage-run + cs==ww byte-id, plus 2
K_BUILDERR rows (eightbyte-share, bare-literal) asserting a loud stop with
the #242 diagnostic on BOTH drivers = 16 ok.
This commit is contained in:
126
cmd/w6c/cgen.c
126
cmd/w6c/cgen.c
@@ -2034,6 +2034,74 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
if (via_outer) goto copy_out;
|
||||
return;
|
||||
}
|
||||
/* #242: tuple payload. Each element rides ONE register-ABI
|
||||
* eightbyte — scalar/float a single 8B word, a slice/str its 3-word
|
||||
* {ptr,len,cap} header (24B) — matching the tagged-return load
|
||||
* (AX=tag, DX=word0, CX=word1, R8=word2) and the cgmlet receive
|
||||
* cursor. NOT the packed-by-size t.N field layout (#238). Mirror of
|
||||
* the struct-literal field-flow below, but 8B-slotted, not field-
|
||||
* offset. */
|
||||
if (su && su->kind == TY_TUPLE && src->kind == N_TUPLE) {
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
/* #242: a tuple built from UNTYPED/literal elements (`(true,7)`)
|
||||
* leaves the src tuple type un-matchable by type_eq, so the
|
||||
* variant tag can't resolve — the supported shape is a tuple of
|
||||
* TYPED expressions (the strconv parseint `(neg, n)` shape).
|
||||
* Loud-stop rather than silently mis-tag (tag 0) — rule 7.
|
||||
* Untyped tuple-element coercion is the #241 literal-init
|
||||
* family. */
|
||||
if (tag < 0)
|
||||
fatal("cg_widen_tagged_store: tuple-in-union variant tag "
|
||||
"unresolved (untyped/literal tuple element; "
|
||||
"see #242 / #241)");
|
||||
/* #242: this 8B-per-eightbyte packing is correct only when no
|
||||
* two scalar elements share a SysV eightbyte — e.g. (bool,u64),
|
||||
* where the sub-8 bool is padded out by u64's 8-alignment. A
|
||||
* tuple whose natural aligned layout packs two narrows into one
|
||||
* eightbyte (e.g. (i32,i32,u64)) would overflow the union
|
||||
* payload the slotted write assumes. Loud-stop (rule 7); the
|
||||
* SysV eightbyte tuple classification is a deferred follow-up. */
|
||||
int total = 0;
|
||||
for (Node *e = src->list; e; e = e->next)
|
||||
total += (node_isstr(e) || node_isslice(e)) ? 24 : 8;
|
||||
if (8 + total > sz)
|
||||
fatal("cg_widen_tagged_store: tuple-in-union payload needs "
|
||||
"SysV eightbyte packing (narrow elements share an "
|
||||
"eightbyte; see #242 follow-up)");
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = 0; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, write_off + k));
|
||||
int foff = 0;
|
||||
for (Node *e = src->list; e; e = e->next) {
|
||||
int e_isf32 = 0;
|
||||
int isflt = fld_isfloat(e->type, &e_isf32);
|
||||
int wide = node_isstr(e) || node_isslice(e);
|
||||
int esz = e->type ? (int)e->type->size : 8;
|
||||
cgexpr(c, e, *locals_p);
|
||||
if (isflt) {
|
||||
ins2(c, e_isf32 ? A_MOVSS : A_MOVSD,
|
||||
areg(D_X0),
|
||||
amem(D_BP, write_off + 8 + foff));
|
||||
} else if (wide) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, write_off + 8 + foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, write_off + 8 + foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, write_off + 8 + foff + 16));
|
||||
} else {
|
||||
ins2(c, fldstoreop(e->type, esz),
|
||||
areg(D_AX),
|
||||
amem(D_BP, write_off + 8 + foff));
|
||||
}
|
||||
foff += wide ? 24 : 8;
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, write_off + 0));
|
||||
if (via_outer) goto copy_out;
|
||||
return;
|
||||
}
|
||||
/* Struct payload: zero the whole slot, then write fields/words
|
||||
* at slot+8+ — keeping the tag word at slot+0 from the zero-fill,
|
||||
* then patch it with the variant tag. */
|
||||
@@ -8399,12 +8467,19 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
int passthrough = istagged && (vu == rt ||
|
||||
type_eq(vt, cg_ret_type));
|
||||
int isstruct = vu && vu->kind == TY_STRUCT;
|
||||
/* #242: a tuple variant must be PACKED into the union
|
||||
* payload (tag + per-element words), not shuffled like a
|
||||
* bare scalar — route it through the scratch-slot widen
|
||||
* path (cg_widen_tagged_store TY_TUPLE arm). The scalar
|
||||
* arm below zeroed the whole value (never packed the
|
||||
* operands). */
|
||||
int istuple = vu && vu->kind == TY_TUPLE;
|
||||
if (rt->nullable) {
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
} else if (passthrough) {
|
||||
/* same tagged type: forward AX/DX/CX. */
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
} else if (!istagged && !isstruct) {
|
||||
} else if (!istagged && !isstruct && !istuple) {
|
||||
/* str / slice / scalar variant: synthesise
|
||||
* the tag in AX and shuffle the value into
|
||||
* DX[/CX[/R8]]. Direct register path keeps
|
||||
@@ -9123,6 +9198,55 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
* at its NATURAL width (#169). The receive has no single lvalue
|
||||
* dest, so it reuses the same per-fn @sretscr slot a discarded
|
||||
* sret call would; the in-reg path below is unchanged. */
|
||||
/* #242: rhs is a tuple already materialised in a local slot (a
|
||||
* match-bound union payload, `let (a,b)=t`), NOT a register-
|
||||
* returning call. cgexpr(tuple ident) loads only word0->AX, so
|
||||
* the register-cursor path below reads DX/CX stale. Copy each
|
||||
* element from the ident's slot at the register-ABI 8B stride
|
||||
* (24B for a slice/str header) — the SAME layout the tagged
|
||||
* construct + match payload-bind write. */
|
||||
if (n->rhs && n->rhs->kind == N_IDENT) {
|
||||
Type *rty = type_chase_named(n->rhs->type);
|
||||
if (rty && rty->kind == TY_TUPLE) {
|
||||
int srcoff = localfind(*locals, n->rhs->str);
|
||||
int lf32b;
|
||||
int foff = 0;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *t = l->type;
|
||||
Type *u = type_chase_named(t);
|
||||
int wide = u && (u->kind == TY_SLICE
|
||||
|| u->kind == TY_STR);
|
||||
int isflt = fld_isfloat(t, &lf32b);
|
||||
int esz = t ? (int)t->size : 8;
|
||||
int bsz = wide ? esz : 8;
|
||||
int off = localoff(c, locals, l->str,
|
||||
bsz, frame);
|
||||
if (isflt) {
|
||||
ins2(c, lf32b ? A_MOVSS : A_MOVSD,
|
||||
amem(D_BP, srcoff + foff),
|
||||
areg(D_X0));
|
||||
ins2(c, lf32b ? A_MOVSS : A_MOVSD,
|
||||
areg(D_X0), amem(D_BP, off));
|
||||
} else if (wide) {
|
||||
for (int k = 0; k < esz; k += 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, srcoff + foff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + k));
|
||||
}
|
||||
} else {
|
||||
ins2(c, fldloadop(t, esz),
|
||||
amem(D_BP, srcoff + foff),
|
||||
areg(D_AX));
|
||||
ins2(c, fldstoreop(t, esz),
|
||||
areg(D_AX), amem(D_BP, off));
|
||||
}
|
||||
foff += wide ? 24 : 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
int sret_recv = (n->rhs && n->rhs->kind == N_CALL)
|
||||
? cg_sret_retsize(n->rhs->type) : 0;
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
|
||||
Reference in New Issue
Block a user