wcc/cgen: #64+#68 tuple-literal cursor-fill decl-blind — massign + call-arg widen (both-stage)
A tuple LITERAL with a declared-tagged element reached the cursor-fill
helper (cg_tuple_lit_to_cursor) through the generic cgexpr(N_TUPLE) arm
with no declared type, so the element was stored stamped-keyed at its
constructed scalar width rather than widened into the declared tagged box.
Both consumers ran silent and wrong on both stages (#263 gate-blind:
cs==ww byte-identical, both wrong — runtime is the only net).
#64 massign: N_MASSIGN derives a declared tuple type from the lvalue
binding types and threads it into cg_tuple_lit_to_cursor + the receive
loop (mirror of the #57 N_LET wire); a `_` target falls back to the rhs
literal element type for cursor stride.
#68 call-arg: the send is made param-aware (fill over the PARAM tuple) and
the restage guard graduates a declared-tagged element to a real widen
(reusing cg_widen_tagged_store); nested tuple/struct/array elements and
tagged elements with no param decl stay rule-7 loud. The matching
pop/drain is made param-aware too so push count == pop count: a
param-aware send pushes the box's N words, so the drain must pop N or the
SysV arg sequence skews. This is a push/pop balance requirement of the
send change, not a separate latent under-drain (the standalone trailing-
arg drain is already correct at HEAD).
Closed by construction: the only remaining cg_tuple_lit_to_cursor caller
passing NULL/nil is the generic cgexpr(N_TUPLE) arm, provably non-widening
(constructed type == governing type). The four widening consumers — LET,
RETURN, MASSIGN, call-arg — are all decl-wired. Whole-tuple single-ident
reassign from a tuple literal is rule-7 loud (task #49), not a silent
widening consumer, so the residual NULL arm stays non-widening.
Pin: 945_tuple_lit_declblind_run — massign / call-arg / `_`-control /
call-arg-drain / nested-tuple-ERR rows, each base-fail at abd97e6 and
post-pass with cs==ww byte-id.
This commit is contained in:
@@ -17065,22 +17065,40 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
emitline("\tX0, (SP)\n");
|
||||
return rest + 1;
|
||||
};
|
||||
cgexpr(c, arg);
|
||||
// #163/#32 (C-t2): tuple ARG (param twin of #164's return). cgexpr
|
||||
// left the tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1) for
|
||||
// every nodetuplearg producer — call (return ABI), ident
|
||||
// (cgtupleslottocursor), literal (cgtuplelittocursor), `?`/`!`
|
||||
// #68: a tuple-LITERAL arg derives its DECLARED tuple type from the
|
||||
// callee PARAM type node (the #57 decl wire, extended to call-arg
|
||||
// send), so a declared-tagged element's concrete rvalue widens into
|
||||
// the box cursor; the restage + push then key on the param tuple type
|
||||
// node (declared element widths), not the literal's element-
|
||||
// constructed types. Mirrors cstage cgcall paramtup.
|
||||
let paramtt: *node = nil;
|
||||
if (arg.kind == nkind.N_TUPLE) { if (param != nil) {
|
||||
if (param.kind == nkind.N_PARAM && param.lhs != nil) {
|
||||
let ptn: *node = param.lhs;
|
||||
for (ptn != nil && ptn.kind == nkind.N_TNAME) {
|
||||
ptn = aliaslookup(c, ptn.str);
|
||||
};
|
||||
if (ptn != nil) { if (ptn.kind == nkind.N_TTUPLE) { paramtt = ptn; }; };
|
||||
};
|
||||
}; };
|
||||
if (paramtt != nil) { cgtuplelittocursor(c, arg, paramtt); }
|
||||
else { cgexpr(c, arg); };
|
||||
// #163/#32 (C-t2): tuple ARG (param twin of #164's return). cgexpr /
|
||||
// the decl-aware fill left the tuple in the return-ABI cursor (AX/DX/
|
||||
// CX/R8 + X0/X1) for every nodetuplearg producer — call (return ABI),
|
||||
// ident (cgtupleslottocursor), literal (cgtuplelittocursor), `?`/`!`
|
||||
// unwrap (payload shift); restage it into @tupargscr by SysV class
|
||||
// (tupstore, the #164 helper) and push the slot words high->low so
|
||||
// the pop drains slot+0 first into the SysV ARG cursor. The frame
|
||||
// slot decouples the return-class regs from the overlapping
|
||||
// arg-class regs. An N_TUPLE literal's elements are VALUE exprs —
|
||||
// classify them the way cgtuplelittocursor does (nodeisstr/
|
||||
// nodeisslice), kind-discriminated; the stride is the slot formula
|
||||
// (wide ? header : 8), the same number slotsize() gives a type node.
|
||||
let tuparg: *node = nodetuplearg(c, arg);
|
||||
// arg-class regs. When the PARAM tuple type is known (#68), walk its
|
||||
// declared element type nodes (p.lhs); else an N_TUPLE literal's
|
||||
// elements are VALUE exprs classified the way cgtuplelittocursor does.
|
||||
let tuparg: *node = paramtt;
|
||||
if (tuparg == nil) { tuparg = nodetuplearg(c, arg); };
|
||||
if (tuparg != nil) {
|
||||
let tuplit: bool = tuparg.kind == nkind.N_TUPLE;
|
||||
let tuplit: bool = false;
|
||||
if (paramtt == nil) { if (tuparg.kind == nkind.N_TUPLE) { tuplit = true; }; };
|
||||
let gptot: i32 = 0;
|
||||
let sstot: i32 = 0;
|
||||
let tsz: i32 = 0;
|
||||
@@ -17099,23 +17117,34 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
let eti: *tinfo = et.type_: *tinfo;
|
||||
eti = tichase(eti);
|
||||
if (eti != nil) {
|
||||
if (eti.kind == tykind.TY_TUPLE
|
||||
let bad: bool = eti.kind == tykind.TY_TUPLE
|
||||
|| eti.kind == tykind.TY_STRUCT
|
||||
|| eti.kind == tykind.TY_ARRAY
|
||||
|| eti.kind == tykind.TY_TAGGED) {
|
||||
|| eti.kind == tykind.TY_ARRAY;
|
||||
// #68: a declared-tagged element graduates to a real
|
||||
// widen — the decl-aware send (cgtuplelittocursor over
|
||||
// the param tuple type) left the box words in the
|
||||
// cursor, so tupstore below carries them. A tagged
|
||||
// element with no param decl stays rule-7 loud (the
|
||||
// cursor was filled stamped-keyed).
|
||||
if (eti.kind == tykind.TY_TAGGED && paramtt == nil) { bad = true; };
|
||||
if (bad) {
|
||||
let mne: str = "#32: tuple arg element kind unsupported (nested tuple/struct/array/tagged; rule 7)\n";
|
||||
os.write(2, mne.ptr, mne.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
// tagged is guarded loud above, so wide-vs-scalar is
|
||||
// the full slot split here; eslot keeps the stride
|
||||
// arithmetic on the accessor scale (#22).
|
||||
let wide: bool = false;
|
||||
if (tuplit) { wide = nodeisstr(c, et) || nodeisslice(c, et); }
|
||||
else { wide = isstrtype(c, et) || isslicetype(c, et); };
|
||||
// eslot — the full slot stride (str/slice header, tagged
|
||||
// box, else 8); on the declared (non-tuplit) path tupeslotn
|
||||
// reads the element TYPE node directly (#68 box-aware,
|
||||
// mirrors cstage tuple_eslot). A literal element rides the
|
||||
// wide-vs-scalar split off its VALUE node.
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
if (tuplit) {
|
||||
let wide: bool = nodeisstr(c, et) || nodeisslice(c, et);
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
} else {
|
||||
eslot = tupeslotn(et);
|
||||
};
|
||||
if (isfloattype(c, et)) { sstot += 1; }
|
||||
else { gptot += eslot / 8; };
|
||||
tsz += eslot;
|
||||
@@ -17136,11 +17165,13 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
for (p != nil) {
|
||||
let et: *node = p.lhs;
|
||||
if (tuplit) { et = p; };
|
||||
let wide: bool = false;
|
||||
if (tuplit) { wide = nodeisstr(c, et) || nodeisslice(c, et); }
|
||||
else { wide = isstrtype(c, et) || isslicetype(c, et); };
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
if (tuplit) {
|
||||
let wide: bool = nodeisstr(c, et) || nodeisslice(c, et);
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
} else {
|
||||
eslot = tupeslotn(et);
|
||||
};
|
||||
tupstore(c, gpcur, ssecur, scr + eoff, eslot, et);
|
||||
if (isfloattype(c, et)) { ssecur += 1; }
|
||||
else { gpcur += eslot / 8; };
|
||||
@@ -28798,19 +28829,38 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
stackslots += 1;
|
||||
};
|
||||
popped += 1;
|
||||
} else { let tuparg: *node = nodetuplearg(c, a);
|
||||
} else {
|
||||
// #68: drain over the PARAM tuple element widths for a
|
||||
// tuple LITERAL arg (the declared-tagged box words pop
|
||||
// together with the i64 that follows), mirroring the
|
||||
// param-aware send; else the source tuple type.
|
||||
let dptt: *node = nil;
|
||||
if (a.kind == nkind.N_TUPLE) { if (dparam != nil) {
|
||||
if (dparam.kind == nkind.N_PARAM && dparam.lhs != nil) {
|
||||
let dtn2: *node = dparam.lhs;
|
||||
for (dtn2 != nil && dtn2.kind == nkind.N_TNAME) {
|
||||
dtn2 = aliaslookup(c, dtn2.str);
|
||||
};
|
||||
if (dtn2 != nil) { if (dtn2.kind == nkind.N_TTUPLE) { dptt = dtn2; }; };
|
||||
};
|
||||
}; };
|
||||
let tuparg: *node = dptt;
|
||||
if (tuparg == nil) { tuparg = nodetuplearg(c, a); };
|
||||
if (tuparg != nil) {
|
||||
// #163/#32: drain the tuple's staged words (slot+0
|
||||
// pushed first) into the SysV arg cursor by SysV class
|
||||
// — a float MOVSD/MOVSS off (SP) into the next XMM,
|
||||
// else POPQ into the next INTEGER arg reg; a slice/str
|
||||
// its 3 words. Reg overflow loud-stops (rule 7); the
|
||||
// its 3 words, a declared-tagged box its eslot words
|
||||
// (#68). Reg overflow loud-stops (rule 7); the
|
||||
// partial-spill stitch is out of scope (twin of #164).
|
||||
// C-t2: nodetuplearg admits ident/literal/unwrap
|
||||
// sources; a literal's elements are VALUE exprs,
|
||||
// classified the way the @tupargscr restage classified
|
||||
// them (kind-discriminated twin walk).
|
||||
let tuplit: bool = tuparg.kind == nkind.N_TUPLE;
|
||||
// them (kind-discriminated twin walk). The param-aware
|
||||
// path (dptt) walks declared element TYPE nodes.
|
||||
let tuplit: bool = false;
|
||||
if (dptt == nil) { if (tuparg.kind == nkind.N_TUPLE) { tuplit = true; }; };
|
||||
let p: *node = tuparg.list;
|
||||
for (p != nil) {
|
||||
let et: *node = p.lhs;
|
||||
@@ -28832,14 +28882,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
fpidx += 1;
|
||||
popped += 1;
|
||||
} else {
|
||||
// tagged is guarded loud at the restage, so
|
||||
// wide-vs-scalar is the full slot split here
|
||||
// (#22 accessor scale).
|
||||
let wide: bool = false;
|
||||
if (tuplit) { wide = nodeisstr(c, et) || nodeisslice(c, et); }
|
||||
else { wide = isstrtype(c, et) || isslicetype(c, et); };
|
||||
// eslot — full slot split; on the declared
|
||||
// path tupeslotn reads the element TYPE node
|
||||
// directly (#68 box-aware), else wide-vs-scalar
|
||||
// off the literal VALUE node (#22 accessor scale).
|
||||
let eb: i32 = 1;
|
||||
if (wide) { eb = (tyslicesize() / 8i64): i32; };
|
||||
if (tuplit) {
|
||||
let wide: bool = nodeisstr(c, et) || nodeisslice(c, et);
|
||||
if (wide) { eb = (tyslicesize() / 8i64): i32; };
|
||||
} else {
|
||||
eb = tupeslotn(et) / 8;
|
||||
};
|
||||
if (intidx + eb > 6) {
|
||||
let msg: str = "tuple arg element overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #163\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
@@ -36355,11 +36408,37 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// #64 (filed, rule 7): an N_TUPLE literal rhs rides this decl-less
|
||||
// cgexpr route, so a declared-TAGGED element's concrete rvalue
|
||||
// still fills the cursor stamped-keyed (silent skew) — the #57
|
||||
// decl wire stops at return/let.
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
// #64: a tuple-LITERAL rhs carries a DECLARED tuple type (built from
|
||||
// the lvalue binding types) into the cursor fill, so a declared-tagged
|
||||
// element's concrete rvalue widens into the box instead of riding the
|
||||
// decl-less stamped-keyed route — the #57 decl wire extended past
|
||||
// cgmlet/cgreturn to destructure-reassign. A `_` lvalue has no local
|
||||
// (no declared type node); its decl element stays nil and the
|
||||
// fill/receive fall back to the rhs literal element's own stamped type
|
||||
// for the cursor stride (harec `_` advance; pinned by the R3 control).
|
||||
let litrhs: bool = false;
|
||||
if (n.rhs != nil) { if (n.rhs.kind == nkind.N_TUPLE) { litrhs = true; }; };
|
||||
let synthdecl: *node = nil;
|
||||
if (litrhs) {
|
||||
synthdecl = newnode(nkind.N_TTUPLE, n.rhs.file, n.rhs.line, n.rhs.col);
|
||||
let dtail: *node = nil;
|
||||
let lb0: *node = n.list;
|
||||
for (lb0 != nil) {
|
||||
let w: *node = newnode(nkind.N_TUPLE, n.rhs.file, n.rhs.line, n.rhs.col);
|
||||
w.lhs = nil;
|
||||
if (lb0.kind == nkind.N_IDENT) {
|
||||
let lc0: *local = localfindnode(c, lb0.str);
|
||||
if (lc0 != nil) { w.lhs = lc0.tnode; };
|
||||
};
|
||||
w.next = nil;
|
||||
if (dtail == nil) { synthdecl.list = w; } else { dtail.next = w; };
|
||||
dtail = w;
|
||||
lb0 = lb0.next;
|
||||
};
|
||||
cgtuplelittocursor(c, n.rhs, synthdecl);
|
||||
} else {
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
};
|
||||
|
||||
if (sretrecv > 0) {
|
||||
let scr: i32 = localfind(c, "@sretscr");
|
||||
@@ -36438,9 +36517,20 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
let l: *node = n.list;
|
||||
let pt: *node = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
// #64: a tuple-LITERAL rhs keys element WIDTH on the DECLARED lvalue
|
||||
// type (synthdecl), not the rettuple (nil for a literal); a `_` slot
|
||||
// (declared type nil) falls back to the rhs literal element's own
|
||||
// stamped type for the cursor stride.
|
||||
let dp: *node = nil;
|
||||
let re: *node = nil;
|
||||
if (litrhs) { dp = synthdecl.list; re = n.rhs.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
if (litrhs) {
|
||||
if (dp != nil && dp.lhs != nil) { tn = dp.lhs; } else { tn = re; };
|
||||
} else {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
if (isfloattype(c, tn)) {
|
||||
ssetotal = ssetotal + 1;
|
||||
} else {
|
||||
@@ -36448,6 +36538,8 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
if (dp != nil) { dp = dp.next; };
|
||||
if (re != nil) { re = re.next; };
|
||||
};
|
||||
if (gptotal > TUPLE_GPCAP) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
@@ -36467,9 +36559,16 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
l = n.list;
|
||||
pt = nil;
|
||||
if (rettuple != nil) { pt = rettuple.list; };
|
||||
dp = nil;
|
||||
re = nil;
|
||||
if (litrhs) { dp = synthdecl.list; re = n.rhs.list; };
|
||||
for (l != nil) {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
if (litrhs) {
|
||||
if (dp != nil && dp.lhs != nil) { tn = dp.lhs; } else { tn = re; };
|
||||
} else {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let off: i32 = 0;
|
||||
@@ -36486,6 +36585,8 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
if (dp != nil) { dp = dp.next; };
|
||||
if (re != nil) { re = re.next; };
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user