node_tuplearg was N_CALL-scoped and its comment claimed non-call forms "loud-stop" — they did NOT: a tuple ident/literal/unwrap arg fell to the scalar single-PUSHQ default, skewing every later arg register so the callee read garbage word 2 (byte-id both stages, the gate-blind both-wrong class; packed shapes SIGSEGV'd pre-C-t0). The receive side (cgfn #163 walk) was already correct. cgexpr already fills the return-ABI cursor for every supported producer (#241: ident via slot-to-cursor, literal via lit-to-cursor, unwrap via payload shift; call via the return ABI) — the send now admits exactly those into the existing @tupargscr restage + per-class drain (node_tuplearg widened; wwstage gains nodetuplearg, mirroring it over the local tnode / inferletcalltype; rettupleof stays N_CALL-scoped for the destructure receives). Any OTHER tuple-typed source shape loud-stops at the push site — the false comment's claim, now true (rule 7). Literal tuple elements are stamped expr types, so the restage/drain wide test goes type_isstr/type_isslice (TY_UNTYPED_STR- aware) with the ty_str->size header stride; the wwstage twin walks a literal's VALUE exprs the way cgtuplelittocursor classifies them. Ken review demands folded in: (1) a NESTED composite element (tuple/struct/array/tagged inside the tuple) occupies more than the one GP word the restage walk counts — the checker accepted it and it ran WRONG (inner words skewed, wwstage SIGSEGV); both stages' restage walks now loud-stop the element kind (wiring is the filed follow-up, task #65). (2) the variadic interaction probed: a tuple arg ahead of a variadic tail rides the restage correctly (positive row); variadic-of-tuples stays bounded-loud via the tuple-in-slice read surface. 941 grows the t2 matrix: packed/16B params with branched callees, mixed arg orders both ways, literal arg, (f64,i64) param, unwrap arg, ken's >6-GP-pressure stress (4 leading scalars + tuple + a 7th stack-class word), variadic-after-tuple, plus rule-7 reject rows (chain-source arg, nested-element arg, variadic-of-tuples, over-cap ident arg) and the fold-4 charset substrate pin ([](u32,u32) append stays LOUD). At the C-t1 parent 18/73 checks fail: every runtime arg row except the (f64,i64) anchor on BOTH stages (byte-identically — the gate-blind both-wrong class) and the chain/nested args silently accepted.
This commit is contained in:
@@ -16622,29 +16622,60 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
return rest + 1;
|
||||
};
|
||||
cgexpr(c, arg);
|
||||
// #163: tuple ARG (param twin of #164's return). cgexpr left the
|
||||
// tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1); 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. rettupleof scopes to an
|
||||
// N_CALL producer (tuple idents/literals as values are a separate
|
||||
// unimplemented gap; the SEND never pushes stale regs, rule 7).
|
||||
let tuparg: *node = rettupleof(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), `?`/`!`
|
||||
// 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);
|
||||
if (tuparg != nil) {
|
||||
let tuplit: bool = tuparg.kind == nkind.N_TUPLE;
|
||||
let gptot: i32 = 0;
|
||||
let sstot: i32 = 0;
|
||||
let tsz: i32 = 0;
|
||||
let p: *node = tuparg.list;
|
||||
for (p != nil) {
|
||||
let et: *node = p.lhs;
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
if (tuplit) { et = p; };
|
||||
// C-t2 (ken demand 1, rule 7): a COMPOSITE element
|
||||
// (nested tuple/struct/array/tagged) occupies more
|
||||
// than the one GP word this walk counts — the checker
|
||||
// accepts the shape but the cursor transport cannot
|
||||
// carry it; pre-guard it ran WRONG (inner words
|
||||
// skewed). The stamped tinfo classifies both type
|
||||
// nodes and literal value exprs. Mirrors the cstage
|
||||
// restage guard.
|
||||
let eti: *tinfo = et.type_: *tinfo;
|
||||
for (eti != nil && eti.kind == tykind.TY_NAMED) {
|
||||
eti = eti.under;
|
||||
};
|
||||
if (eti != nil) {
|
||||
if (eti.kind == tykind.TY_TUPLE
|
||||
|| eti.kind == tykind.TY_STRUCT
|
||||
|| eti.kind == tykind.TY_ARRAY
|
||||
|| eti.kind == tykind.TY_TAGGED) {
|
||||
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);
|
||||
};
|
||||
};
|
||||
let wide: bool = false;
|
||||
if (tuplit) { wide = nodeisstr(c, et) || nodeisslice(c, et); }
|
||||
else { wide = isstrtype(c, et) || isslicetype(c, et); };
|
||||
if (isfloattype(c, et)) { sstot += 1; }
|
||||
else { gptot += tupebytes(wide); };
|
||||
tsz += slotsize(c, et);
|
||||
if (wide) { tsz += (tyslicesize(): i32); }
|
||||
else { tsz += 8; };
|
||||
p = p.next;
|
||||
};
|
||||
// The producing call already satisfied #164's return caps;
|
||||
// The producing cursor fill already satisfied #164's caps;
|
||||
// guard anyway (tupstore indexes [AX,DX,CX,R8] / [X0,X1]).
|
||||
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP) {
|
||||
let msg: str = "tuple arg exceeds return-cursor ABI capacity; see #163/#164\n";
|
||||
@@ -16658,11 +16689,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
p = tuparg.list;
|
||||
for (p != nil) {
|
||||
let et: *node = p.lhs;
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
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); };
|
||||
tupstore(c, gpcur, ssecur, scr + eoff, wide, et);
|
||||
if (isfloattype(c, et)) { ssecur += 1; }
|
||||
else { gpcur += tupebytes(wide); };
|
||||
eoff += slotsize(c, et);
|
||||
if (wide) { eoff += (tyslicesize(): i32); }
|
||||
else { eoff += 8; };
|
||||
p = p.next;
|
||||
};
|
||||
let w: i32 = tsz - 8;
|
||||
@@ -16675,6 +16710,24 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
};
|
||||
return rest + tsz / 8;
|
||||
};
|
||||
// #32 (C-t2, rule 7): a tuple-typed arg from a source shape whose
|
||||
// cgexpr does NOT fill the return cursor (chain reads, match exprs,
|
||||
// ...) must die loud here — pre-fix it fell to the scalar
|
||||
// single-PUSHQ default and silently skewed every later arg
|
||||
// register. Mirrors the cstage cgcall guard.
|
||||
{
|
||||
let ati: *tinfo = arg.type_: *tinfo;
|
||||
for (ati != nil && ati.kind == tykind.TY_NAMED) {
|
||||
ati = ati.under;
|
||||
};
|
||||
if (ati != nil) {
|
||||
if (ati.kind == tykind.TY_TUPLE) {
|
||||
let m32: str = "#32: tuple arg from unsupported source shape (call/ident/literal/unwrap only; rule 7)\n";
|
||||
os.write(2, m32.ptr, m32.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (nodeisslice(c, arg)) {
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
@@ -26280,17 +26333,23 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
stackslots += 1;
|
||||
};
|
||||
popped += 1;
|
||||
} else { let tuparg: *node = rettupleof(c, a);
|
||||
} else { let tuparg: *node = nodetuplearg(c, a);
|
||||
if (tuparg != nil) {
|
||||
// #163: 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
|
||||
// #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
|
||||
// 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;
|
||||
let p: *node = tuparg.list;
|
||||
for (p != nil) {
|
||||
let et: *node = p.lhs;
|
||||
if (tuplit) { et = p; };
|
||||
if (isfloattype(c, et)) {
|
||||
if (fpidx >= 8) {
|
||||
let msg: str = "tuple arg float element overflows SSE arg regs (X0..X7); stitch out of scope, see #163\n";
|
||||
@@ -26308,7 +26367,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
fpidx += 1;
|
||||
popped += 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
let wide: bool = false;
|
||||
if (tuplit) { wide = nodeisstr(c, et) || nodeisslice(c, et); }
|
||||
else { wide = isstrtype(c, et) || isslicetype(c, et); };
|
||||
let eb: i32 = tupebytes(wide);
|
||||
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";
|
||||
@@ -30363,6 +30424,37 @@ fn rettupleof(c: *cgen, rhs: *node) *node = {
|
||||
return rtyp;
|
||||
};
|
||||
|
||||
// nodetuplearg — the tuple node of a call ARG whose cgexpr fills the
|
||||
// return-ABI cursor (#163/#32, C-t2): an N_CALL or `?`/`!` unwrap (the
|
||||
// declared return / success variant via inferletcalltype), an N_IDENT
|
||||
// local (declared tnode, alias-peeled), or an N_TUPLE literal — returned
|
||||
// AS-IS, kind-discriminated at the walks (its elements are VALUE exprs,
|
||||
// classified the way cgtuplelittocursor classifies them, not type
|
||||
// nodes). Mirror of cstage node_tuplearg; the cgcall push site
|
||||
// loud-stops any other tuple-typed source shape (rule 7). rettupleof
|
||||
// stays N_CALL-scoped for the destructure/reassign receive sites.
|
||||
fn nodetuplearg(c: *cgen, a: *node) *node = {
|
||||
if (a == nil) { return nil; };
|
||||
if (a.kind == nkind.N_TUPLE) { return a; };
|
||||
if (a.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, a.str);
|
||||
if (lc == nil) { return nil; };
|
||||
let tn: *node = lc.tnode;
|
||||
for (tn != nil && tn.kind == nkind.N_TNAME) {
|
||||
tn = aliaslookup(c, tn.str);
|
||||
};
|
||||
if (tn != nil) {
|
||||
if (tn.kind == nkind.N_TTUPLE) { return tn; };
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
let t: *node = inferletcalltype(c, a);
|
||||
if (t != nil) {
|
||||
if (t.kind == nkind.N_TTUPLE) { return t; };
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// 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}
|
||||
// header (ref/hare/rt/ensure.ha:4-8) at off/+8/+16 from consecutive
|
||||
|
||||
Reference in New Issue
Block a user