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:
@@ -238,16 +238,22 @@ node_isslice(Node *n)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* node_tuplearg — the underlying TY_TUPLE Type of a tuple-typed argument
|
/* node_tuplearg — the underlying TY_TUPLE Type of a tuple-typed argument
|
||||||
* VALUE, else NULL. #163: scoped to an N_CALL producer — the only form
|
* VALUE, else NULL. #163/#32 (C-t2): admits every producer whose cgexpr
|
||||||
* that leaves a tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1, per
|
* leaves the tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1, per
|
||||||
* #164). A tuple ident / literal as a first-class value is a separate
|
* #164) — a CALL (return ABI), an IDENT (cg_tuple_slot_to_cursor, #241),
|
||||||
* unimplemented gap (`let t = (1,2)` does not materialise a slot today),
|
* a LITERAL (cg_tuple_lit_to_cursor, #241), a `?`/`!` unwrap
|
||||||
* so the SEND restricts to the call form and loud-stops the rest rather
|
* (cg_tagged_tuple_payload_shift, #241). Pre-C-t2 this was N_CALL-scoped
|
||||||
* than push stale registers (rule 7, never a silent drop). */
|
* and the comment claimed the rest "loud-stop" — they did NOT: a tuple
|
||||||
|
* ident arg fell to the scalar single-PUSHQ default, skewing every later
|
||||||
|
* arg register (callee read garbage word 2). The cgcall push site now
|
||||||
|
* loud-stops any OTHER tuple-typed source shape (rule 7). */
|
||||||
static Type *
|
static Type *
|
||||||
node_tuplearg(Node *n)
|
node_tuplearg(Node *n)
|
||||||
{
|
{
|
||||||
if (n == NULL || n->kind != N_CALL) return NULL;
|
if (n == NULL) return NULL;
|
||||||
|
if (n->kind != N_CALL && n->kind != N_IDENT && n->kind != N_TUPLE
|
||||||
|
&& n->kind != N_TRYUNW && n->kind != N_TRYPROP)
|
||||||
|
return NULL;
|
||||||
Type *t = n->type;
|
Type *t = n->type;
|
||||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||||
return (u && u->kind == TY_TUPLE) ? u : NULL;
|
return (u && u->kind == TY_TUPLE) ? u : NULL;
|
||||||
@@ -8087,6 +8093,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
cgexpr(c, args[i], locals);
|
cgexpr(c, args[i], locals);
|
||||||
Type *tuparg_push = node_tuplearg(args[i]);
|
Type *tuparg_push = node_tuplearg(args[i]);
|
||||||
|
/* #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. */
|
||||||
|
if (tuparg_push == NULL) {
|
||||||
|
Type *targ = args[i]->type;
|
||||||
|
if (targ && targ->kind == TY_NAMED)
|
||||||
|
targ = targ->under;
|
||||||
|
if (targ && targ->kind == TY_TUPLE)
|
||||||
|
fatal("#32: tuple arg from unsupported "
|
||||||
|
"source shape %d (call/ident/"
|
||||||
|
"literal/unwrap only; rule 7)",
|
||||||
|
args[i]->kind);
|
||||||
|
}
|
||||||
if (node_isfloat(args[i])) {
|
if (node_isfloat(args[i])) {
|
||||||
/* f32 spills 4B (MOVSS), f64 8B (MOVSD): the SysV
|
/* f32 spills 4B (MOVSS), f64 8B (MOVSD): the SysV
|
||||||
* float class drives the width per ref/qbe
|
* float class drives the width per ref/qbe
|
||||||
@@ -8146,11 +8167,36 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
int gpcur = 0, ssecur = 0, eoff = 0, ef32;
|
int gpcur = 0, ssecur = 0, eoff = 0, ef32;
|
||||||
int gptot = 0, sstot = 0, tsz = 0;
|
int gptot = 0, sstot = 0, tsz = 0;
|
||||||
for (Tparam *p = tuparg_push->params; p; p = p->next) {
|
for (Tparam *p = tuparg_push->params; p; p = p->next) {
|
||||||
Type *pu = (p->type
|
/* C-t2 (ken demand 1, rule 7): a
|
||||||
&& p->type->kind == TY_NAMED)
|
* COMPOSITE element (nested tuple /
|
||||||
? p->type->under : p->type;
|
* struct / array / tagged) occupies
|
||||||
int wide = pu && (pu->kind == TY_SLICE
|
* more than the one GP word this walk
|
||||||
|| pu->kind == TY_STR);
|
* counts — the checker accepts the
|
||||||
|
* shape but the cursor transport
|
||||||
|
* cannot carry it; pre-guard it ran
|
||||||
|
* WRONG (inner words skewed). Loud
|
||||||
|
* until a consumer motivates wiring. */
|
||||||
|
Type *cu = type_chase_named(p->type);
|
||||||
|
if (cu && (cu->kind == TY_TUPLE
|
||||||
|
|| cu->kind == TY_STRUCT
|
||||||
|
|| cu->kind == TY_ARRAY
|
||||||
|
|| cu->kind == TY_TAGGED))
|
||||||
|
fatal("#32: tuple arg element "
|
||||||
|
"kind unsupported (nested "
|
||||||
|
"tuple/struct/array/tagged; "
|
||||||
|
"rule 7)");
|
||||||
|
/* C-t2: type_isstr/type_isslice, not a
|
||||||
|
* raw kind test — a LITERAL tuple's
|
||||||
|
* element types are the stamped expr
|
||||||
|
* types, so a strlit element is
|
||||||
|
* TY_UNTYPED_STR (size 0); the raw test
|
||||||
|
* under-classified it as 1 GP word
|
||||||
|
* against lit-to-cursor's node_isstr
|
||||||
|
* 3-word push. The wide stride reads
|
||||||
|
* ty_str->size (the header SSoT) for
|
||||||
|
* the same reason. */
|
||||||
|
int wide = type_isstr(p->type)
|
||||||
|
|| type_isslice(p->type);
|
||||||
if (fld_isfloat(p->type, &ef32))
|
if (fld_isfloat(p->type, &ef32))
|
||||||
sstot++;
|
sstot++;
|
||||||
else
|
else
|
||||||
@@ -8159,7 +8205,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* size); matches the wwstage slotsize() walk so
|
* size); matches the wwstage slotsize() walk so
|
||||||
* the @tupargscr width + reverse-push count agree
|
* the @tupargscr width + reverse-push count agree
|
||||||
* byte-for-byte. */
|
* byte-for-byte. */
|
||||||
tsz += wide ? (int)pu->size : 8;
|
tsz += wide ? (int)ty_str->size : 8;
|
||||||
}
|
}
|
||||||
/* The producing call already satisfied #164's
|
/* The producing call already satisfied #164's
|
||||||
* return caps; guard anyway (tuple_store indexes
|
* return caps; guard anyway (tuple_store indexes
|
||||||
@@ -8177,11 +8223,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
"#163)", cg_tupargscr_sz, tsz);
|
"#163)", cg_tupargscr_sz, tsz);
|
||||||
}
|
}
|
||||||
for (Tparam *p = tuparg_push->params; p; p = p->next) {
|
for (Tparam *p = tuparg_push->params; p; p = p->next) {
|
||||||
Type *pu = (p->type
|
int wide = type_isstr(p->type)
|
||||||
&& p->type->kind == TY_NAMED)
|
|| type_isslice(p->type);
|
||||||
? p->type->under : p->type;
|
|
||||||
int wide = pu && (pu->kind == TY_SLICE
|
|
||||||
|| pu->kind == TY_STR);
|
|
||||||
int isflt = fld_isfloat(p->type, &ef32);
|
int isflt = fld_isfloat(p->type, &ef32);
|
||||||
tuple_store(c, p->type, wide, gpcur, ssecur,
|
tuple_store(c, p->type, wide, gpcur, ssecur,
|
||||||
cg_tupargscr + eoff);
|
cg_tupargscr + eoff);
|
||||||
@@ -8189,7 +8232,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ssecur++;
|
ssecur++;
|
||||||
else
|
else
|
||||||
gpcur += tuple_ebytes(wide);
|
gpcur += tuple_ebytes(wide);
|
||||||
eoff += wide ? (int)pu->size : 8;
|
eoff += wide ? (int)ty_str->size : 8;
|
||||||
}
|
}
|
||||||
for (int w = tsz - 8; w >= 0; w -= 8) {
|
for (int w = tsz - 8; w >= 0; w -= 8) {
|
||||||
ins2(c, A_MOVQ,
|
ins2(c, A_MOVQ,
|
||||||
@@ -8400,11 +8443,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* scope (twin of #164's cap). */
|
* scope (twin of #164's cap). */
|
||||||
int ef32;
|
int ef32;
|
||||||
for (Tparam *p = tu->params; p; p = p->next) {
|
for (Tparam *p = tu->params; p; p = p->next) {
|
||||||
Type *pu = (p->type
|
/* C-t2: untyped-str-aware wide test —
|
||||||
&& p->type->kind == TY_NAMED)
|
* twin of the @tupargscr restage walk. */
|
||||||
? p->type->under : p->type;
|
int wide = type_isstr(p->type)
|
||||||
int wide = pu && (pu->kind == TY_SLICE
|
|| type_isslice(p->type);
|
||||||
|| pu->kind == TY_STR);
|
|
||||||
if (fld_isfloat(p->type, &ef32)) {
|
if (fld_isfloat(p->type, &ef32)) {
|
||||||
if (fi >= 8)
|
if (fi >= 8)
|
||||||
fatal("tuple arg float "
|
fatal("tuple arg float "
|
||||||
|
|||||||
@@ -16622,29 +16622,60 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
return rest + 1;
|
return rest + 1;
|
||||||
};
|
};
|
||||||
cgexpr(c, arg);
|
cgexpr(c, arg);
|
||||||
// #163: tuple ARG (param twin of #164's return). cgexpr left the
|
// #163/#32 (C-t2): tuple ARG (param twin of #164's return). cgexpr
|
||||||
// tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1); restage it
|
// left the tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1) for
|
||||||
// into @tupargscr by SysV class (tupstore, the #164 helper) and push
|
// every nodetuplearg producer — call (return ABI), ident
|
||||||
// the slot words high->low so the pop drains slot+0 first into the
|
// (cgtupleslottocursor), literal (cgtuplelittocursor), `?`/`!`
|
||||||
// SysV ARG cursor. The frame slot decouples the return-class regs
|
// unwrap (payload shift); restage it into @tupargscr by SysV class
|
||||||
// from the overlapping arg-class regs. rettupleof scopes to an
|
// (tupstore, the #164 helper) and push the slot words high->low so
|
||||||
// N_CALL producer (tuple idents/literals as values are a separate
|
// the pop drains slot+0 first into the SysV ARG cursor. The frame
|
||||||
// unimplemented gap; the SEND never pushes stale regs, rule 7).
|
// slot decouples the return-class regs from the overlapping
|
||||||
let tuparg: *node = rettupleof(c, arg);
|
// 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) {
|
if (tuparg != nil) {
|
||||||
|
let tuplit: bool = tuparg.kind == nkind.N_TUPLE;
|
||||||
let gptot: i32 = 0;
|
let gptot: i32 = 0;
|
||||||
let sstot: i32 = 0;
|
let sstot: i32 = 0;
|
||||||
let tsz: i32 = 0;
|
let tsz: i32 = 0;
|
||||||
let p: *node = tuparg.list;
|
let p: *node = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
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; }
|
if (isfloattype(c, et)) { sstot += 1; }
|
||||||
else { gptot += tupebytes(wide); };
|
else { gptot += tupebytes(wide); };
|
||||||
tsz += slotsize(c, et);
|
if (wide) { tsz += (tyslicesize(): i32); }
|
||||||
|
else { tsz += 8; };
|
||||||
p = p.next;
|
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]).
|
// guard anyway (tupstore indexes [AX,DX,CX,R8] / [X0,X1]).
|
||||||
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP) {
|
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP) {
|
||||||
let msg: str = "tuple arg exceeds return-cursor ABI capacity; see #163/#164\n";
|
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;
|
p = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
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);
|
tupstore(c, gpcur, ssecur, scr + eoff, wide, et);
|
||||||
if (isfloattype(c, et)) { ssecur += 1; }
|
if (isfloattype(c, et)) { ssecur += 1; }
|
||||||
else { gpcur += tupebytes(wide); };
|
else { gpcur += tupebytes(wide); };
|
||||||
eoff += slotsize(c, et);
|
if (wide) { eoff += (tyslicesize(): i32); }
|
||||||
|
else { eoff += 8; };
|
||||||
p = p.next;
|
p = p.next;
|
||||||
};
|
};
|
||||||
let w: i32 = tsz - 8;
|
let w: i32 = tsz - 8;
|
||||||
@@ -16675,6 +16710,24 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
};
|
};
|
||||||
return rest + tsz / 8;
|
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)) {
|
if (nodeisslice(c, arg)) {
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
@@ -26280,17 +26333,23 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
stackslots += 1;
|
stackslots += 1;
|
||||||
};
|
};
|
||||||
popped += 1;
|
popped += 1;
|
||||||
} else { let tuparg: *node = rettupleof(c, a);
|
} else { let tuparg: *node = nodetuplearg(c, a);
|
||||||
if (tuparg != nil) {
|
if (tuparg != nil) {
|
||||||
// #163: drain the tuple's staged words (slot+0 pushed
|
// #163/#32: drain the tuple's staged words (slot+0
|
||||||
// first) into the SysV arg cursor by SysV class — a
|
// pushed first) into the SysV arg cursor by SysV class
|
||||||
// float MOVSD/MOVSS off (SP) into the next XMM, else
|
// — a float MOVSD/MOVSS off (SP) into the next XMM,
|
||||||
// POPQ into the next INTEGER arg reg; a slice/str its
|
// else POPQ into the next INTEGER arg reg; a slice/str
|
||||||
// 3 words. Reg overflow loud-stops (rule 7); the
|
// its 3 words. Reg overflow loud-stops (rule 7); the
|
||||||
// partial-spill stitch is out of scope (twin of #164).
|
// 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;
|
let p: *node = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
let et: *node = p.lhs;
|
||||||
|
if (tuplit) { et = p; };
|
||||||
if (isfloattype(c, et)) {
|
if (isfloattype(c, et)) {
|
||||||
if (fpidx >= 8) {
|
if (fpidx >= 8) {
|
||||||
let msg: str = "tuple arg float element overflows SSE arg regs (X0..X7); stitch out of scope, see #163\n";
|
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;
|
fpidx += 1;
|
||||||
popped += 1;
|
popped += 1;
|
||||||
} else {
|
} 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);
|
let eb: i32 = tupebytes(wide);
|
||||||
if (intidx + eb > 6) {
|
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";
|
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;
|
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
|
// 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
|
||||||
|
|||||||
@@ -6056,17 +6056,23 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
stackslots += 1;
|
stackslots += 1;
|
||||||
};
|
};
|
||||||
popped += 1;
|
popped += 1;
|
||||||
} else { let tuparg: *node = rettupleof(c, a);
|
} else { let tuparg: *node = nodetuplearg(c, a);
|
||||||
if (tuparg != nil) {
|
if (tuparg != nil) {
|
||||||
// #163: drain the tuple's staged words (slot+0 pushed
|
// #163/#32: drain the tuple's staged words (slot+0
|
||||||
// first) into the SysV arg cursor by SysV class — a
|
// pushed first) into the SysV arg cursor by SysV class
|
||||||
// float MOVSD/MOVSS off (SP) into the next XMM, else
|
// — a float MOVSD/MOVSS off (SP) into the next XMM,
|
||||||
// POPQ into the next INTEGER arg reg; a slice/str its
|
// else POPQ into the next INTEGER arg reg; a slice/str
|
||||||
// 3 words. Reg overflow loud-stops (rule 7); the
|
// its 3 words. Reg overflow loud-stops (rule 7); the
|
||||||
// partial-spill stitch is out of scope (twin of #164).
|
// 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;
|
let p: *node = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
let et: *node = p.lhs;
|
||||||
|
if (tuplit) { et = p; };
|
||||||
if (isfloattype(c, et)) {
|
if (isfloattype(c, et)) {
|
||||||
if (fpidx >= 8) {
|
if (fpidx >= 8) {
|
||||||
let msg: str = "tuple arg float element overflows SSE arg regs (X0..X7); stitch out of scope, see #163\n";
|
let msg: str = "tuple arg float element overflows SSE arg regs (X0..X7); stitch out of scope, see #163\n";
|
||||||
@@ -6084,7 +6090,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
fpidx += 1;
|
fpidx += 1;
|
||||||
popped += 1;
|
popped += 1;
|
||||||
} else {
|
} 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);
|
let eb: i32 = tupebytes(wide);
|
||||||
if (intidx + eb > 6) {
|
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";
|
let msg: str = "tuple arg element overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #163\n";
|
||||||
|
|||||||
@@ -169,6 +169,37 @@ fn rettupleof(c: *cgen, rhs: *node) *node = {
|
|||||||
return rtyp;
|
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
|
// 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
|
||||||
|
|||||||
@@ -706,29 +706,60 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
return rest + 1;
|
return rest + 1;
|
||||||
};
|
};
|
||||||
cgexpr(c, arg);
|
cgexpr(c, arg);
|
||||||
// #163: tuple ARG (param twin of #164's return). cgexpr left the
|
// #163/#32 (C-t2): tuple ARG (param twin of #164's return). cgexpr
|
||||||
// tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1); restage it
|
// left the tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1) for
|
||||||
// into @tupargscr by SysV class (tupstore, the #164 helper) and push
|
// every nodetuplearg producer — call (return ABI), ident
|
||||||
// the slot words high->low so the pop drains slot+0 first into the
|
// (cgtupleslottocursor), literal (cgtuplelittocursor), `?`/`!`
|
||||||
// SysV ARG cursor. The frame slot decouples the return-class regs
|
// unwrap (payload shift); restage it into @tupargscr by SysV class
|
||||||
// from the overlapping arg-class regs. rettupleof scopes to an
|
// (tupstore, the #164 helper) and push the slot words high->low so
|
||||||
// N_CALL producer (tuple idents/literals as values are a separate
|
// the pop drains slot+0 first into the SysV ARG cursor. The frame
|
||||||
// unimplemented gap; the SEND never pushes stale regs, rule 7).
|
// slot decouples the return-class regs from the overlapping
|
||||||
let tuparg: *node = rettupleof(c, arg);
|
// 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) {
|
if (tuparg != nil) {
|
||||||
|
let tuplit: bool = tuparg.kind == nkind.N_TUPLE;
|
||||||
let gptot: i32 = 0;
|
let gptot: i32 = 0;
|
||||||
let sstot: i32 = 0;
|
let sstot: i32 = 0;
|
||||||
let tsz: i32 = 0;
|
let tsz: i32 = 0;
|
||||||
let p: *node = tuparg.list;
|
let p: *node = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
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; }
|
if (isfloattype(c, et)) { sstot += 1; }
|
||||||
else { gptot += tupebytes(wide); };
|
else { gptot += tupebytes(wide); };
|
||||||
tsz += slotsize(c, et);
|
if (wide) { tsz += (tyslicesize(): i32); }
|
||||||
|
else { tsz += 8; };
|
||||||
p = p.next;
|
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]).
|
// guard anyway (tupstore indexes [AX,DX,CX,R8] / [X0,X1]).
|
||||||
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP) {
|
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP) {
|
||||||
let msg: str = "tuple arg exceeds return-cursor ABI capacity; see #163/#164\n";
|
let msg: str = "tuple arg exceeds return-cursor ABI capacity; see #163/#164\n";
|
||||||
@@ -742,11 +773,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
p = tuparg.list;
|
p = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
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);
|
tupstore(c, gpcur, ssecur, scr + eoff, wide, et);
|
||||||
if (isfloattype(c, et)) { ssecur += 1; }
|
if (isfloattype(c, et)) { ssecur += 1; }
|
||||||
else { gpcur += tupebytes(wide); };
|
else { gpcur += tupebytes(wide); };
|
||||||
eoff += slotsize(c, et);
|
if (wide) { eoff += (tyslicesize(): i32); }
|
||||||
|
else { eoff += 8; };
|
||||||
p = p.next;
|
p = p.next;
|
||||||
};
|
};
|
||||||
let w: i32 = tsz - 8;
|
let w: i32 = tsz - 8;
|
||||||
@@ -759,6 +794,24 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
};
|
};
|
||||||
return rest + tsz / 8;
|
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)) {
|
if (nodeisslice(c, arg)) {
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
|
|||||||
@@ -16622,29 +16622,60 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
return rest + 1;
|
return rest + 1;
|
||||||
};
|
};
|
||||||
cgexpr(c, arg);
|
cgexpr(c, arg);
|
||||||
// #163: tuple ARG (param twin of #164's return). cgexpr left the
|
// #163/#32 (C-t2): tuple ARG (param twin of #164's return). cgexpr
|
||||||
// tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1); restage it
|
// left the tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1) for
|
||||||
// into @tupargscr by SysV class (tupstore, the #164 helper) and push
|
// every nodetuplearg producer — call (return ABI), ident
|
||||||
// the slot words high->low so the pop drains slot+0 first into the
|
// (cgtupleslottocursor), literal (cgtuplelittocursor), `?`/`!`
|
||||||
// SysV ARG cursor. The frame slot decouples the return-class regs
|
// unwrap (payload shift); restage it into @tupargscr by SysV class
|
||||||
// from the overlapping arg-class regs. rettupleof scopes to an
|
// (tupstore, the #164 helper) and push the slot words high->low so
|
||||||
// N_CALL producer (tuple idents/literals as values are a separate
|
// the pop drains slot+0 first into the SysV ARG cursor. The frame
|
||||||
// unimplemented gap; the SEND never pushes stale regs, rule 7).
|
// slot decouples the return-class regs from the overlapping
|
||||||
let tuparg: *node = rettupleof(c, arg);
|
// 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) {
|
if (tuparg != nil) {
|
||||||
|
let tuplit: bool = tuparg.kind == nkind.N_TUPLE;
|
||||||
let gptot: i32 = 0;
|
let gptot: i32 = 0;
|
||||||
let sstot: i32 = 0;
|
let sstot: i32 = 0;
|
||||||
let tsz: i32 = 0;
|
let tsz: i32 = 0;
|
||||||
let p: *node = tuparg.list;
|
let p: *node = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
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; }
|
if (isfloattype(c, et)) { sstot += 1; }
|
||||||
else { gptot += tupebytes(wide); };
|
else { gptot += tupebytes(wide); };
|
||||||
tsz += slotsize(c, et);
|
if (wide) { tsz += (tyslicesize(): i32); }
|
||||||
|
else { tsz += 8; };
|
||||||
p = p.next;
|
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]).
|
// guard anyway (tupstore indexes [AX,DX,CX,R8] / [X0,X1]).
|
||||||
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP) {
|
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP) {
|
||||||
let msg: str = "tuple arg exceeds return-cursor ABI capacity; see #163/#164\n";
|
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;
|
p = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
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);
|
tupstore(c, gpcur, ssecur, scr + eoff, wide, et);
|
||||||
if (isfloattype(c, et)) { ssecur += 1; }
|
if (isfloattype(c, et)) { ssecur += 1; }
|
||||||
else { gpcur += tupebytes(wide); };
|
else { gpcur += tupebytes(wide); };
|
||||||
eoff += slotsize(c, et);
|
if (wide) { eoff += (tyslicesize(): i32); }
|
||||||
|
else { eoff += 8; };
|
||||||
p = p.next;
|
p = p.next;
|
||||||
};
|
};
|
||||||
let w: i32 = tsz - 8;
|
let w: i32 = tsz - 8;
|
||||||
@@ -16675,6 +16710,24 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
|||||||
};
|
};
|
||||||
return rest + tsz / 8;
|
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)) {
|
if (nodeisslice(c, arg)) {
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
@@ -26280,17 +26333,23 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
stackslots += 1;
|
stackslots += 1;
|
||||||
};
|
};
|
||||||
popped += 1;
|
popped += 1;
|
||||||
} else { let tuparg: *node = rettupleof(c, a);
|
} else { let tuparg: *node = nodetuplearg(c, a);
|
||||||
if (tuparg != nil) {
|
if (tuparg != nil) {
|
||||||
// #163: drain the tuple's staged words (slot+0 pushed
|
// #163/#32: drain the tuple's staged words (slot+0
|
||||||
// first) into the SysV arg cursor by SysV class — a
|
// pushed first) into the SysV arg cursor by SysV class
|
||||||
// float MOVSD/MOVSS off (SP) into the next XMM, else
|
// — a float MOVSD/MOVSS off (SP) into the next XMM,
|
||||||
// POPQ into the next INTEGER arg reg; a slice/str its
|
// else POPQ into the next INTEGER arg reg; a slice/str
|
||||||
// 3 words. Reg overflow loud-stops (rule 7); the
|
// its 3 words. Reg overflow loud-stops (rule 7); the
|
||||||
// partial-spill stitch is out of scope (twin of #164).
|
// 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;
|
let p: *node = tuparg.list;
|
||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
let et: *node = p.lhs;
|
let et: *node = p.lhs;
|
||||||
|
if (tuplit) { et = p; };
|
||||||
if (isfloattype(c, et)) {
|
if (isfloattype(c, et)) {
|
||||||
if (fpidx >= 8) {
|
if (fpidx >= 8) {
|
||||||
let msg: str = "tuple arg float element overflows SSE arg regs (X0..X7); stitch out of scope, see #163\n";
|
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;
|
fpidx += 1;
|
||||||
popped += 1;
|
popped += 1;
|
||||||
} else {
|
} 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);
|
let eb: i32 = tupebytes(wide);
|
||||||
if (intidx + eb > 6) {
|
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";
|
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;
|
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
|
// 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
|
||||||
|
|||||||
@@ -74,7 +74,25 @@ slurp_eq(const char *a, const char *b)
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct row { const char *label; const char *src; int want; };
|
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
|
||||||
|
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
|
||||||
|
|
||||||
|
struct row { const char *label; const char *src; int want;
|
||||||
|
int kind; const char *experr; };
|
||||||
|
|
||||||
|
/* errlog_has — a BUILDERR row must fail WITH its diagnostic; any other
|
||||||
|
* failure (parse error, crash) is a vacuous reject (940 precedent). */
|
||||||
|
static int
|
||||||
|
errlog_has(const char *path, const char *needle)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return 0;
|
||||||
|
char buf[8192];
|
||||||
|
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[got] = '\0';
|
||||||
|
return strstr(buf, needle) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct row rows[] = {
|
static const struct row rows[] = {
|
||||||
{ "letcall_packed",
|
{ "letcall_packed",
|
||||||
@@ -87,7 +105,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.0 != 3) { return 1; };\n"
|
" if (t.0 != 3) { return 1; };\n"
|
||||||
" if (t.1 != 4) { return 2; };\n"
|
" if (t.1 != 4) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "letcall_float_packed",
|
{ "letcall_float_packed",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn f() (u32, f64) = {\n"
|
"fn f() (u32, f64) = {\n"
|
||||||
@@ -100,7 +118,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.0 != 9) { return 1; };\n"
|
" if (t.0 != 9) { return 1; };\n"
|
||||||
" if (t.1 != 2.5) { return 2; };\n"
|
" if (t.1 != 2.5) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "letcall_signed_packed",
|
{ "letcall_signed_packed",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn f() (i32, i32) = {\n"
|
"fn f() (i32, i32) = {\n"
|
||||||
@@ -114,7 +132,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.1 != -6) { return 2; };\n"
|
" if (t.1 != -6) { return 2; };\n"
|
||||||
" if (t.0 + t.1 != -11) { return 3; };\n"
|
" if (t.0 + t.1 != -11) { return 3; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "letcall_mixed_packed",
|
{ "letcall_mixed_packed",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn f() (u32, str) = {\n"
|
"fn f() (u32, str) = {\n"
|
||||||
@@ -127,7 +145,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.0 != 7) { return 1; };\n"
|
" if (t.0 != 7) { return 1; };\n"
|
||||||
" if (len(t.1) != 5) { return 2; };\n"
|
" if (len(t.1) != 5) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "letcall_16_neutral",
|
{ "letcall_16_neutral",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn f() (i64, i64) = {\n"
|
"fn f() (i64, i64) = {\n"
|
||||||
@@ -138,7 +156,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.0 != 41) { return 1; };\n"
|
" if (t.0 != 41) { return 1; };\n"
|
||||||
" if (t.1 != 17) { return 2; };\n"
|
" if (t.1 != 17) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "letcall_32_neutral",
|
{ "letcall_32_neutral",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn f() (i64, str) = {\n"
|
"fn f() (i64, str) = {\n"
|
||||||
@@ -151,7 +169,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.0 != 12) { return 1; };\n"
|
" if (t.0 != 12) { return 1; };\n"
|
||||||
" if (len(t.1) != 4) { return 2; };\n"
|
" if (len(t.1) != 4) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "mlet_packed_neutral",
|
{ "mlet_packed_neutral",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn f() (u32, u32) = {\n"
|
"fn f() (u32, u32) = {\n"
|
||||||
@@ -162,7 +180,7 @@ static const struct row rows[] = {
|
|||||||
" if (a != 3) { return 1; };\n"
|
" if (a != 3) { return 1; };\n"
|
||||||
" if (b != 4) { return 2; };\n"
|
" if (b != 4) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
|
||||||
/* ---- C-t1 (#33): the let RECEIVE re-keyed onto the declared
|
/* ---- C-t1 (#33): the let RECEIVE re-keyed onto the declared
|
||||||
* type's register classify. Pre-C-t1 wwstage keyed on producer
|
* type's register classify. Pre-C-t1 wwstage keyed on producer
|
||||||
@@ -180,7 +198,7 @@ static const struct row rows[] = {
|
|||||||
"export fn main() i32 = {\n"
|
"export fn main() i32 = {\n"
|
||||||
" if (second() != 4) { return 1; };\n"
|
" if (second() != 4) { return 1; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "t1_lit_16",
|
{ "t1_lit_16",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"export fn main() i32 = {\n"
|
"export fn main() i32 = {\n"
|
||||||
@@ -188,7 +206,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.0 != 3) { return 1; };\n"
|
" if (t.0 != 3) { return 1; };\n"
|
||||||
" if (t.1 != 4) { return 2; };\n"
|
" if (t.1 != 4) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "t1_lit_mixed",
|
{ "t1_lit_mixed",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"export fn main() i32 = {\n"
|
"export fn main() i32 = {\n"
|
||||||
@@ -196,7 +214,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.0 != 12) { return 1; };\n"
|
" if (t.0 != 12) { return 1; };\n"
|
||||||
" if (len(t.1) != 4) { return 2; };\n"
|
" if (len(t.1) != 4) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "t1_lit_3scalar",
|
{ "t1_lit_3scalar",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn second() i64 = {\n"
|
"fn second() i64 = {\n"
|
||||||
@@ -208,7 +226,7 @@ static const struct row rows[] = {
|
|||||||
"export fn main() i32 = {\n"
|
"export fn main() i32 = {\n"
|
||||||
" if (second() != 5) { return 1; };\n"
|
" if (second() != 5) { return 1; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "t1_call_3scalar",
|
{ "t1_call_3scalar",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn f() (i64, i64, i64) = {\n"
|
"fn f() (i64, i64, i64) = {\n"
|
||||||
@@ -220,7 +238,7 @@ static const struct row rows[] = {
|
|||||||
" if (t.1 != 8) { return 2; };\n"
|
" if (t.1 != 8) { return 2; };\n"
|
||||||
" if (t.2 != 9) { return 3; };\n"
|
" if (t.2 != 9) { return 3; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
{ "t1_call_noannot_neutral",
|
{ "t1_call_noannot_neutral",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"fn f() (i64, i64) = {\n"
|
"fn f() (i64, i64) = {\n"
|
||||||
@@ -231,7 +249,193 @@ static const struct row rows[] = {
|
|||||||
" if (t.0 != 3) { return 1; };\n"
|
" if (t.0 != 3) { return 1; };\n"
|
||||||
" if (t.1 != 4) { return 2; };\n"
|
" if (t.1 != 4) { return 2; };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0 },
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
|
||||||
|
/* ---- C-t2 (#32): the by-value tuple ARG send. node_tuplearg
|
||||||
|
* was N_CALL-scoped (and its comment FALSELY claimed the rest
|
||||||
|
* loud-stop): a tuple ident/literal/unwrap arg fell to the
|
||||||
|
* scalar single-PUSHQ default, skewing every later arg register
|
||||||
|
* — the callee read garbage word 2 (packed shapes SIGSEGV'd
|
||||||
|
* pre-C-t0). Now every cursor-filling producer rides the #163
|
||||||
|
* @tupargscr restage; any other tuple-typed source loud-stops. ---- */
|
||||||
|
{ "t2_param_packed",
|
||||||
|
"package main;\n"
|
||||||
|
"fn pick(t: (u32, u32), k: i32) u32 = {\n"
|
||||||
|
" if (k == 0) { return t.0; };\n"
|
||||||
|
" return t.1;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let t: (u32, u32) = (41, 17);\n"
|
||||||
|
" if (pick(t, 0) != 41) { return 1; };\n"
|
||||||
|
" if (pick(t, 1) != 17) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
{ "t2_param_16",
|
||||||
|
"package main;\n"
|
||||||
|
"fn pick(t: (i64, i64), k: i32) i64 = {\n"
|
||||||
|
" if (k == 0) { return t.0; };\n"
|
||||||
|
" return t.1;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let t: (i64, i64) = (41, 17);\n"
|
||||||
|
" if (pick(t, 0) != 41) { return 1; };\n"
|
||||||
|
" if (pick(t, 1) != 17) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
{ "t2_mixed_order",
|
||||||
|
"package main;\n"
|
||||||
|
"fn g(a: i64, t: (u32, u32), b: i64) i64 = {\n"
|
||||||
|
" return a*1000 + (t.0: i64)*100 + (t.1: i64)*10 + b;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn h(t: (i64, i64), s: str, k: i64) i64 = {\n"
|
||||||
|
" return t.0 + t.1 + (len(s): i64) + k;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let t: (u32, u32) = (3, 4);\n"
|
||||||
|
" if (g(1, t, 2) != 1342) { return 1; };\n"
|
||||||
|
" let u: (i64, i64) = (10, 20);\n"
|
||||||
|
" if (h(u, \"abc\", 7) != 40) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
{ "t2_lit_arg",
|
||||||
|
"package main;\n"
|
||||||
|
"fn pick(t: (i64, i64), k: i32) i64 = {\n"
|
||||||
|
" if (k == 0) { return t.0; };\n"
|
||||||
|
" return t.1;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (pick((41, 17), 0) != 41) { return 1; };\n"
|
||||||
|
" if (pick((41, 17), 1) != 17) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
{ "t2_float_param",
|
||||||
|
"package main;\n"
|
||||||
|
"fn fsum(t: (f64, i64)) f64 = {\n"
|
||||||
|
" return t.0 + (t.1: f64);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let t: (f64, i64) = (2.5, 4);\n"
|
||||||
|
" if (fsum(t) != 6.5) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
{ "t2_unwrap_arg",
|
||||||
|
"package main;\n"
|
||||||
|
"fn mk() ((i64, i64) | nomem) = {\n"
|
||||||
|
" let a: i64 = 5;\n"
|
||||||
|
" let b: i64 = 6;\n"
|
||||||
|
" return (a, b);\n"
|
||||||
|
"};\n"
|
||||||
|
"fn sum(t: (i64, i64)) i64 = {\n"
|
||||||
|
" return t.0 + t.1;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (sum(mk()!) != 11) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
/* ken's >6-GP-pressure stress: 4 leading scalars + the 2-word
|
||||||
|
* tuple + a 7th GP word — the restage drain fills the INTEGER
|
||||||
|
* cursor past the tuple and the 7th word must still land in the
|
||||||
|
* stack arg class, not be eaten or shifted by the tuple's pops. */
|
||||||
|
{ "t2_gp_pressure",
|
||||||
|
"package main;\n"
|
||||||
|
"fn f(a: i64, b: i64, c: i64, d: i64, t: (i64, i64), e: i64) i64 = {\n"
|
||||||
|
" if (e != 60) { return -1; };\n"
|
||||||
|
" if (a != 1 || b != 2 || c != 3 || d != 4) { return -2; };\n"
|
||||||
|
" return t.0 * 100 + t.1;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let t: (i64, i64) = (7, 9);\n"
|
||||||
|
" if (f(1, 2, 3, 4, t, 60) != 709) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
/* a tuple arg mixed with a VARIADIC tail rides the same restage —
|
||||||
|
* positive pin of the variadic interaction (ken demand 2). */
|
||||||
|
{ "t2_variadic_after_tuple",
|
||||||
|
"package main;\n"
|
||||||
|
"fn g(t: (i64, i64), xs: i64...) i64 = {\n"
|
||||||
|
" let s: i64 = t.0 + t.1;\n"
|
||||||
|
" for (let x .. xs) { s += x; };\n"
|
||||||
|
" return s;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let t: (i64, i64) = (3, 4);\n"
|
||||||
|
" if (g(t, 1, 2) != 10) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
/* rule-7 (ken demand 1): a NESTED composite element (tuple-in-
|
||||||
|
* tuple here) occupies more than the one GP word the restage walk
|
||||||
|
* counts — pre-guard the checker accepted it and it ran WRONG
|
||||||
|
* (inner words skewed; wwstage SIGSEGV'd). Loud until a consumer
|
||||||
|
* motivates the wiring. */
|
||||||
|
{ "t2_reject_nested_elem_arg",
|
||||||
|
"package main;\n"
|
||||||
|
"fn f(t: ((i64, i64), i64)) i64 = {\n"
|
||||||
|
" return t.1;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let inner: (i64, i64) = (1, 2);\n"
|
||||||
|
" let t: ((i64, i64), i64) = (inner, 9);\n"
|
||||||
|
" if (f(t) != 9) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0,
|
||||||
|
K_BUILDERR, "tuple arg element kind unsupported" },
|
||||||
|
/* variadic-of-tuples ((i64,i64)...) stays LOUD via the
|
||||||
|
* tuple-in-slice read surface — bounded, not silent (demand 2). */
|
||||||
|
{ "t2_reject_variadic_of_tuples",
|
||||||
|
"package main;\n"
|
||||||
|
"fn first(ts: (i64, i64)...) i64 = {\n"
|
||||||
|
" if (len(ts) == 0) { return -1; };\n"
|
||||||
|
" return ts[0].0;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let t: (i64, i64) = (3, 4);\n"
|
||||||
|
" if (first(t) != 3) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0,
|
||||||
|
K_BUILDERR, "unsupported field-read shape" },
|
||||||
|
/* rule-7: a tuple arg from a source whose cgexpr does NOT fill
|
||||||
|
* the cursor (here a struct-field chain) dies loud — pre-C-t2
|
||||||
|
* it fell to the scalar single-PUSHQ default silently. */
|
||||||
|
{ "t2_reject_chain_arg",
|
||||||
|
"package main;\n"
|
||||||
|
"type holder = struct { t: (i64, i64) };\n"
|
||||||
|
"fn sum(t: (i64, i64)) i64 = {\n"
|
||||||
|
" return t.0 + t.1;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let h: holder = holder{ t = (1, 2) };\n"
|
||||||
|
" if (sum(h.t) != 3) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0,
|
||||||
|
K_BUILDERR, "tuple arg from unsupported source shape" },
|
||||||
|
/* rule-7: an over-cap tuple ident arg louds at the slot-to-cursor
|
||||||
|
* cap (the #10 sret follow-up), never a partial push. */
|
||||||
|
{ "t2_reject_overcap_ident_arg",
|
||||||
|
"package main;\n"
|
||||||
|
"fn f(t: (str, str)) i64 = {\n"
|
||||||
|
" return (len(t.0) + len(t.1)): i64;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn g() (str, str) = {\n"
|
||||||
|
" return (\"ab\", \"cde\");\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let t: (str, str) = g();\n"
|
||||||
|
" if (f(t) != 5) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0,
|
||||||
|
K_BUILDERR, "tuple ident exceeds register-return ABI capacity" },
|
||||||
|
/* fold-4 substrate pin (charset_range_item = [](u32,u32)): the
|
||||||
|
* tuple-in-slice consumer shape is LOUD today, both stages —
|
||||||
|
* wiring it is fold-4's prerequisite work, not a silent gap. */
|
||||||
|
{ "charset_slice_append_reject",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let s: [](u32, u32) = [];\n"
|
||||||
|
" append(s, (1, 2));\n"
|
||||||
|
" if (len(s) != 1) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0,
|
||||||
|
K_BUILDERR, "element kind unsupported" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
|
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
|
||||||
@@ -252,6 +456,16 @@ run_driver(const char *driver, const struct row *r, int i)
|
|||||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>%s",
|
snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>%s",
|
||||||
tmpdir, driver, src, errf);
|
tmpdir, driver, src, errf);
|
||||||
int brc = runwait(cmd);
|
int brc = runwait(cmd);
|
||||||
|
if (r->kind == K_BUILDERR) {
|
||||||
|
int ok = (brc != 0)
|
||||||
|
&& (r->experr == NULL || errlog_has(errf, r->experr));
|
||||||
|
if (!ok)
|
||||||
|
fprintf(stderr, "row[%s]: %s expected loud builderr "
|
||||||
|
"\"%s\" (brc=%d)\n", r->label, driver,
|
||||||
|
r->experr ? r->experr : "", brc);
|
||||||
|
unlink(src); unlink(errf); rmdir(tmpdir);
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
||||||
if (brc != 0) {
|
if (brc != 0) {
|
||||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||||
r->label, driver);
|
r->label, driver);
|
||||||
@@ -341,6 +555,8 @@ main(void)
|
|||||||
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
|
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (rows[i].kind == K_BUILDERR)
|
||||||
|
continue;
|
||||||
total++;
|
total++;
|
||||||
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
|
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user