wcc+w6c+w6c_ww: tagged tuple elements take their full slot — tuple_eslot accessor (#22a)
slot = roundup8(size(elem)) — 8B is a FLOOR, not a ceiling (user- ratified 2026-06-04; the #237 fieldslotsize precedent; (str,str)=48B predates this — tagged was the one truncated >8B kind). Pre-fix the checkers truncated a tagged element to one 8B slot and every cgen transport walk strode wide=(STR||SLICE)-else-8: cstage read the NEIGHBOR slot, wwstage read ZEROS — both-wrong-differently, so the byte-id gates were blind (prober-9 PG1, /tmp/p9). One stride accessor per stage — tuple_eslot (cgen.c) / tupeslot (cgenstmt.ww) — now feeds every tuple walk; the per-site predicates are deleted as absorbed. Sizer: check.c N_TTUPLE + check.ww tupleelemslot gain the TY_TAGGED arm (astsize already rides the type table since commit 0). Routes flipped to the accessor, both stages symmetric: cursor producers (lit/slot-to-cursor; tagged ident elements load their box from the slot — cgexpr's tagged ident load is word0-only), let-receive (tuple_store/tupstore generalized to eslot/8 words), N_RETURN send, by-value param receive, arg restage/drain (tagged stays loud per C-t2), destructure (MLET/MASSIGN, ident + sret + in-cap), t.N read + len(t.N) + global-g.N offset walks (t.N gains the tagged box load: AX=tag, DX/CX/R8=payload — the is/as spill cursor), sret classifier, DATA emit. wwstage cgtagvariantidx gains the #67 stamped-carrier arm (flatvariantidxt on .type_) — its AST-only key silently clamped 't.0 is size' to tag 0; fused here because the tuple-element read this commit wires is its only exercisable consumer. Exit invariant: zero silent tagged-tuple paths — in-cap shapes (<=4 GP eightbytes) are correct end-to-end; everything else is LOUD: over-cap sret return (#22b, task #28), call-arg (C-t2 #32), non-local literal element sources (#22b/#23), tuple-in-union payload (#242/#22b), global static-init, element write (pre-existing). Closure proof-grep at HEAD: 'tuple_ebytes|tupebytes' -> 0 hits; 'wide.*=.*(TY_SLICE|TY_STR)' tuple-walk survivors are all behind tagged loud-guards (cgen.c:2535/2568/12013 widen-store + over-cap send; cgenutil.ww:3527 twin). Latent cross-checks closed by the accessor: wwstage MLET-sret strode esz (4 for i32) vs cstage 8; wwstage param-receive strode slotsize (composite slotsize) vs cstage 8; both now the accessor's floor-8. Tagged inits in pins use the CAST form (5: size) — the bare untyped-int widen-store mis-tag is pre-existing at master and filed (task #33). 941 gains 13 rows: t22_* size/align folds (+ void-elem 0-slot pin), the full runtime round-trip (read, is/as, both element orders, void variant, destructure, literal-let), two-tagged-elem in-cap and float+tagged in BOTH orders (ken k1/k2 regression-pinned as rows), t.N-as-call-arg, 5 loud pins (arg, over-cap return, call-source element, global init, element write), and the sret_narrow_mix_* triple below. Runtime rows exit-checked under both drivers + byte-id. 129/129; unit tests green; sizelint clean. ken R1 (re-validation fix): an OVER-CAP tuple init whose rhs is not a CALL fell past every cstage N_LET store arm to NOTHING — silent uninitialized-frame reads — while wwstage loud-rejected the same shape. Pre-existing for (str,str) literals; the #22a tagged slots routed >16B-box tagged shapes into it (k5b/k5c/k5d, base-correct because base truncated them in-cap). cstage now routes the rhs through cgexpr (the cursor materialisers carry the exact wwstage loud texts) with a trailing fatal as the net; 941 pins both the tagged and the (str,str) spellings of the hole. reviewer-22 (review fixes, folded): the MLET-receive slot flip above landed ALONE on the wwstage over-cap sret family — the ww RETURN-send and MASSIGN-receive still strode packed esz (4 for u32), so `let (a,b,c) = f()` over (str,u32,str) read c at slot offset 32 while ww's send wrote it packed at 28: a ww runtime REGRESSION vs base (base was packed-consistent on both sides — ran right, byte-diff). Both walks now stride the slot (send mirrors cstage's `wide ? esz : 8`; MASSIGN strides tupeslotn) — closing, in the same stroke, the pre-existing base skews: ww `let t = f()` over-cap narrow-mix read (reader slot-laid vs send packed, runtime-wrong at base) and the cs≠ww asm on all three routings. Second find: t.N tagged element as a CALL ARG — cstage rides its generic node_istaggedarg cursor push, but wwstage's kind-gated aistagged missed N_DOT and mis-routed the box into the widening branch (taggedvariantindex -1 clamped to 0: callee read variant 0, silent, ww-only). cgenutil.ww gains the N_DOT arm (aistagged + pushargsrev), the #67 stamped-carrier twin of the N_INDEX (#12) arm. The N_DOT arm was a LIVE wwstage stdlib miscompile: hextest's import graph carried two t.N tagged-arg sites (base .s diff = exactly the clamped-tag PUSHQ pair) — the 989 lib-byteid ratchet caught the convergence and #59.4 graduates to M_ID (runtime-correct per 979_hex_run). Probes: /tmp/r22. Residual filed: tagged arg from deref/cast/unwrap sources is a word0-only read on BOTH stages (pre-existing, base-confirmed) — generalizing wwstage's kind gate to cstage's type-keyed check rides that task.
This commit is contained in:
381
cmd/w6c/cgen.c
381
cmd/w6c/cgen.c
@@ -197,7 +197,7 @@ type_chase_named(Type *t)
|
||||
}
|
||||
|
||||
/* cg_sret_retsize — sret classifier; defined after the tuple register-
|
||||
* return helpers (tuple_rseq / tuple_ebytes / fld_isfloat) it consults
|
||||
* return helpers (tuple_rseq / tuple_eslot / fld_isfloat) it consults
|
||||
* for the over-cap-tuple arm. Forward-declared here for the earlier
|
||||
* callers (cgcall, fn prologue). Task #23 / #10. */
|
||||
static int cg_sret_retsize(Type *rt);
|
||||
@@ -286,10 +286,32 @@ static const int tuple_sse_seq[] = { D_X0, D_X1 };
|
||||
#define TUPLE_GPCAP ((int)nelem(tuple_rseq))
|
||||
#define TUPLE_SSECAP ((int)nelem(tuple_sse_seq))
|
||||
|
||||
/* tuple_eslot — THE tuple element-stride accessor (#22): the slot a
|
||||
* tuple element occupies, in bytes. slot = roundup8(size(elem)), 8B a
|
||||
* FLOOR not a ceiling (user-ratified 2026-06-04): str/slice carry
|
||||
* their 24B header, a tagged element its full tag+payload box
|
||||
* ((str,str)=48B predates this; tagged was the one truncated >8B
|
||||
* kind — the #237 fieldslotsize precedent), narrow scalars pad UP to
|
||||
* one 8B eightbyte. Every tuple walk (cursor send/receive, t.N read,
|
||||
* destructure, sret classify, DATA emit) MUST take its stride and
|
||||
* its eightbyte count (eslot/8) from here — the per-site
|
||||
* wide=(STR||SLICE)-else-8 predicates this absorbs were the #22
|
||||
* neighbor-slot/zeros miscompile. Checker twin: check.c N_TTUPLE /
|
||||
* check.ww tupleelemslot. */
|
||||
static int
|
||||
tuple_ebytes(int wide)
|
||||
tuple_eslot(Type *t)
|
||||
{
|
||||
return wide ? (int)(ty_str->size / 8) : 1;
|
||||
Type *u = type_chase_named(t);
|
||||
if (u == NULL) return 8;
|
||||
if (u->kind == TY_VOID) return 0;
|
||||
/* a literal tuple's stamped element can be TY_UNTYPED_STR
|
||||
* (size 0) — it occupies the str header slot (the C-t2
|
||||
* type_isstr lesson at the arg restage). */
|
||||
if (u->kind == TY_UNTYPED_STR) return (int)ty_str->size;
|
||||
if (u->kind == TY_STR || u->kind == TY_SLICE
|
||||
|| u->kind == TY_TAGGED)
|
||||
return (int)((u->size + 7) & ~(u64)7);
|
||||
return 8;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -373,13 +395,10 @@ cg_sret_retsize(Type *rt)
|
||||
if (rt->kind == TY_TUPLE) {
|
||||
int gptotal = 0, ssecount = 0, f32;
|
||||
for (Tparam *p = rt->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
int wide = pu && (pu->kind == TY_SLICE
|
||||
|| pu->kind == TY_STR);
|
||||
if (fld_isfloat(p->type, &f32))
|
||||
ssecount++;
|
||||
else
|
||||
gptotal += tuple_ebytes(wide);
|
||||
gptotal += tuple_eslot(p->type) / 8;
|
||||
}
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > TUPLE_SSECAP)
|
||||
return (int)rt->size;
|
||||
@@ -1624,25 +1643,26 @@ ins1(Cg *c, int op, Adr to)
|
||||
}
|
||||
|
||||
/* tuple_store — store one received tuple element at BP-relative `off`
|
||||
* from its SysV-class register. A slice/str rides its 3-word
|
||||
* {ptr,len,cap} header from the INTEGER cursor tuple_rseq[gp..]; a float
|
||||
* from its SysV-class register. A >8B element (slice/str 3-word
|
||||
* {ptr,len,cap} header, tagged tag+payload box) rides tuple_eslot/8
|
||||
* consecutive INTEGER cursor words from tuple_rseq[gp..]; a float
|
||||
* rides tuple_sse_seq[sse] via MOVSD/MOVSS (#105 single-float widened to
|
||||
* the SSE cursor for #164/#107 multi-float); a scalar rides one INTEGER
|
||||
* word from tuple_rseq[gp]. The caller owns the dual cursor (validated +
|
||||
* advanced); this just emits the store. Shared by N_LET/N_MLET/N_MASSIGN
|
||||
* and, per #171, struct unpack — mirrors wwstage cgenstmt.ww tupstore. */
|
||||
static void
|
||||
tuple_store(Cg *c, Type *t, int wide, int gp, int sse, int off)
|
||||
tuple_store(Cg *c, Type *t, int gp, int sse, int off)
|
||||
{
|
||||
int f32 = 0;
|
||||
int eslot = tuple_eslot(t);
|
||||
|
||||
if (wide) {
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[gp + 0]),
|
||||
amem(D_BP, off + 0)); /* .ptr */
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[gp + 1]),
|
||||
amem(D_BP, off + 8)); /* .len */
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[gp + 2]),
|
||||
amem(D_BP, off + 16)); /* .cap */
|
||||
if (eslot == 0)
|
||||
return; /* void element: the checker's 0-slot */
|
||||
if (eslot > 8) {
|
||||
for (int k = 0; k < eslot / 8; k++)
|
||||
ins2(c, A_MOVQ, areg(tuple_rseq[gp + k]),
|
||||
amem(D_BP, off + k * 8));
|
||||
return;
|
||||
}
|
||||
if (fld_isfloat(t, &f32)) {
|
||||
@@ -2502,8 +2522,18 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
* 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 = tupsrc->list; e; e = e->next)
|
||||
for (Node *e = tupsrc->list; e; e = e->next) {
|
||||
/* #22a (rule 7): a tagged element's box can't ride
|
||||
* the scalar/wide store arms below — pre-guard it
|
||||
* silently stored word0. Nested tagged-in-tuple-in-
|
||||
* union packing is the #242/#22b family. */
|
||||
Type *eu = type_chase_named(e->type);
|
||||
if (eu && eu->kind == TY_TAGGED)
|
||||
fatal("cg_widen_tagged_store: tagged element "
|
||||
"in a tuple-in-union payload unwired "
|
||||
"(see #242/#22b)");
|
||||
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 "
|
||||
@@ -3093,6 +3123,65 @@ cg_structlit_fill_bp(Cg *c, Local **locals_p, Type *lu, Node *lit, int bp_off)
|
||||
cg_structlit_fill(c, locals_p, lu, lit, DST_BP, 0, NULL, bp_off);
|
||||
}
|
||||
|
||||
/* tuple_lit_gpwords — INTEGER cursor words an N_TUPLE literal element
|
||||
* occupies. MUST mirror the literal push arms (cg_tuple_lit_to_cursor /
|
||||
* cgreturn N_TUPLE) exactly — the count drives the POP fill, so a
|
||||
* count/push skew silently shifts every later element (#22 class). A
|
||||
* float rides the SSE row (0 GP words); str/slice push their 3-word
|
||||
* header; a tagged element its tuple_eslot/8 box words; a void element
|
||||
* pushes nothing (the checker's 0-slot — pre-#22 the push/receive
|
||||
* disagreed with the checker here, latent, no consumer); a scalar 1. */
|
||||
static int
|
||||
tuple_lit_gpwords(Node *e)
|
||||
{
|
||||
int f32;
|
||||
if (fld_isfloat(e->type, &f32)) return 0;
|
||||
if (node_isstr(e) || node_isslice(e)) return (int)(ty_str->size / 8);
|
||||
Type *eu = type_chase_named(e->type);
|
||||
if (eu && (eu->kind == TY_TAGGED || eu->kind == TY_VOID))
|
||||
return tuple_eslot(e->type) / 8;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* tuple_lit_push_elem — evaluate one N_TUPLE literal element and push
|
||||
* its INTEGER cursor words L→R (the pop side fills tuple_rseq in
|
||||
* reverse). A tagged element loads its box words straight from its
|
||||
* local slot — cgexpr's ident load is word0-only for tagged (every
|
||||
* tagged consumer reads memory), so the cursor fill must too; any
|
||||
* other tagged source shape is loud (rule 7; the cursor-receive arm
|
||||
* for call results is with #22b's tupstore work, widening literals
|
||||
* with #23). Shared by cg_tuple_lit_to_cursor and the cgreturn
|
||||
* N_TUPLE arm — count (tuple_lit_gpwords) and push live or die
|
||||
* together. */
|
||||
static void
|
||||
tuple_lit_push_elem(Cg *c, Local *locals, Node *e)
|
||||
{
|
||||
Type *eu = type_chase_named(e->type);
|
||||
if (eu && eu->kind == TY_TAGGED) {
|
||||
int eslot = tuple_eslot(e->type);
|
||||
int eoff = (e->kind == N_IDENT && e->str)
|
||||
? localfind(locals, e->str) : 0;
|
||||
if (eoff == 0)
|
||||
fatal("#22a: tagged tuple element from a non-local "
|
||||
"source shape unwired (ident locals only; "
|
||||
"rule 7, #22b/#23 follow-ups)");
|
||||
for (int k = 0; k < eslot / 8; k++) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, eoff + k * 8),
|
||||
areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
}
|
||||
return;
|
||||
}
|
||||
cgexpr(c, e, locals);
|
||||
if (eu && eu->kind == TY_VOID)
|
||||
return;
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (node_isstr(e) || node_isslice(e)) {
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
}
|
||||
}
|
||||
|
||||
/* cg_tuple_lit_to_cursor — #241: materialise an N_TUPLE literal's elements
|
||||
* into the SysV register-return cursor — integer words L→R over tuple_rseq
|
||||
* (AX,DX,CX,R8), floats over tuple_sse_seq (X0,X1), a slice/str's
|
||||
@@ -3116,8 +3205,7 @@ cg_tuple_lit_to_cursor(Cg *c, Local **locals, Node *tuple)
|
||||
if (fld_isfloat(e->type, &f32))
|
||||
ssecount++;
|
||||
else
|
||||
gptotal += tuple_ebytes(node_isstr(e)
|
||||
|| node_isslice(e));
|
||||
gptotal += tuple_lit_gpwords(e);
|
||||
}
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > TUPLE_SSECAP)
|
||||
fatal("tuple literal exceeds register-return ABI capacity "
|
||||
@@ -3137,18 +3225,14 @@ cg_tuple_lit_to_cursor(Cg *c, Local **locals, Node *tuple)
|
||||
int sseidx = 0;
|
||||
for (Node *e = tuple->list; e; e = e->next) {
|
||||
int isflt = fld_isfloat(e->type, &f32);
|
||||
cgexpr(c, e, *locals);
|
||||
if (isflt) {
|
||||
cgexpr(c, e, *locals);
|
||||
ins2(c, f32 ? A_MOVSS : A_MOVSD, areg(D_X0),
|
||||
amem(D_BP, fscr + sseidx * 8));
|
||||
sseidx++;
|
||||
continue;
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (node_isstr(e) || node_isslice(e)) {
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
}
|
||||
tuple_lit_push_elem(c, *locals, e);
|
||||
}
|
||||
for (int i = gptotal - 1; i >= 0; i--)
|
||||
ins1(c, A_POPQ, areg(tuple_rseq[i]));
|
||||
@@ -3179,12 +3263,10 @@ cg_tuple_slot_to_cursor(Cg *c, int srcoff, Type *tu)
|
||||
int f32;
|
||||
int gptotal = 0, ssecount = 0;
|
||||
for (Tparam *p = tu->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
int wide = pu && (pu->kind == TY_SLICE || pu->kind == TY_STR);
|
||||
if (fld_isfloat(p->type, &f32))
|
||||
ssecount++;
|
||||
else
|
||||
gptotal += tuple_ebytes(wide);
|
||||
gptotal += tuple_eslot(p->type) / 8;
|
||||
}
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > TUPLE_SSECAP)
|
||||
fatal("tuple ident exceeds register-return ABI capacity "
|
||||
@@ -3193,8 +3275,7 @@ cg_tuple_slot_to_cursor(Cg *c, int srcoff, Type *tu)
|
||||
gptotal, TUPLE_GPCAP, ssecount, TUPLE_SSECAP);
|
||||
int gp = 0, sse = 0, foff = 0;
|
||||
for (Tparam *p = tu->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
int wide = pu && (pu->kind == TY_SLICE || pu->kind == TY_STR);
|
||||
int eslot = tuple_eslot(p->type);
|
||||
int isflt = fld_isfloat(p->type, &f32);
|
||||
if (isflt) {
|
||||
ins2(c, f32 ? A_MOVSS : A_MOVSD,
|
||||
@@ -3202,18 +3283,13 @@ cg_tuple_slot_to_cursor(Cg *c, int srcoff, Type *tu)
|
||||
areg(tuple_sse_seq[sse]));
|
||||
sse++;
|
||||
foff += 8;
|
||||
} else if (wide) {
|
||||
for (int k = 0; k < 3; k++)
|
||||
} else {
|
||||
for (int k = 0; k < eslot / 8; k++)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, srcoff + foff + k * 8),
|
||||
areg(tuple_rseq[gp + k]));
|
||||
gp += 3;
|
||||
foff += (int)pu->size;
|
||||
} else {
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff + foff),
|
||||
areg(tuple_rseq[gp]));
|
||||
gp += 1;
|
||||
foff += 8;
|
||||
gp += eslot / 8;
|
||||
foff += eslot;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3234,13 +3310,12 @@ cg_tagged_tuple_payload_shift(Cg *c, Type *tup)
|
||||
int f32;
|
||||
int words = 0;
|
||||
for (Tparam *p = tup->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
int wide = pu && (pu->kind == TY_SLICE || pu->kind == TY_STR);
|
||||
if (fld_isfloat(p->type, &f32) || wide)
|
||||
fatal("tuple-in-union ? unwrap: float/slice/str payload "
|
||||
"element needs SysV per-eightbyte classification "
|
||||
"(see #243); only integer tuple payloads supported");
|
||||
words += tuple_ebytes(0);
|
||||
if (fld_isfloat(p->type, &f32) || tuple_eslot(p->type) != 8)
|
||||
fatal("tuple-in-union ? unwrap: float/slice/str/tagged "
|
||||
"payload element needs SysV per-eightbyte "
|
||||
"classification (see #243); only integer tuple "
|
||||
"payloads supported");
|
||||
words += 1;
|
||||
}
|
||||
/* tag occupies AX, so only DX/CX/R8 carry payload words. */
|
||||
if (words > (int)nelem(seq) - 1)
|
||||
@@ -6780,17 +6855,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
idx = idx * 10 + (*q - '0');
|
||||
Tparam *tp = bu->params;
|
||||
int foff = 0;
|
||||
/* C-t0: slot stride, twin of the N_DOT
|
||||
* TY_TUPLE walk. */
|
||||
/* C-t0/#22: slot stride (tuple_eslot),
|
||||
* twin of the N_DOT TY_TUPLE walk. */
|
||||
while (idx > 0 && tp) {
|
||||
Type *su = (tp->type
|
||||
&& tp->type->kind == TY_NAMED)
|
||||
? tp->type->under : tp->type;
|
||||
if (su && (su->kind == TY_STR
|
||||
|| su->kind == TY_SLICE))
|
||||
foff += (int)su->size;
|
||||
else
|
||||
foff += 8;
|
||||
foff += tuple_eslot(tp->type);
|
||||
tp = tp->next;
|
||||
idx--;
|
||||
}
|
||||
@@ -8554,27 +8622,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
"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))
|
||||
sstot++;
|
||||
else
|
||||
gptot += tuple_ebytes(wide);
|
||||
gptot += tuple_eslot(p->type) / 8;
|
||||
/* slot stride per element (sum == tuple slot
|
||||
* size); matches the wwstage slotsize() walk so
|
||||
* the @tupargscr width + reverse-push count agree
|
||||
* byte-for-byte. */
|
||||
tsz += wide ? (int)ty_str->size : 8;
|
||||
tsz += tuple_eslot(p->type);
|
||||
}
|
||||
/* The producing call already satisfied #164's
|
||||
* return caps; guard anyway (tuple_store indexes
|
||||
@@ -8592,16 +8648,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
"#163)", cg_tupargscr_sz, tsz);
|
||||
}
|
||||
for (Tparam *p = tuparg_push->params; p; p = p->next) {
|
||||
int wide = type_isstr(p->type)
|
||||
|| type_isslice(p->type);
|
||||
int isflt = fld_isfloat(p->type, &ef32);
|
||||
tuple_store(c, p->type, wide, gpcur, ssecur,
|
||||
tuple_store(c, p->type, gpcur, ssecur,
|
||||
cg_tupargscr + eoff);
|
||||
if (isflt)
|
||||
ssecur++;
|
||||
else
|
||||
gpcur += tuple_ebytes(wide);
|
||||
eoff += wide ? (int)ty_str->size : 8;
|
||||
gpcur += tuple_eslot(p->type) / 8;
|
||||
eoff += tuple_eslot(p->type);
|
||||
}
|
||||
for (int w = tsz - 8; w >= 0; w -= 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
@@ -8812,10 +8866,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* scope (twin of #164's cap). */
|
||||
int ef32;
|
||||
for (Tparam *p = tu->params; p; p = p->next) {
|
||||
/* C-t2: untyped-str-aware wide test —
|
||||
* twin of the @tupargscr restage walk. */
|
||||
int wide = type_isstr(p->type)
|
||||
|| type_isslice(p->type);
|
||||
if (fld_isfloat(p->type, &ef32)) {
|
||||
if (fi >= 8)
|
||||
fatal("tuple arg float "
|
||||
@@ -8831,7 +8881,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fi++;
|
||||
continue;
|
||||
}
|
||||
int eb = tuple_ebytes(wide);
|
||||
int eb = tuple_eslot(p->type) / 8;
|
||||
if (ii + eb > 6)
|
||||
fatal("tuple arg element "
|
||||
"overflows integer arg regs "
|
||||
@@ -9924,24 +9974,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
}
|
||||
/* tuple positional field access: t.0, t.1, ...
|
||||
* C-t0: slot stride (a str/slice its header, everything else
|
||||
* one 8B eightbyte) — the layout every cursor transport site
|
||||
* writes and the checker's TY_TUPLE size now counts. The load
|
||||
* below keeps the element's NATURAL width (fldloadop). */
|
||||
* C-t0/#22: slot stride (tuple_eslot — str/slice header,
|
||||
* tagged box, 8B floor) — the layout every cursor transport
|
||||
* site writes and the checker's TY_TUPLE size counts. The
|
||||
* load below keeps the element's NATURAL width (fldloadop). */
|
||||
if (u && u->kind == TY_TUPLE && n->lhs->kind == N_IDENT && n->str) {
|
||||
int idx = 0;
|
||||
for (const char *q = n->str; *q; q++) idx = idx * 10 + (*q - '0');
|
||||
Tparam *tp = u->params;
|
||||
int foff = 0;
|
||||
while (idx > 0 && tp) {
|
||||
Type *su = (tp->type
|
||||
&& tp->type->kind == TY_NAMED)
|
||||
? tp->type->under : tp->type;
|
||||
if (su && (su->kind == TY_STR
|
||||
|| su->kind == TY_SLICE))
|
||||
foff += (int)su->size;
|
||||
else
|
||||
foff += 8;
|
||||
foff += tuple_eslot(tp->type);
|
||||
tp = tp->next;
|
||||
idx--;
|
||||
}
|
||||
@@ -9997,6 +10040,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
areg(D_CX));
|
||||
break;
|
||||
}
|
||||
/* #22a: tagged element — load the box into the
|
||||
* tagged value regs (AX=tag, DX/CX/R8=payload),
|
||||
* the same cursor the is/as spill + match read.
|
||||
* A CX-based global never co-occurs (tagged
|
||||
* element inits are loud at the DATA emit), so
|
||||
* the ascending walk can't clobber the base. */
|
||||
if (fu && fu->kind == TY_TAGGED) {
|
||||
int eslot = tuple_eslot(tp->type);
|
||||
for (int k = 0; k < eslot / 8; k++)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg,
|
||||
base_disp + foff + k * 8),
|
||||
areg(tuple_rseq[k]));
|
||||
break;
|
||||
}
|
||||
ins2(c, op, amem(base_reg, base_disp + foff),
|
||||
areg(D_AX));
|
||||
}
|
||||
@@ -10961,21 +11019,33 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
int gpcur = 0, ssecur = 0, eoff = 0, ef32;
|
||||
for (Tparam *p = lu->params; p; p = p->next) {
|
||||
Type *pu = (p->type && p->type->kind == TY_NAMED)
|
||||
? p->type->under : p->type;
|
||||
int wide = pu && (pu->kind == TY_SLICE
|
||||
|| pu->kind == TY_STR);
|
||||
int isflt = fld_isfloat(p->type, &ef32);
|
||||
tuple_store(c, p->type, wide, gpcur, ssecur,
|
||||
tuple_store(c, p->type, gpcur, ssecur,
|
||||
off + eoff);
|
||||
if (isflt)
|
||||
ssecur++;
|
||||
else
|
||||
gpcur += tuple_ebytes(wide);
|
||||
eoff += wide ? (int)pu->size : 8;
|
||||
gpcur += tuple_eslot(p->type) / 8;
|
||||
eoff += tuple_eslot(p->type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* #22a (rule 7, ken R1): an OVER-CAP tuple init whose rhs is
|
||||
* not a CALL has no store path — only the CALL shape rides
|
||||
* the generic sret receive below; every other rhs fell past
|
||||
* ALL the store arms to NOTHING (silent uninitialized-frame
|
||||
* reads). Pre-existing for (str,str) literals; the #22a
|
||||
* tagged slot sizes routed tagged shapes into it. cgexpr's
|
||||
* cursor materialisers carry the loud caps (the literal /
|
||||
* ident messages wwstage emits for the same shapes — rule
|
||||
* 10); the fatal after is the net for any rhs kind whose
|
||||
* cgexpr doesn't loud. Wiring is the #10/#22b sret family. */
|
||||
if (n->rhs && n->rhs->kind != N_CALL && lu
|
||||
&& lu->kind == TY_TUPLE && cg_sret_retsize(lt) > 0) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
fatal("over-cap tuple initialiser from a non-call "
|
||||
"source unwired (see #10/#22b)");
|
||||
}
|
||||
/* #38b residual (rule 7): `let w: T = f()?;` / `f()!` where f
|
||||
* returns an sret-classified tagged union — the unwrap would
|
||||
* need a mem-based read of the sret slot. The N_LET arms
|
||||
@@ -12175,8 +12245,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
if (fld_isfloat(e->type, &f32))
|
||||
ssecount++;
|
||||
else
|
||||
gptotal += tuple_ebytes(node_isstr(e)
|
||||
|| node_isslice(e));
|
||||
gptotal += tuple_lit_gpwords(e);
|
||||
}
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
|
||||
/* #10 Fold A: over-cap tuple returns via sret. The
|
||||
@@ -12203,6 +12272,18 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
Type *rtt = type_chase_named(cg_ret_type);
|
||||
Tparam *pp = (rtt && rtt->kind == TY_TUPLE)
|
||||
? rtt->params : NULL;
|
||||
/* #22a (rule 7): a tagged element's box can't
|
||||
* ride the scalar/wide store arms below — the
|
||||
* MEMORY-class tagged element store is #22b
|
||||
* (task #28); pre-guard it silently stored
|
||||
* word0 of the box. */
|
||||
for (Tparam *gp_ = pp; gp_; gp_ = gp_->next) {
|
||||
Type *gu = type_chase_named(gp_->type);
|
||||
if (gu && gu->kind == TY_TAGGED)
|
||||
fatal("#22b: tagged element in "
|
||||
"an over-cap (sret) tuple "
|
||||
"return unwired (rule 7)");
|
||||
}
|
||||
int foff = 0;
|
||||
for (Node *e = n->lhs->list; e; e = e->next) {
|
||||
int isflt = fld_isfloat(e->type, &f32);
|
||||
@@ -12255,18 +12336,16 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
int sseidx = 0;
|
||||
for (Node *e = n->lhs->list; e; e = e->next) {
|
||||
int isflt = fld_isfloat(e->type, &f32);
|
||||
cgexpr(c, e, *locals); /* scalar=AX; slice/str=AX,BX,CX; float=X0 */
|
||||
if (isflt) {
|
||||
cgexpr(c, e, *locals); /* float=X0 */
|
||||
ins2(c, f32 ? A_MOVSS : A_MOVSD, areg(D_X0),
|
||||
amem(D_BP, fscr + sseidx * 8));
|
||||
sseidx++;
|
||||
continue;
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* scalar / .ptr */
|
||||
if (node_isstr(e) || node_isslice(e)) {
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* .len */
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* .cap */
|
||||
}
|
||||
/* scalar=AX; slice/str=AX,BX,CX; tagged box
|
||||
* from its slot (tuple_lit_push_elem) */
|
||||
tuple_lit_push_elem(c, *locals, e);
|
||||
}
|
||||
for (int i = gptotal - 1; i >= 0; i--)
|
||||
ins1(c, A_POPQ, areg(tuple_rseq[i]));
|
||||
@@ -12574,22 +12653,19 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
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 eslot = tuple_eslot(t);
|
||||
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);
|
||||
eslot > 8 ? eslot : 8, 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) {
|
||||
} else if (eslot > 8) {
|
||||
for (int k = 0; k < eslot; k += 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, srcoff + foff + k),
|
||||
areg(D_AX));
|
||||
@@ -12603,7 +12679,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, fldstoreop(t, esz),
|
||||
areg(D_AX), amem(D_BP, off));
|
||||
}
|
||||
foff += wide ? 24 : 8;
|
||||
foff += eslot;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -12617,20 +12693,18 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
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 eslot = tuple_eslot(t);
|
||||
int isflt = fld_isfloat(t, &lf32);
|
||||
int esz = t ? (int)t->size : 8;
|
||||
int bsz = wide ? esz : 8;
|
||||
int off = localoff(c, locals, l->str, bsz, frame);
|
||||
int off = localoff(c, locals, l->str,
|
||||
eslot > 8 ? eslot : 8, frame);
|
||||
if (isflt) {
|
||||
ins2(c, lf32 ? A_MOVSS : A_MOVSD,
|
||||
amem(D_BP, scr + foff), areg(D_X0));
|
||||
ins2(c, lf32 ? A_MOVSS : A_MOVSD,
|
||||
areg(D_X0), amem(D_BP, off));
|
||||
} else if (wide) {
|
||||
for (int k = 0; k < esz; k += 8) {
|
||||
} else if (eslot > 8) {
|
||||
for (int k = 0; k < eslot; k += 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr + foff + k),
|
||||
areg(D_AX));
|
||||
@@ -12645,7 +12719,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
/* C-t0: slot stride — must mirror the
|
||||
* N_RETURN over-cap SEND's buffer layout. */
|
||||
foff += wide ? esz : 8;
|
||||
foff += eslot;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -12653,13 +12727,10 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
int ssecap = TUPLE_SSECAP;
|
||||
int gptotal = 0, ssetotal = 0;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *t = l->type;
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
if (fld_isfloat(t, &lf32))
|
||||
if (fld_isfloat(l->type, &lf32))
|
||||
ssetotal++;
|
||||
else
|
||||
gptotal += tuple_ebytes(u && (u->kind == TY_SLICE
|
||||
|| u->kind == TY_STR));
|
||||
gptotal += tuple_eslot(l->type) / 8;
|
||||
}
|
||||
if (gptotal > gpcap)
|
||||
fatal("tuple destructure exceeds integer register-return "
|
||||
@@ -12672,16 +12743,15 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
int gpcur = 0, ssecur = 0;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *t = l->type;
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
||||
int eslot = tuple_eslot(t);
|
||||
int isflt = fld_isfloat(t, &lf32);
|
||||
int sz = wide ? (int)u->size : 8;
|
||||
int off = localoff(c, locals, l->str, sz, frame);
|
||||
tuple_store(c, t, wide, gpcur, ssecur, off);
|
||||
int off = localoff(c, locals, l->str,
|
||||
eslot > 8 ? eslot : 8, frame);
|
||||
tuple_store(c, t, gpcur, ssecur, off);
|
||||
if (isflt)
|
||||
ssecur++;
|
||||
else
|
||||
gpcur += tuple_ebytes(wide);
|
||||
gpcur += eslot / 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -12722,9 +12792,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
Tparam *tp = tp0;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *et = tp ? tp->type : NULL;
|
||||
Type *eu = type_chase_named(et);
|
||||
int wide = eu && (eu->kind == TY_SLICE
|
||||
|| eu->kind == TY_STR);
|
||||
int eslot = tuple_eslot(et);
|
||||
int isflt = fld_isfloat(et, &mf32);
|
||||
int esz = et ? (int)et->size : 8;
|
||||
int off = (l->kind == N_IDENT)
|
||||
@@ -12736,8 +12804,8 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
areg(D_X0));
|
||||
ins2(c, mf32 ? A_MOVSS : A_MOVSD,
|
||||
areg(D_X0), amem(D_BP, off));
|
||||
} else if (wide) {
|
||||
for (int k = 0; k < esz; k += 8) {
|
||||
} else if (eslot > 8) {
|
||||
for (int k = 0; k < eslot; k += 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr + foff + k),
|
||||
areg(D_AX));
|
||||
@@ -12754,7 +12822,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
/* C-t0: slot stride — must mirror the
|
||||
* N_RETURN over-cap SEND's buffer layout. */
|
||||
foff += wide ? esz : 8;
|
||||
foff += eslot;
|
||||
if (tp) tp = tp->next;
|
||||
}
|
||||
break;
|
||||
@@ -12763,13 +12831,10 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
int ssecap = TUPLE_SSECAP;
|
||||
int gptotal = 0, ssetotal = 0;
|
||||
for (Tparam *tp = tp0; tp; tp = tp->next) {
|
||||
Type *u = (tp->type && tp->type->kind == TY_NAMED)
|
||||
? tp->type->under : tp->type;
|
||||
if (fld_isfloat(tp->type, &mf32))
|
||||
ssetotal++;
|
||||
else
|
||||
gptotal += tuple_ebytes(u && (u->kind == TY_SLICE
|
||||
|| u->kind == TY_STR));
|
||||
gptotal += tuple_eslot(tp->type) / 8;
|
||||
}
|
||||
if (gptotal > gpcap)
|
||||
fatal("tuple destructure exceeds integer register-return "
|
||||
@@ -12783,19 +12848,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
Tparam *tp = tp0;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *et = tp ? tp->type : NULL;
|
||||
Type *u = (et && et->kind == TY_NAMED) ? et->under : et;
|
||||
int wide = u && (u->kind == TY_SLICE || u->kind == TY_STR);
|
||||
int isflt = fld_isfloat(et, &mf32);
|
||||
int off = (l->kind == N_IDENT)
|
||||
? localfind(*locals, l->str) : 0;
|
||||
/* harec `_` (off==0): skip the store but CONSUME the
|
||||
* cursor slot so the next element stays aligned. */
|
||||
if (off != 0)
|
||||
tuple_store(c, et, wide, gpcur, ssecur, off);
|
||||
tuple_store(c, et, gpcur, ssecur, off);
|
||||
if (isflt)
|
||||
ssecur++;
|
||||
else
|
||||
gpcur += tuple_ebytes(wide);
|
||||
gpcur += tuple_eslot(et) / 8;
|
||||
if (tp) tp = tp->next;
|
||||
}
|
||||
break;
|
||||
@@ -12988,11 +13051,6 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
int off = localoff(c, &locals, p->str, sz, &frame);
|
||||
int eoff = 0, ef32;
|
||||
for (Tparam *te = pu->params; te; te = te->next) {
|
||||
Type *teu = (te->type
|
||||
&& te->type->kind == TY_NAMED)
|
||||
? te->type->under : te->type;
|
||||
int wide = teu && (teu->kind == TY_SLICE
|
||||
|| teu->kind == TY_STR);
|
||||
if (fld_isfloat(te->type, &ef32)) {
|
||||
if (fargi >= 8)
|
||||
fatal("tuple param float element "
|
||||
@@ -13006,7 +13064,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
eoff += 8;
|
||||
continue;
|
||||
}
|
||||
int eb = tuple_ebytes(wide);
|
||||
int eb = tuple_eslot(te->type) / 8;
|
||||
if (argi + eb > 6)
|
||||
fatal("tuple param element overflows "
|
||||
"integer arg regs (DI/SI/DX/CX/R8/"
|
||||
@@ -13015,7 +13073,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
ins2(c, A_MOVQ,
|
||||
areg(sysv_argregs[argi]),
|
||||
amem(D_BP, off + eoff + k * 8));
|
||||
eoff += wide ? (int)teu->size : 8;
|
||||
eoff += tuple_eslot(te->type);
|
||||
}
|
||||
if (tp) tp = tp->next;
|
||||
continue;
|
||||
@@ -13805,6 +13863,15 @@ emit_tuple_data(FILE *out, Cg *c, const char *name, const char *module,
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
if (ev == NULL) return 0;
|
||||
/* #22a (rule 7): a tagged element slot has no static-init
|
||||
* shape (tag word + payload widening) — reject so the
|
||||
* caller loud-stops; pre-guard an int init would have
|
||||
* emitted one 8B word into the 16B+ box (silent layout
|
||||
* skew). */
|
||||
{
|
||||
Type *eu = type_chase_named(tp ? tp->type : NULL);
|
||||
if (eu && eu->kind == TY_TAGGED) return 0;
|
||||
}
|
||||
int wide = tp && (type_isstr(tp->type)
|
||||
|| type_isslice(tp->type));
|
||||
if (wide) {
|
||||
@@ -13849,7 +13916,7 @@ emit_tuple_data(FILE *out, Cg *c, const char *name, const char *module,
|
||||
fprintf(out, "DATAR %s+%d(SB),%s(SB)\n",
|
||||
sym, foff, lab);
|
||||
}
|
||||
foff += wide ? (int)ty_str->size : 8;
|
||||
foff += tuple_eslot(tp ? tp->type : NULL);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -652,11 +652,16 @@ resolve_type(Checker *c, Node *n)
|
||||
if (tp->type && tp->type->align > al) al = tp->type->align;
|
||||
/* Slot layout is the tuple SSoT (tuple arc C-t0,
|
||||
* user-ratified): every element occupies the stride
|
||||
* cgen's cursor transport actually writes — a
|
||||
* str/slice its header, everything else (narrow
|
||||
* scalars included) one 8B eightbyte. ww-internal ABI
|
||||
* only (tuples never cross extern); size((u32,u32))=16
|
||||
* is observable via size() and diverges from Hare
|
||||
* cgen's cursor transport actually writes — slot =
|
||||
* roundup8(size(elem)), 8B a FLOOR not a ceiling
|
||||
* (#22, user-ratified 2026-06-04): str/slice carry
|
||||
* their 24B header, a tagged element its full
|
||||
* tag+payload box ((str,str)=48B predates this;
|
||||
* tagged was the one truncated >8B kind — the #237
|
||||
* fieldslotsize precedent), narrow scalars pad UP
|
||||
* to one 8B eightbyte. ww-internal ABI only (tuples
|
||||
* never cross extern); size((u32,u32))=16 is
|
||||
* observable via size() and diverges from Hare
|
||||
* (harec type_store.c:533-580 anonymous-struct rule)
|
||||
* AND from ww's own structs (which pack narrow fields
|
||||
* post-fldloadop) — that internal inconsistency is
|
||||
@@ -669,8 +674,9 @@ resolve_type(Checker *c, Node *n)
|
||||
Type *eu = tp->type->kind == TY_NAMED
|
||||
? tp->type->under : tp->type;
|
||||
if (eu && (eu->kind == TY_STR
|
||||
|| eu->kind == TY_SLICE))
|
||||
sz += eu->size;
|
||||
|| eu->kind == TY_SLICE
|
||||
|| eu->kind == TY_TAGGED))
|
||||
sz += (eu->size + 7) & ~(u64)7;
|
||||
else if (eu == NULL || eu->kind != TY_VOID)
|
||||
sz += 8; /* sizelint-ok: the slot IS the 8B eightbyte */
|
||||
}
|
||||
|
||||
@@ -11864,6 +11864,13 @@ fn tupleelemslot(pt: *tinfo) u64 = {
|
||||
if (pk == tykind.TY_VOID) { return 0u64; };
|
||||
if (pk == tykind.TY_STR) { return t.size; };
|
||||
if (pk == tykind.TY_SLICE) { return t.size; };
|
||||
// #22 (user-ratified 2026-06-04): slot = roundup8(size(elem)) — 8B
|
||||
// is a FLOOR, not a ceiling. (str,str)=48B predates this; tagged
|
||||
// was the one truncated >8B kind (the #237 fieldslotsize-missing-
|
||||
// TY_TUPLE precedent: fieldslotsize below already carried this
|
||||
// arm). Cstage twin: check.c N_TTUPLE; cgen accessor: tuple_eslot
|
||||
// / tupeslot.
|
||||
if (pk == tykind.TY_TAGGED) { return (t.size + 7u64) & ~7u64; };
|
||||
if (pk == tykind.TY_PTR || pk == tykind.TY_FN ||
|
||||
pk == tykind.TY_CHAN || pk == tykind.TY_I64 ||
|
||||
pk == tykind.TY_U64 || pk == tykind.TY_INT ||
|
||||
@@ -16273,6 +16280,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #22a: t.N tuple-element read leaves the
|
||||
// same AX/DX/CX/R8 box cursor (this arc's
|
||||
// t.N box load) — without this gate the
|
||||
// widening scalar branch clamps the
|
||||
// unresolvable tag to 0 and the callee
|
||||
// reads variant 0. Stamped-carrier (#67)
|
||||
// twin of the N_INDEX arm above; cstage
|
||||
// needs no kind gate (its widen[i] `same`
|
||||
// check is type-keyed on args[i]->type).
|
||||
if (arg.kind == nkind.N_DOT) {
|
||||
if (istaggedtype(c, arg)) {
|
||||
if (slotsize(c, arg) == slotsize(c, ptype)) {
|
||||
aistagged = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!aistagged) {
|
||||
widensz = slotsize(c, ptype);
|
||||
let tagged: *node = resolvetagged(c, ptype);
|
||||
@@ -16751,13 +16774,17 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
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); };
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
if (isfloattype(c, et)) { sstot += 1; }
|
||||
else { gptot += tupebytes(wide); };
|
||||
if (wide) { tsz += (tyslicesize(): i32); }
|
||||
else { tsz += 8; };
|
||||
else { gptot += eslot / 8; };
|
||||
tsz += eslot;
|
||||
p = p.next;
|
||||
};
|
||||
// The producing cursor fill already satisfied #164's caps;
|
||||
@@ -16778,11 +16805,12 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
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);
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
tupstore(c, gpcur, ssecur, scr + eoff, eslot, et);
|
||||
if (isfloattype(c, et)) { ssecur += 1; }
|
||||
else { gpcur += tupebytes(wide); };
|
||||
if (wide) { eoff += (tyslicesize(): i32); }
|
||||
else { eoff += 8; };
|
||||
else { gpcur += eslot / 8; };
|
||||
eoff += eslot;
|
||||
p = p.next;
|
||||
};
|
||||
let w: i32 = tsz - 8;
|
||||
@@ -16855,7 +16883,13 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
// arg.type_ is the element tinfo (istaggedtype/slotsize read
|
||||
// .type_) — feed the N_INDEX node directly, dropping the
|
||||
// indexvaluetnode walk.
|
||||
if (arg.kind == nkind.N_INDEX) {
|
||||
// #22a: N_DOT rides the same arm — the t.N tuple-element box load
|
||||
// (this arc) fills the identical AX/DX/CX/R8 cursor. cstage's twin
|
||||
// is the generic node_istaggedarg push (cgen.c cgcall); wwstage
|
||||
// keeps the stamped-carrier kind gate (#67 pattern) — the
|
||||
// remaining kinds (deref/cast/unwrap) are word0-only reads today,
|
||||
// filed residual.
|
||||
if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) {
|
||||
if (istaggedtype(c, arg)) {
|
||||
let isz: i32 = slotsize(c, arg);
|
||||
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||
@@ -17661,8 +17695,7 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
||||
if (isfloattype(c, et)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(et) / 8;
|
||||
};
|
||||
pt = pt.next;
|
||||
};
|
||||
@@ -19438,6 +19471,20 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
let ttotal: i32 = 0;
|
||||
let ce: *node = tupsrc.list;
|
||||
for (ce != nil) {
|
||||
// #22a (rule 7): a tagged element's box can't
|
||||
// ride the scalar/wide store arms below —
|
||||
// pre-guard it silently stored word0. Nested
|
||||
// tagged-in-tuple-in-union packing is the
|
||||
// #242/#22b family. Mirrors cstage.
|
||||
let ceti: *tinfo = ce.type_: *tinfo;
|
||||
for (ceti != nil && ceti.kind == tykind.TY_NAMED) {
|
||||
ceti = ceti.under;
|
||||
};
|
||||
if (ceti != nil && ceti.kind == tykind.TY_TAGGED) {
|
||||
let m22: str = "cgwidentaggedstore: tagged element in a tuple-in-union payload unwired (see #242/#22b)\n";
|
||||
os.write(2, m22.ptr, m22.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (nodeisstr(c, ce) || nodeisslice(c, ce)) {
|
||||
ttotal += 24;
|
||||
} else { ttotal += 8; };
|
||||
@@ -20570,7 +20617,22 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (vt == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) {
|
||||
// #22a: a STAMPED-CARRIER scrutinee (the #67 matchscrutt
|
||||
// shape — is/as on a tuple element t.N, a struct field, an
|
||||
// indexed element) is not an N_TTAGGED type-AST node; its
|
||||
// tagged type rides .type_. Resolve via the tinfo twin
|
||||
// (flatvariantidxt), the same core the widen-store uses —
|
||||
// cstage cg_tag_for_variant is type-based for every
|
||||
// scrutinee shape, so the AST-keyed -1 here was a silent
|
||||
// tag-0 clamp on wwstage (cs CMPQ $1 vs ww CMPQ $0).
|
||||
let sti: *tinfo = tagged.type_: *tinfo;
|
||||
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
|
||||
if (sti != nil && sti.kind == tykind.TY_TAGGED && vt.type_ != nil) {
|
||||
return flatvariantidxt(sti, vt.type_: *tinfo);
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
// `is []T` / `as []T` — slice-shape lookup routes through the
|
||||
// element-aware helper, which carries the loose first-slice-shape
|
||||
// fallback (cstage type_assignable stand-in) that flatvariantidx's
|
||||
@@ -23080,7 +23142,9 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp.lhs);
|
||||
// C-t0/#22: slot stride
|
||||
// (tupeslot accessor).
|
||||
foff += tupeslotn(tp.lhs);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
@@ -23114,6 +23178,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), X0\n");
|
||||
return;
|
||||
};
|
||||
// #22a: tagged element — load the box
|
||||
// into the tagged value regs (AX=tag,
|
||||
// DX/CX/R8=payload), the cursor the
|
||||
// is/as spill + match read. Byte-id
|
||||
// twin of cstage's N_DOT TY_TUPLE
|
||||
// tagged arm.
|
||||
if (istaggedtype(c, tpt)) {
|
||||
let eslot: i32 = tupeslotn(tpt);
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + k * 8): i64);
|
||||
emitline("(BP), ");
|
||||
emitline(tupreg(k));
|
||||
emitline("\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// C-t0: load at the element's NATURAL
|
||||
// width (narrow MOVL/MOVSXD/... at the
|
||||
// slot base), not the 8B slot width —
|
||||
@@ -23284,7 +23367,9 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
for (gi < gidx) {
|
||||
if (gtp == nil) { gi = gidx; }
|
||||
else {
|
||||
gfoff += slotsize(c, gtp.lhs);
|
||||
// C-t0/#22: slot stride
|
||||
// (tupeslot accessor).
|
||||
gfoff += tupeslotn(gtp.lhs);
|
||||
gtp = gtp.next;
|
||||
gi += 1;
|
||||
};
|
||||
@@ -26474,7 +26559,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp.lhs);
|
||||
// C-t0/#22: slot
|
||||
// stride (tupeslot).
|
||||
foff += tupeslotn(tp.lhs);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
@@ -26510,7 +26597,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
for (gi < gidx) {
|
||||
if (gtp == nil) { gi = gidx; }
|
||||
else {
|
||||
gfoff += slotsize(c, gtp.lhs);
|
||||
// C-t0/#22: slot
|
||||
// stride (tupeslot).
|
||||
gfoff += tupeslotn(gtp.lhs);
|
||||
gtp = gtp.next;
|
||||
gi += 1;
|
||||
};
|
||||
@@ -26963,10 +27052,14 @@ 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); };
|
||||
let eb: i32 = tupebytes(wide);
|
||||
let eb: i32 = 1;
|
||||
if (wide) { eb = (tyslicesize() / 8i64): i32; };
|
||||
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);
|
||||
@@ -31050,9 +31143,36 @@ fn tupsse(i: i32) str = {
|
||||
return "X1";
|
||||
};
|
||||
|
||||
fn tupebytes(wide: bool) i32 = {
|
||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||
return 1;
|
||||
// tupeslot — THE tuple element-stride accessor (#22): the slot a tuple
|
||||
// element occupies, in bytes. slot = roundup8(size(elem)), 8B a FLOOR
|
||||
// not a ceiling (user-ratified 2026-06-04): str/slice carry their 24B
|
||||
// header, a tagged element its full tag+payload box ((str,str)=48B
|
||||
// predates this; tagged was the one truncated >8B kind — the #237
|
||||
// fieldslotsize precedent), narrow scalars pad UP to one 8B eightbyte.
|
||||
// Every tuple walk (cursor send/receive, t.N read, destructure, sret
|
||||
// classify, DATA emit) takes its stride and its eightbyte count
|
||||
// (eslot/8) from here — the per-site wide=(STR||SLICE)-else-8
|
||||
// predicates this absorbs were the #22 neighbor-slot/zeros miscompile.
|
||||
// Checker twin: check.ww tupleelemslot / check.c N_TTUPLE; cstage twin:
|
||||
// tuple_eslot (cmd/w6c/cgen.c).
|
||||
export fn tupeslot(ti: *tinfo) i32 = {
|
||||
let t: *tinfo = ti;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t == nil) { return 8; };
|
||||
if (t.kind == tykind.TY_VOID) { return 0; };
|
||||
// a literal tuple's stamped element can be untyped_str (size 0) —
|
||||
// it occupies the str header slot (the C-t2 type_isstr lesson).
|
||||
if (t.kind == tykind.TY_UNTYPED_STR) { return tyslicesize(): i32; };
|
||||
if (t.kind == tykind.TY_STR || t.kind == tykind.TY_SLICE ||
|
||||
t.kind == tykind.TY_TAGGED) {
|
||||
return ((t.size + 7u64) & ~7u64): i32;
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
|
||||
export fn tupeslotn(n: *node) i32 = {
|
||||
if (n == nil) { return 8; };
|
||||
return tupeslot(n.type_: *tinfo);
|
||||
};
|
||||
|
||||
// rettupleof — the N_TTUPLE return-type node of an N_CALL rhs (else nil).
|
||||
@@ -31119,29 +31239,24 @@ fn nodetuplearg(c: *cgen, a: *node) *node = {
|
||||
};
|
||||
|
||||
// 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
|
||||
// INTEGER cursor registers; a float rides the SSE cursor (X0,X1); a
|
||||
// scalar stores 1 INTEGER word. The caller owns the dual cursor
|
||||
// (validated + advanced). Byte-identical to the cstage tuple_store
|
||||
// (cmd/w6c/cgen.c).
|
||||
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node) void = {
|
||||
if (wide) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 0));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 1));
|
||||
emitline(", ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 2));
|
||||
emitline(", ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
// BP-relative slot at `off`. A >8B element (slice/str 3-word
|
||||
// {ptr,len,cap} header, ref/hare/rt/ensure.ha:4-8; tagged tag+payload
|
||||
// box, #22) stores its eslot/8 words from consecutive INTEGER cursor
|
||||
// registers; a float rides the SSE cursor (X0,X1); a scalar stores 1
|
||||
// INTEGER word. The caller owns the dual cursor (validated +
|
||||
// advanced). Byte-identical to the cstage tuple_store (cmd/w6c/cgen.c).
|
||||
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, eslot: i32, tn: *node) void = {
|
||||
if (eslot == 0) { return; }; // void element: the checker's 0-slot
|
||||
if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + k));
|
||||
emitline(", ");
|
||||
emitoff((off + k * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// #105 / #164 (#107): an f64/f32 element rides the SSE cursor reg
|
||||
@@ -31195,6 +31310,67 @@ fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node)
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
|
||||
// tuplitgpwords — INTEGER cursor words an N_TUPLE literal element
|
||||
// occupies. MUST mirror the literal push arms (tuplitpushelem) exactly
|
||||
// — the count drives the POP fill, so a count/push skew silently
|
||||
// shifts every later element (#22 class). A float rides the SSE row
|
||||
// (0 GP words); str/slice push their 3-word header; a tagged element
|
||||
// its tupeslot/8 box words; a void element pushes nothing (the
|
||||
// checker's 0-slot); a scalar 1. Mirror of cstage tuple_lit_gpwords.
|
||||
fn tuplitgpwords(c: *cgen, e: *node) i32 = {
|
||||
if (isfloattype(c, e)) { return 0; };
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
return (tyslicesize() / 8i64): i32;
|
||||
};
|
||||
let t: *tinfo = e.type_: *tinfo;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t != nil && (t.kind == tykind.TY_TAGGED ||
|
||||
t.kind == tykind.TY_VOID)) {
|
||||
return tupeslotn(e) / 8;
|
||||
};
|
||||
return 1;
|
||||
};
|
||||
|
||||
// tuplitpushelem — evaluate one N_TUPLE literal element and push its
|
||||
// INTEGER cursor words L->R (the pop side fills tupreg in reverse). A
|
||||
// tagged element loads its box words straight from its local slot —
|
||||
// cgexpr's ident load is word0-only for tagged (every tagged consumer
|
||||
// reads memory), so the cursor fill must too; any other tagged source
|
||||
// shape is loud (rule 7; the cursor-receive arm for call results is
|
||||
// with #22b's tupstore work, widening literals with #23). Mirror of
|
||||
// cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or
|
||||
// die together.
|
||||
fn tuplitpushelem(c: *cgen, e: *node) void = {
|
||||
let t: *tinfo = e.type_: *tinfo;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t != nil && t.kind == tykind.TY_TAGGED) {
|
||||
let eslot: i32 = tupeslotn(e);
|
||||
let eoff: i32 = 0;
|
||||
if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); };
|
||||
if (eoff == 0) {
|
||||
let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7, #22b/#23 follow-ups)\n";
|
||||
os.write(2, m22.ptr, m22.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((eoff + k * 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
cgexpr(c, e);
|
||||
if (t != nil && t.kind == tykind.TY_VOID) { return; };
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
};
|
||||
};
|
||||
|
||||
// cgtuplelittocursor — #241: materialise an N_TUPLE literal's elements into
|
||||
// the SysV register-return cursor (integer words L->R over tupreg AX/DX/CX/
|
||||
// R8, floats over tupsse X0/X1, a slice/str's {ptr,len,cap} over three
|
||||
@@ -31214,8 +31390,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
if (isfloattype(c, e)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tuplitgpwords(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -31232,8 +31407,8 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
e = tuple.list;
|
||||
for (e != nil) {
|
||||
let isflt: bool = isfloattype(c, e);
|
||||
cgexpr(c, e);
|
||||
if (isflt) {
|
||||
cgexpr(c, e);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
@@ -31241,11 +31416,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
sseidx = sseidx + 1;
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
};
|
||||
tuplitpushelem(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -31292,8 +31463,7 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
if (et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslot(el.type_) / 8;
|
||||
};
|
||||
el = el.tnext;
|
||||
};
|
||||
@@ -31310,7 +31480,7 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
let et: *tinfo = el.type_;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
let isflt: bool = et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64);
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
let eslot: i32 = tupeslot(el.type_);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
if (et.kind == tykind.TY_F32) { mov = "MOVSS"; };
|
||||
@@ -31321,9 +31491,9 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
emitline("\n");
|
||||
sse = sse + 1;
|
||||
foff += 8;
|
||||
} else { if (wide) {
|
||||
} else {
|
||||
let k: i32 = 0;
|
||||
for (k < 3) {
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff + k * 8): i64);
|
||||
emitline("(BP), ");
|
||||
@@ -31331,17 +31501,9 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
emitline("\n");
|
||||
k += 1;
|
||||
};
|
||||
gp += 3;
|
||||
foff += et.size: i32;
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff): i64);
|
||||
emitline("(BP), ");
|
||||
emitline(tupreg(gp));
|
||||
emitline("\n");
|
||||
gp += 1;
|
||||
foff += 8;
|
||||
}; };
|
||||
gp += eslot / 8;
|
||||
foff += eslot;
|
||||
};
|
||||
el = el.tnext;
|
||||
};
|
||||
};
|
||||
@@ -31361,9 +31523,8 @@ fn cgtaggedtuplepayloadshift(c: *cgen, tup: *tinfo) void = {
|
||||
let et: *tinfo = el.type_;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
let isflt: bool = et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64);
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
if (isflt || wide) {
|
||||
let msg: str = "tuple-in-union ? unwrap: float/slice/str payload element needs SysV per-eightbyte classification (see #243); only integer tuple payloads supported\n";
|
||||
if (isflt || tupeslot(el.type_) != 8) {
|
||||
let msg: str = "tuple-in-union ? unwrap: float/slice/str/tagged payload element needs SysV per-eightbyte classification (see #243); only integer tuple payloads supported\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
@@ -31420,12 +31581,28 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, e)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tuplitgpwords(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
|
||||
// #22a (rule 7): a tagged element's box can't
|
||||
// ride the scalar/wide store arms below — the
|
||||
// MEMORY-class tagged element store is #22b
|
||||
// (task #28); pre-guard it silently stored
|
||||
// word0 of the box.
|
||||
let gq: *node = nil;
|
||||
if (c.fnret != nil) { gq = c.fnret.list; };
|
||||
for (gq != nil) {
|
||||
let gqt: *tinfo = gq.lhs.type_: *tinfo;
|
||||
for (gqt != nil && gqt.kind == tykind.TY_NAMED) { gqt = gqt.under; };
|
||||
if (gqt != nil && gqt.kind == tykind.TY_TAGGED) {
|
||||
let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return unwired (rule 7)\n";
|
||||
os.write(2, m22b.ptr, m22b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
gq = gq.next;
|
||||
};
|
||||
// #10 Fold A: over-cap tuple returns via sret. The
|
||||
// prologue wired @sretarg (sretretsize agrees on the
|
||||
// caps — TUPLE_GPCAP/TUPLE_SSECAP, the shared SSoT),
|
||||
@@ -31485,7 +31662,12 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("\n");
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
// C-t0: the sret buffer is slot-laid like
|
||||
// every tuple home (checker size, t.N
|
||||
// reader, mlet receive agree); esz keeps
|
||||
// the store WIDTH natural. Mirrors cstage
|
||||
// cgen.c N_RETURN over-cap arm.
|
||||
if (wide) { foff += esz; } else { foff += 8; };
|
||||
we = we.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
@@ -31506,8 +31688,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
e = rhs.list;
|
||||
for (e != nil) {
|
||||
let isflt: bool = isfloattype(c, e);
|
||||
cgexpr(c, e);
|
||||
if (isflt) {
|
||||
cgexpr(c, e);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
@@ -31517,11 +31699,9 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
sseidx = sseidx + 1;
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n"); // .len
|
||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||
};
|
||||
// scalar=AX; slice/str=AX,BX,CX; tagged
|
||||
// box from its slot (tuplitpushelem)
|
||||
tuplitpushelem(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -32962,20 +33142,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
for (q != nil) {
|
||||
let qt: *node = q.lhs;
|
||||
let isflt: bool = isfloattype(c, qt);
|
||||
let wide: bool = isstrtype(c, qt)
|
||||
|| isslicetype(c, qt);
|
||||
let eslot: i32 = tupeslotn(qt);
|
||||
tupstore(c, gpcur, ssecur,
|
||||
off + eoff, wide, qt);
|
||||
off + eoff, eslot, qt);
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
};
|
||||
if (wide) {
|
||||
eoff = eoff + (tyslicesize(): i32);
|
||||
} else {
|
||||
eoff = eoff + 8;
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
eoff = eoff + eslot;
|
||||
q = q.next;
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
@@ -33816,7 +33991,10 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
// C-t0: slot stride — must mirror the N_RETURN
|
||||
// over-cap SEND's buffer layout (cstage N_MASSIGN
|
||||
// twin strides tuple_eslot).
|
||||
foff += tupeslotn(tn);
|
||||
lb = lb.next;
|
||||
if (pt2 != nil) { pt2 = pt2.next; };
|
||||
};
|
||||
@@ -33836,8 +34014,7 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, tn)) {
|
||||
ssetotal = ssetotal + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(tn) / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -33864,18 +34041,18 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let off: i32 = 0;
|
||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||
// harec `_` (off==0): skip the store but CONSUME the cursor
|
||||
// slot so the next element stays aligned.
|
||||
if (off != 0) {
|
||||
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||
tupstore(c, gpcur, ssecur, off, eslot, tn);
|
||||
};
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -33938,13 +34115,13 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
for (lb != nil) {
|
||||
let tn: *node = lb.lhs;
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let esz: i32 = 8;
|
||||
let eti: *tinfo = nil;
|
||||
if (tn != nil) { eti = tn.type_: *tinfo; };
|
||||
if (eti != nil) { esz = eti.size: i32; };
|
||||
let bsz: i32 = 8;
|
||||
if (wide) { bsz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { bsz = eslot; };
|
||||
let off: i32 = localadd(c, lb.str, bsz, tn);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
@@ -33954,9 +34131,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), X0\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
} else { if (wide) {
|
||||
} else { if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < esz) {
|
||||
for (k < eslot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -33974,7 +34151,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("\t"); emitline(sop); emitline("\tAX, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
}; };
|
||||
if (wide) { foff += 24; } else { foff += 8; };
|
||||
foff += eslot;
|
||||
lb = lb.next;
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
@@ -33995,14 +34172,14 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let tn: *node = nil;
|
||||
if (pt2 != nil) { tn = pt2.lhs; };
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let esz: i32 = 8;
|
||||
if (pt2 != nil) {
|
||||
let eti: *tinfo = pt2.lhs.type_: *tinfo;
|
||||
if (eti != nil) { esz = eti.size: i32; };
|
||||
};
|
||||
let bsz: i32 = 8;
|
||||
if (wide) { bsz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { bsz = eslot; };
|
||||
let off: i32 = localadd(c, lb.str, bsz, tn);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
@@ -34013,9 +34190,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
} else {
|
||||
if (wide) {
|
||||
if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < esz) {
|
||||
for (k < eslot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scr + foff + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -34035,7 +34212,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
foff += eslot;
|
||||
lb = lb.next;
|
||||
if (pt2 != nil) { pt2 = pt2.next; };
|
||||
};
|
||||
@@ -34057,8 +34234,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, tn)) {
|
||||
ssetotal = ssetotal + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(tn) / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -34087,15 +34263,15 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let sz: i32 = 8;
|
||||
if (wide) { sz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { sz = eslot; };
|
||||
let off: i32 = localadd(c, l.str, sz, tn);
|
||||
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||
tupstore(c, gpcur, ssecur, off, eslot, tn);
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -34745,8 +34921,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
fidx += 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
let eb: i32 = tupebytes(wide);
|
||||
let eb: i32 = tupeslotn(et) / 8;
|
||||
if (idx + eb > 6) {
|
||||
let msg: str = "tuple param 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);
|
||||
@@ -34763,7 +34938,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
k += 1;
|
||||
};
|
||||
};
|
||||
eoff += slotsize(c, et);
|
||||
eoff += tupeslotn(et);
|
||||
te = te.next;
|
||||
};
|
||||
p = p.next;
|
||||
@@ -37456,15 +37631,12 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
|
||||
fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool = {
|
||||
if (tt == nil) { return false; };
|
||||
if (rhs == nil) {
|
||||
// #22: slot-sum via the accessor so the zero-fill matches
|
||||
// the checker size (cstage zero-emits u->size).
|
||||
let zsz: i32 = 0;
|
||||
let p0: *node = tt.list;
|
||||
for (p0 != nil) {
|
||||
let et0: *node = p0.lhs;
|
||||
if (isstrtype(c, et0) || isslicetype(c, et0)) {
|
||||
zsz += (tyslicesize(): i32);
|
||||
} else {
|
||||
zsz += 8;
|
||||
};
|
||||
zsz += tupeslotn(p0.lhs);
|
||||
p0 = p0.next;
|
||||
};
|
||||
emitline("DATAW ");
|
||||
@@ -37486,6 +37658,21 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool =
|
||||
let ev: *node = e;
|
||||
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||
if (ev == nil) { return false; };
|
||||
// #22a (rule 7): a tagged element slot has no static-init
|
||||
// shape (tag word + payload widening) — reject so the caller
|
||||
// loud-stops; pre-guard an int init would have emitted one
|
||||
// 8B word into the 16B+ box (silent layout skew). Mirrors
|
||||
// cstage emit_tuple_data.
|
||||
{
|
||||
let eti: *tinfo = nil;
|
||||
if (et != nil) { eti = et.type_: *tinfo; };
|
||||
for (eti != nil && eti.kind == tykind.TY_NAMED) {
|
||||
eti = eti.under;
|
||||
};
|
||||
if (eti != nil && eti.kind == tykind.TY_TAGGED) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
if (wide) {
|
||||
if (ev.kind != nkind.N_STRLIT) { return false; };
|
||||
@@ -37557,7 +37744,10 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool =
|
||||
};
|
||||
foff += (tyslicesize(): i32);
|
||||
} else {
|
||||
foff += 8;
|
||||
// #22: slot stride via the accessor (tagged is
|
||||
// rejected upstream; non-wide is 8 today — keeps the
|
||||
// stride on the accessor scale).
|
||||
foff += tupeslotn(et);
|
||||
};
|
||||
e = e.next;
|
||||
if (tp != nil) { tp = tp.next; };
|
||||
|
||||
@@ -2208,15 +2208,12 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
|
||||
fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool = {
|
||||
if (tt == nil) { return false; };
|
||||
if (rhs == nil) {
|
||||
// #22: slot-sum via the accessor so the zero-fill matches
|
||||
// the checker size (cstage zero-emits u->size).
|
||||
let zsz: i32 = 0;
|
||||
let p0: *node = tt.list;
|
||||
for (p0 != nil) {
|
||||
let et0: *node = p0.lhs;
|
||||
if (isstrtype(c, et0) || isslicetype(c, et0)) {
|
||||
zsz += (tyslicesize(): i32);
|
||||
} else {
|
||||
zsz += 8;
|
||||
};
|
||||
zsz += tupeslotn(p0.lhs);
|
||||
p0 = p0.next;
|
||||
};
|
||||
emitline("DATAW ");
|
||||
@@ -2238,6 +2235,21 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool =
|
||||
let ev: *node = e;
|
||||
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||
if (ev == nil) { return false; };
|
||||
// #22a (rule 7): a tagged element slot has no static-init
|
||||
// shape (tag word + payload widening) — reject so the caller
|
||||
// loud-stops; pre-guard an int init would have emitted one
|
||||
// 8B word into the 16B+ box (silent layout skew). Mirrors
|
||||
// cstage emit_tuple_data.
|
||||
{
|
||||
let eti: *tinfo = nil;
|
||||
if (et != nil) { eti = et.type_: *tinfo; };
|
||||
for (eti != nil && eti.kind == tykind.TY_NAMED) {
|
||||
eti = eti.under;
|
||||
};
|
||||
if (eti != nil && eti.kind == tykind.TY_TAGGED) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
if (wide) {
|
||||
if (ev.kind != nkind.N_STRLIT) { return false; };
|
||||
@@ -2309,7 +2321,10 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool =
|
||||
};
|
||||
foff += (tyslicesize(): i32);
|
||||
} else {
|
||||
foff += 8;
|
||||
// #22: slot stride via the accessor (tagged is
|
||||
// rejected upstream; non-wide is 8 today — keeps the
|
||||
// stride on the accessor scale).
|
||||
foff += tupeslotn(et);
|
||||
};
|
||||
e = e.next;
|
||||
if (tp != nil) { tp = tp.next; };
|
||||
|
||||
@@ -135,8 +135,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
fidx += 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
let eb: i32 = tupebytes(wide);
|
||||
let eb: i32 = tupeslotn(et) / 8;
|
||||
if (idx + eb > 6) {
|
||||
let msg: str = "tuple param 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);
|
||||
@@ -153,7 +152,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
k += 1;
|
||||
};
|
||||
};
|
||||
eoff += slotsize(c, et);
|
||||
eoff += tupeslotn(et);
|
||||
te = te.next;
|
||||
};
|
||||
p = p.next;
|
||||
|
||||
@@ -134,7 +134,22 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (vt == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) {
|
||||
// #22a: a STAMPED-CARRIER scrutinee (the #67 matchscrutt
|
||||
// shape — is/as on a tuple element t.N, a struct field, an
|
||||
// indexed element) is not an N_TTAGGED type-AST node; its
|
||||
// tagged type rides .type_. Resolve via the tinfo twin
|
||||
// (flatvariantidxt), the same core the widen-store uses —
|
||||
// cstage cg_tag_for_variant is type-based for every
|
||||
// scrutinee shape, so the AST-keyed -1 here was a silent
|
||||
// tag-0 clamp on wwstage (cs CMPQ $1 vs ww CMPQ $0).
|
||||
let sti: *tinfo = tagged.type_: *tinfo;
|
||||
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
|
||||
if (sti != nil && sti.kind == tykind.TY_TAGGED && vt.type_ != nil) {
|
||||
return flatvariantidxt(sti, vt.type_: *tinfo);
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
// `is []T` / `as []T` — slice-shape lookup routes through the
|
||||
// element-aware helper, which carries the loose first-slice-shape
|
||||
// fallback (cstage type_assignable stand-in) that flatvariantidx's
|
||||
@@ -2644,7 +2659,9 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp.lhs);
|
||||
// C-t0/#22: slot stride
|
||||
// (tupeslot accessor).
|
||||
foff += tupeslotn(tp.lhs);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
@@ -2678,6 +2695,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), X0\n");
|
||||
return;
|
||||
};
|
||||
// #22a: tagged element — load the box
|
||||
// into the tagged value regs (AX=tag,
|
||||
// DX/CX/R8=payload), the cursor the
|
||||
// is/as spill + match read. Byte-id
|
||||
// twin of cstage's N_DOT TY_TUPLE
|
||||
// tagged arm.
|
||||
if (istaggedtype(c, tpt)) {
|
||||
let eslot: i32 = tupeslotn(tpt);
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + k * 8): i64);
|
||||
emitline("(BP), ");
|
||||
emitline(tupreg(k));
|
||||
emitline("\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// C-t0: load at the element's NATURAL
|
||||
// width (narrow MOVL/MOVSXD/... at the
|
||||
// slot base), not the 8B slot width —
|
||||
@@ -2848,7 +2884,9 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
for (gi < gidx) {
|
||||
if (gtp == nil) { gi = gidx; }
|
||||
else {
|
||||
gfoff += slotsize(c, gtp.lhs);
|
||||
// C-t0/#22: slot stride
|
||||
// (tupeslot accessor).
|
||||
gfoff += tupeslotn(gtp.lhs);
|
||||
gtp = gtp.next;
|
||||
gi += 1;
|
||||
};
|
||||
@@ -6038,7 +6076,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp.lhs);
|
||||
// C-t0/#22: slot
|
||||
// stride (tupeslot).
|
||||
foff += tupeslotn(tp.lhs);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
@@ -6074,7 +6114,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
for (gi < gidx) {
|
||||
if (gtp == nil) { gi = gidx; }
|
||||
else {
|
||||
gfoff += slotsize(c, gtp.lhs);
|
||||
// C-t0/#22: slot
|
||||
// stride (tupeslot).
|
||||
gfoff += tupeslotn(gtp.lhs);
|
||||
gtp = gtp.next;
|
||||
gi += 1;
|
||||
};
|
||||
@@ -6527,10 +6569,14 @@ 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); };
|
||||
let eb: i32 = tupebytes(wide);
|
||||
let eb: i32 = 1;
|
||||
if (wide) { eb = (tyslicesize() / 8i64): i32; };
|
||||
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);
|
||||
|
||||
@@ -132,9 +132,36 @@ fn tupsse(i: i32) str = {
|
||||
return "X1";
|
||||
};
|
||||
|
||||
fn tupebytes(wide: bool) i32 = {
|
||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||
return 1;
|
||||
// tupeslot — THE tuple element-stride accessor (#22): the slot a tuple
|
||||
// element occupies, in bytes. slot = roundup8(size(elem)), 8B a FLOOR
|
||||
// not a ceiling (user-ratified 2026-06-04): str/slice carry their 24B
|
||||
// header, a tagged element its full tag+payload box ((str,str)=48B
|
||||
// predates this; tagged was the one truncated >8B kind — the #237
|
||||
// fieldslotsize precedent), narrow scalars pad UP to one 8B eightbyte.
|
||||
// Every tuple walk (cursor send/receive, t.N read, destructure, sret
|
||||
// classify, DATA emit) takes its stride and its eightbyte count
|
||||
// (eslot/8) from here — the per-site wide=(STR||SLICE)-else-8
|
||||
// predicates this absorbs were the #22 neighbor-slot/zeros miscompile.
|
||||
// Checker twin: check.ww tupleelemslot / check.c N_TTUPLE; cstage twin:
|
||||
// tuple_eslot (cmd/w6c/cgen.c).
|
||||
export fn tupeslot(ti: *tinfo) i32 = {
|
||||
let t: *tinfo = ti;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t == nil) { return 8; };
|
||||
if (t.kind == tykind.TY_VOID) { return 0; };
|
||||
// a literal tuple's stamped element can be untyped_str (size 0) —
|
||||
// it occupies the str header slot (the C-t2 type_isstr lesson).
|
||||
if (t.kind == tykind.TY_UNTYPED_STR) { return tyslicesize(): i32; };
|
||||
if (t.kind == tykind.TY_STR || t.kind == tykind.TY_SLICE ||
|
||||
t.kind == tykind.TY_TAGGED) {
|
||||
return ((t.size + 7u64) & ~7u64): i32;
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
|
||||
export fn tupeslotn(n: *node) i32 = {
|
||||
if (n == nil) { return 8; };
|
||||
return tupeslot(n.type_: *tinfo);
|
||||
};
|
||||
|
||||
// rettupleof — the N_TTUPLE return-type node of an N_CALL rhs (else nil).
|
||||
@@ -201,29 +228,24 @@ fn nodetuplearg(c: *cgen, a: *node) *node = {
|
||||
};
|
||||
|
||||
// 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
|
||||
// INTEGER cursor registers; a float rides the SSE cursor (X0,X1); a
|
||||
// scalar stores 1 INTEGER word. The caller owns the dual cursor
|
||||
// (validated + advanced). Byte-identical to the cstage tuple_store
|
||||
// (cmd/w6c/cgen.c).
|
||||
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node) void = {
|
||||
if (wide) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 0));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 1));
|
||||
emitline(", ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 2));
|
||||
emitline(", ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
// BP-relative slot at `off`. A >8B element (slice/str 3-word
|
||||
// {ptr,len,cap} header, ref/hare/rt/ensure.ha:4-8; tagged tag+payload
|
||||
// box, #22) stores its eslot/8 words from consecutive INTEGER cursor
|
||||
// registers; a float rides the SSE cursor (X0,X1); a scalar stores 1
|
||||
// INTEGER word. The caller owns the dual cursor (validated +
|
||||
// advanced). Byte-identical to the cstage tuple_store (cmd/w6c/cgen.c).
|
||||
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, eslot: i32, tn: *node) void = {
|
||||
if (eslot == 0) { return; }; // void element: the checker's 0-slot
|
||||
if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + k));
|
||||
emitline(", ");
|
||||
emitoff((off + k * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// #105 / #164 (#107): an f64/f32 element rides the SSE cursor reg
|
||||
@@ -277,6 +299,67 @@ fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node)
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
|
||||
// tuplitgpwords — INTEGER cursor words an N_TUPLE literal element
|
||||
// occupies. MUST mirror the literal push arms (tuplitpushelem) exactly
|
||||
// — the count drives the POP fill, so a count/push skew silently
|
||||
// shifts every later element (#22 class). A float rides the SSE row
|
||||
// (0 GP words); str/slice push their 3-word header; a tagged element
|
||||
// its tupeslot/8 box words; a void element pushes nothing (the
|
||||
// checker's 0-slot); a scalar 1. Mirror of cstage tuple_lit_gpwords.
|
||||
fn tuplitgpwords(c: *cgen, e: *node) i32 = {
|
||||
if (isfloattype(c, e)) { return 0; };
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
return (tyslicesize() / 8i64): i32;
|
||||
};
|
||||
let t: *tinfo = e.type_: *tinfo;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t != nil && (t.kind == tykind.TY_TAGGED ||
|
||||
t.kind == tykind.TY_VOID)) {
|
||||
return tupeslotn(e) / 8;
|
||||
};
|
||||
return 1;
|
||||
};
|
||||
|
||||
// tuplitpushelem — evaluate one N_TUPLE literal element and push its
|
||||
// INTEGER cursor words L->R (the pop side fills tupreg in reverse). A
|
||||
// tagged element loads its box words straight from its local slot —
|
||||
// cgexpr's ident load is word0-only for tagged (every tagged consumer
|
||||
// reads memory), so the cursor fill must too; any other tagged source
|
||||
// shape is loud (rule 7; the cursor-receive arm for call results is
|
||||
// with #22b's tupstore work, widening literals with #23). Mirror of
|
||||
// cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or
|
||||
// die together.
|
||||
fn tuplitpushelem(c: *cgen, e: *node) void = {
|
||||
let t: *tinfo = e.type_: *tinfo;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t != nil && t.kind == tykind.TY_TAGGED) {
|
||||
let eslot: i32 = tupeslotn(e);
|
||||
let eoff: i32 = 0;
|
||||
if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); };
|
||||
if (eoff == 0) {
|
||||
let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7, #22b/#23 follow-ups)\n";
|
||||
os.write(2, m22.ptr, m22.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((eoff + k * 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
cgexpr(c, e);
|
||||
if (t != nil && t.kind == tykind.TY_VOID) { return; };
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
};
|
||||
};
|
||||
|
||||
// cgtuplelittocursor — #241: materialise an N_TUPLE literal's elements into
|
||||
// the SysV register-return cursor (integer words L->R over tupreg AX/DX/CX/
|
||||
// R8, floats over tupsse X0/X1, a slice/str's {ptr,len,cap} over three
|
||||
@@ -296,8 +379,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
if (isfloattype(c, e)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tuplitgpwords(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -314,8 +396,8 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
e = tuple.list;
|
||||
for (e != nil) {
|
||||
let isflt: bool = isfloattype(c, e);
|
||||
cgexpr(c, e);
|
||||
if (isflt) {
|
||||
cgexpr(c, e);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
@@ -323,11 +405,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
sseidx = sseidx + 1;
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
};
|
||||
tuplitpushelem(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -374,8 +452,7 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
if (et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslot(el.type_) / 8;
|
||||
};
|
||||
el = el.tnext;
|
||||
};
|
||||
@@ -392,7 +469,7 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
let et: *tinfo = el.type_;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
let isflt: bool = et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64);
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
let eslot: i32 = tupeslot(el.type_);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
if (et.kind == tykind.TY_F32) { mov = "MOVSS"; };
|
||||
@@ -403,9 +480,9 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
emitline("\n");
|
||||
sse = sse + 1;
|
||||
foff += 8;
|
||||
} else { if (wide) {
|
||||
} else {
|
||||
let k: i32 = 0;
|
||||
for (k < 3) {
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff + k * 8): i64);
|
||||
emitline("(BP), ");
|
||||
@@ -413,17 +490,9 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
emitline("\n");
|
||||
k += 1;
|
||||
};
|
||||
gp += 3;
|
||||
foff += et.size: i32;
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff): i64);
|
||||
emitline("(BP), ");
|
||||
emitline(tupreg(gp));
|
||||
emitline("\n");
|
||||
gp += 1;
|
||||
foff += 8;
|
||||
}; };
|
||||
gp += eslot / 8;
|
||||
foff += eslot;
|
||||
};
|
||||
el = el.tnext;
|
||||
};
|
||||
};
|
||||
@@ -443,9 +512,8 @@ fn cgtaggedtuplepayloadshift(c: *cgen, tup: *tinfo) void = {
|
||||
let et: *tinfo = el.type_;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
let isflt: bool = et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64);
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
if (isflt || wide) {
|
||||
let msg: str = "tuple-in-union ? unwrap: float/slice/str payload element needs SysV per-eightbyte classification (see #243); only integer tuple payloads supported\n";
|
||||
if (isflt || tupeslot(el.type_) != 8) {
|
||||
let msg: str = "tuple-in-union ? unwrap: float/slice/str/tagged payload element needs SysV per-eightbyte classification (see #243); only integer tuple payloads supported\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
@@ -502,12 +570,28 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, e)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tuplitgpwords(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
|
||||
// #22a (rule 7): a tagged element's box can't
|
||||
// ride the scalar/wide store arms below — the
|
||||
// MEMORY-class tagged element store is #22b
|
||||
// (task #28); pre-guard it silently stored
|
||||
// word0 of the box.
|
||||
let gq: *node = nil;
|
||||
if (c.fnret != nil) { gq = c.fnret.list; };
|
||||
for (gq != nil) {
|
||||
let gqt: *tinfo = gq.lhs.type_: *tinfo;
|
||||
for (gqt != nil && gqt.kind == tykind.TY_NAMED) { gqt = gqt.under; };
|
||||
if (gqt != nil && gqt.kind == tykind.TY_TAGGED) {
|
||||
let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return unwired (rule 7)\n";
|
||||
os.write(2, m22b.ptr, m22b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
gq = gq.next;
|
||||
};
|
||||
// #10 Fold A: over-cap tuple returns via sret. The
|
||||
// prologue wired @sretarg (sretretsize agrees on the
|
||||
// caps — TUPLE_GPCAP/TUPLE_SSECAP, the shared SSoT),
|
||||
@@ -567,7 +651,12 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("\n");
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
// C-t0: the sret buffer is slot-laid like
|
||||
// every tuple home (checker size, t.N
|
||||
// reader, mlet receive agree); esz keeps
|
||||
// the store WIDTH natural. Mirrors cstage
|
||||
// cgen.c N_RETURN over-cap arm.
|
||||
if (wide) { foff += esz; } else { foff += 8; };
|
||||
we = we.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
@@ -588,8 +677,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
e = rhs.list;
|
||||
for (e != nil) {
|
||||
let isflt: bool = isfloattype(c, e);
|
||||
cgexpr(c, e);
|
||||
if (isflt) {
|
||||
cgexpr(c, e);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
@@ -599,11 +688,9 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
sseidx = sseidx + 1;
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n"); // .len
|
||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||
};
|
||||
// scalar=AX; slice/str=AX,BX,CX; tagged
|
||||
// box from its slot (tuplitpushelem)
|
||||
tuplitpushelem(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -2044,20 +2131,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
for (q != nil) {
|
||||
let qt: *node = q.lhs;
|
||||
let isflt: bool = isfloattype(c, qt);
|
||||
let wide: bool = isstrtype(c, qt)
|
||||
|| isslicetype(c, qt);
|
||||
let eslot: i32 = tupeslotn(qt);
|
||||
tupstore(c, gpcur, ssecur,
|
||||
off + eoff, wide, qt);
|
||||
off + eoff, eslot, qt);
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
};
|
||||
if (wide) {
|
||||
eoff = eoff + (tyslicesize(): i32);
|
||||
} else {
|
||||
eoff = eoff + 8;
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
eoff = eoff + eslot;
|
||||
q = q.next;
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
@@ -2898,7 +2980,10 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
// C-t0: slot stride — must mirror the N_RETURN
|
||||
// over-cap SEND's buffer layout (cstage N_MASSIGN
|
||||
// twin strides tuple_eslot).
|
||||
foff += tupeslotn(tn);
|
||||
lb = lb.next;
|
||||
if (pt2 != nil) { pt2 = pt2.next; };
|
||||
};
|
||||
@@ -2918,8 +3003,7 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, tn)) {
|
||||
ssetotal = ssetotal + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(tn) / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -2946,18 +3030,18 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let off: i32 = 0;
|
||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||
// harec `_` (off==0): skip the store but CONSUME the cursor
|
||||
// slot so the next element stays aligned.
|
||||
if (off != 0) {
|
||||
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||
tupstore(c, gpcur, ssecur, off, eslot, tn);
|
||||
};
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -3020,13 +3104,13 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
for (lb != nil) {
|
||||
let tn: *node = lb.lhs;
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let esz: i32 = 8;
|
||||
let eti: *tinfo = nil;
|
||||
if (tn != nil) { eti = tn.type_: *tinfo; };
|
||||
if (eti != nil) { esz = eti.size: i32; };
|
||||
let bsz: i32 = 8;
|
||||
if (wide) { bsz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { bsz = eslot; };
|
||||
let off: i32 = localadd(c, lb.str, bsz, tn);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
@@ -3036,9 +3120,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), X0\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
} else { if (wide) {
|
||||
} else { if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < esz) {
|
||||
for (k < eslot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -3056,7 +3140,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("\t"); emitline(sop); emitline("\tAX, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
}; };
|
||||
if (wide) { foff += 24; } else { foff += 8; };
|
||||
foff += eslot;
|
||||
lb = lb.next;
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
@@ -3077,14 +3161,14 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let tn: *node = nil;
|
||||
if (pt2 != nil) { tn = pt2.lhs; };
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let esz: i32 = 8;
|
||||
if (pt2 != nil) {
|
||||
let eti: *tinfo = pt2.lhs.type_: *tinfo;
|
||||
if (eti != nil) { esz = eti.size: i32; };
|
||||
};
|
||||
let bsz: i32 = 8;
|
||||
if (wide) { bsz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { bsz = eslot; };
|
||||
let off: i32 = localadd(c, lb.str, bsz, tn);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
@@ -3095,9 +3179,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
} else {
|
||||
if (wide) {
|
||||
if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < esz) {
|
||||
for (k < eslot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scr + foff + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -3117,7 +3201,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
foff += eslot;
|
||||
lb = lb.next;
|
||||
if (pt2 != nil) { pt2 = pt2.next; };
|
||||
};
|
||||
@@ -3139,8 +3223,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, tn)) {
|
||||
ssetotal = ssetotal + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(tn) / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -3169,15 +3252,15 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let sz: i32 = 8;
|
||||
if (wide) { sz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { sz = eslot; };
|
||||
let off: i32 = localadd(c, l.str, sz, tn);
|
||||
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||
tupstore(c, gpcur, ssecur, off, eslot, tn);
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
|
||||
@@ -288,6 +288,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #22a: t.N tuple-element read leaves the
|
||||
// same AX/DX/CX/R8 box cursor (this arc's
|
||||
// t.N box load) — without this gate the
|
||||
// widening scalar branch clamps the
|
||||
// unresolvable tag to 0 and the callee
|
||||
// reads variant 0. Stamped-carrier (#67)
|
||||
// twin of the N_INDEX arm above; cstage
|
||||
// needs no kind gate (its widen[i] `same`
|
||||
// check is type-keyed on args[i]->type).
|
||||
if (arg.kind == nkind.N_DOT) {
|
||||
if (istaggedtype(c, arg)) {
|
||||
if (slotsize(c, arg) == slotsize(c, ptype)) {
|
||||
aistagged = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!aistagged) {
|
||||
widensz = slotsize(c, ptype);
|
||||
let tagged: *node = resolvetagged(c, ptype);
|
||||
@@ -766,13 +782,17 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
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); };
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
if (isfloattype(c, et)) { sstot += 1; }
|
||||
else { gptot += tupebytes(wide); };
|
||||
if (wide) { tsz += (tyslicesize(): i32); }
|
||||
else { tsz += 8; };
|
||||
else { gptot += eslot / 8; };
|
||||
tsz += eslot;
|
||||
p = p.next;
|
||||
};
|
||||
// The producing cursor fill already satisfied #164's caps;
|
||||
@@ -793,11 +813,12 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
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);
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
tupstore(c, gpcur, ssecur, scr + eoff, eslot, et);
|
||||
if (isfloattype(c, et)) { ssecur += 1; }
|
||||
else { gpcur += tupebytes(wide); };
|
||||
if (wide) { eoff += (tyslicesize(): i32); }
|
||||
else { eoff += 8; };
|
||||
else { gpcur += eslot / 8; };
|
||||
eoff += eslot;
|
||||
p = p.next;
|
||||
};
|
||||
let w: i32 = tsz - 8;
|
||||
@@ -870,7 +891,13 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
// arg.type_ is the element tinfo (istaggedtype/slotsize read
|
||||
// .type_) — feed the N_INDEX node directly, dropping the
|
||||
// indexvaluetnode walk.
|
||||
if (arg.kind == nkind.N_INDEX) {
|
||||
// #22a: N_DOT rides the same arm — the t.N tuple-element box load
|
||||
// (this arc) fills the identical AX/DX/CX/R8 cursor. cstage's twin
|
||||
// is the generic node_istaggedarg push (cgen.c cgcall); wwstage
|
||||
// keeps the stamped-carrier kind gate (#67 pattern) — the
|
||||
// remaining kinds (deref/cast/unwrap) are word0-only reads today,
|
||||
// filed residual.
|
||||
if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) {
|
||||
if (istaggedtype(c, arg)) {
|
||||
let isz: i32 = slotsize(c, arg);
|
||||
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||
@@ -1676,8 +1703,7 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
||||
if (isfloattype(c, et)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(et) / 8;
|
||||
};
|
||||
pt = pt.next;
|
||||
};
|
||||
@@ -3453,6 +3479,20 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
let ttotal: i32 = 0;
|
||||
let ce: *node = tupsrc.list;
|
||||
for (ce != nil) {
|
||||
// #22a (rule 7): a tagged element's box can't
|
||||
// ride the scalar/wide store arms below —
|
||||
// pre-guard it silently stored word0. Nested
|
||||
// tagged-in-tuple-in-union packing is the
|
||||
// #242/#22b family. Mirrors cstage.
|
||||
let ceti: *tinfo = ce.type_: *tinfo;
|
||||
for (ceti != nil && ceti.kind == tykind.TY_NAMED) {
|
||||
ceti = ceti.under;
|
||||
};
|
||||
if (ceti != nil && ceti.kind == tykind.TY_TAGGED) {
|
||||
let m22: str = "cgwidentaggedstore: tagged element in a tuple-in-union payload unwired (see #242/#22b)\n";
|
||||
os.write(2, m22.ptr, m22.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (nodeisstr(c, ce) || nodeisslice(c, ce)) {
|
||||
ttotal += 24;
|
||||
} else { ttotal += 8; };
|
||||
|
||||
@@ -1600,6 +1600,13 @@ fn tupleelemslot(pt: *tinfo) u64 = {
|
||||
if (pk == tykind.TY_VOID) { return 0u64; };
|
||||
if (pk == tykind.TY_STR) { return t.size; };
|
||||
if (pk == tykind.TY_SLICE) { return t.size; };
|
||||
// #22 (user-ratified 2026-06-04): slot = roundup8(size(elem)) — 8B
|
||||
// is a FLOOR, not a ceiling. (str,str)=48B predates this; tagged
|
||||
// was the one truncated >8B kind (the #237 fieldslotsize-missing-
|
||||
// TY_TUPLE precedent: fieldslotsize below already carried this
|
||||
// arm). Cstage twin: check.c N_TTUPLE; cgen accessor: tuple_eslot
|
||||
// / tupeslot.
|
||||
if (pk == tykind.TY_TAGGED) { return (t.size + 7u64) & ~7u64; };
|
||||
if (pk == tykind.TY_PTR || pk == tykind.TY_FN ||
|
||||
pk == tykind.TY_CHAN || pk == tykind.TY_I64 ||
|
||||
pk == tykind.TY_U64 || pk == tykind.TY_INT ||
|
||||
|
||||
@@ -11864,6 +11864,13 @@ fn tupleelemslot(pt: *tinfo) u64 = {
|
||||
if (pk == tykind.TY_VOID) { return 0u64; };
|
||||
if (pk == tykind.TY_STR) { return t.size; };
|
||||
if (pk == tykind.TY_SLICE) { return t.size; };
|
||||
// #22 (user-ratified 2026-06-04): slot = roundup8(size(elem)) — 8B
|
||||
// is a FLOOR, not a ceiling. (str,str)=48B predates this; tagged
|
||||
// was the one truncated >8B kind (the #237 fieldslotsize-missing-
|
||||
// TY_TUPLE precedent: fieldslotsize below already carried this
|
||||
// arm). Cstage twin: check.c N_TTUPLE; cgen accessor: tuple_eslot
|
||||
// / tupeslot.
|
||||
if (pk == tykind.TY_TAGGED) { return (t.size + 7u64) & ~7u64; };
|
||||
if (pk == tykind.TY_PTR || pk == tykind.TY_FN ||
|
||||
pk == tykind.TY_CHAN || pk == tykind.TY_I64 ||
|
||||
pk == tykind.TY_U64 || pk == tykind.TY_INT ||
|
||||
@@ -16273,6 +16280,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #22a: t.N tuple-element read leaves the
|
||||
// same AX/DX/CX/R8 box cursor (this arc's
|
||||
// t.N box load) — without this gate the
|
||||
// widening scalar branch clamps the
|
||||
// unresolvable tag to 0 and the callee
|
||||
// reads variant 0. Stamped-carrier (#67)
|
||||
// twin of the N_INDEX arm above; cstage
|
||||
// needs no kind gate (its widen[i] `same`
|
||||
// check is type-keyed on args[i]->type).
|
||||
if (arg.kind == nkind.N_DOT) {
|
||||
if (istaggedtype(c, arg)) {
|
||||
if (slotsize(c, arg) == slotsize(c, ptype)) {
|
||||
aistagged = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!aistagged) {
|
||||
widensz = slotsize(c, ptype);
|
||||
let tagged: *node = resolvetagged(c, ptype);
|
||||
@@ -16751,13 +16774,17 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
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); };
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
if (isfloattype(c, et)) { sstot += 1; }
|
||||
else { gptot += tupebytes(wide); };
|
||||
if (wide) { tsz += (tyslicesize(): i32); }
|
||||
else { tsz += 8; };
|
||||
else { gptot += eslot / 8; };
|
||||
tsz += eslot;
|
||||
p = p.next;
|
||||
};
|
||||
// The producing cursor fill already satisfied #164's caps;
|
||||
@@ -16778,11 +16805,12 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
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);
|
||||
let eslot: i32 = 8;
|
||||
if (wide) { eslot = tyslicesize(): i32; };
|
||||
tupstore(c, gpcur, ssecur, scr + eoff, eslot, et);
|
||||
if (isfloattype(c, et)) { ssecur += 1; }
|
||||
else { gpcur += tupebytes(wide); };
|
||||
if (wide) { eoff += (tyslicesize(): i32); }
|
||||
else { eoff += 8; };
|
||||
else { gpcur += eslot / 8; };
|
||||
eoff += eslot;
|
||||
p = p.next;
|
||||
};
|
||||
let w: i32 = tsz - 8;
|
||||
@@ -16855,7 +16883,13 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
|
||||
// arg.type_ is the element tinfo (istaggedtype/slotsize read
|
||||
// .type_) — feed the N_INDEX node directly, dropping the
|
||||
// indexvaluetnode walk.
|
||||
if (arg.kind == nkind.N_INDEX) {
|
||||
// #22a: N_DOT rides the same arm — the t.N tuple-element box load
|
||||
// (this arc) fills the identical AX/DX/CX/R8 cursor. cstage's twin
|
||||
// is the generic node_istaggedarg push (cgen.c cgcall); wwstage
|
||||
// keeps the stamped-carrier kind gate (#67 pattern) — the
|
||||
// remaining kinds (deref/cast/unwrap) are word0-only reads today,
|
||||
// filed residual.
|
||||
if (arg.kind == nkind.N_INDEX || arg.kind == nkind.N_DOT) {
|
||||
if (istaggedtype(c, arg)) {
|
||||
let isz: i32 = slotsize(c, arg);
|
||||
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||
@@ -17661,8 +17695,7 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
||||
if (isfloattype(c, et)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(et) / 8;
|
||||
};
|
||||
pt = pt.next;
|
||||
};
|
||||
@@ -19438,6 +19471,20 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
let ttotal: i32 = 0;
|
||||
let ce: *node = tupsrc.list;
|
||||
for (ce != nil) {
|
||||
// #22a (rule 7): a tagged element's box can't
|
||||
// ride the scalar/wide store arms below —
|
||||
// pre-guard it silently stored word0. Nested
|
||||
// tagged-in-tuple-in-union packing is the
|
||||
// #242/#22b family. Mirrors cstage.
|
||||
let ceti: *tinfo = ce.type_: *tinfo;
|
||||
for (ceti != nil && ceti.kind == tykind.TY_NAMED) {
|
||||
ceti = ceti.under;
|
||||
};
|
||||
if (ceti != nil && ceti.kind == tykind.TY_TAGGED) {
|
||||
let m22: str = "cgwidentaggedstore: tagged element in a tuple-in-union payload unwired (see #242/#22b)\n";
|
||||
os.write(2, m22.ptr, m22.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (nodeisstr(c, ce) || nodeisslice(c, ce)) {
|
||||
ttotal += 24;
|
||||
} else { ttotal += 8; };
|
||||
@@ -20570,7 +20617,22 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (vt == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) {
|
||||
// #22a: a STAMPED-CARRIER scrutinee (the #67 matchscrutt
|
||||
// shape — is/as on a tuple element t.N, a struct field, an
|
||||
// indexed element) is not an N_TTAGGED type-AST node; its
|
||||
// tagged type rides .type_. Resolve via the tinfo twin
|
||||
// (flatvariantidxt), the same core the widen-store uses —
|
||||
// cstage cg_tag_for_variant is type-based for every
|
||||
// scrutinee shape, so the AST-keyed -1 here was a silent
|
||||
// tag-0 clamp on wwstage (cs CMPQ $1 vs ww CMPQ $0).
|
||||
let sti: *tinfo = tagged.type_: *tinfo;
|
||||
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
|
||||
if (sti != nil && sti.kind == tykind.TY_TAGGED && vt.type_ != nil) {
|
||||
return flatvariantidxt(sti, vt.type_: *tinfo);
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
// `is []T` / `as []T` — slice-shape lookup routes through the
|
||||
// element-aware helper, which carries the loose first-slice-shape
|
||||
// fallback (cstage type_assignable stand-in) that flatvariantidx's
|
||||
@@ -23080,7 +23142,9 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp.lhs);
|
||||
// C-t0/#22: slot stride
|
||||
// (tupeslot accessor).
|
||||
foff += tupeslotn(tp.lhs);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
@@ -23114,6 +23178,25 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), X0\n");
|
||||
return;
|
||||
};
|
||||
// #22a: tagged element — load the box
|
||||
// into the tagged value regs (AX=tag,
|
||||
// DX/CX/R8=payload), the cursor the
|
||||
// is/as spill + match read. Byte-id
|
||||
// twin of cstage's N_DOT TY_TUPLE
|
||||
// tagged arm.
|
||||
if (istaggedtype(c, tpt)) {
|
||||
let eslot: i32 = tupeslotn(tpt);
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + k * 8): i64);
|
||||
emitline("(BP), ");
|
||||
emitline(tupreg(k));
|
||||
emitline("\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// C-t0: load at the element's NATURAL
|
||||
// width (narrow MOVL/MOVSXD/... at the
|
||||
// slot base), not the 8B slot width —
|
||||
@@ -23284,7 +23367,9 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
for (gi < gidx) {
|
||||
if (gtp == nil) { gi = gidx; }
|
||||
else {
|
||||
gfoff += slotsize(c, gtp.lhs);
|
||||
// C-t0/#22: slot stride
|
||||
// (tupeslot accessor).
|
||||
gfoff += tupeslotn(gtp.lhs);
|
||||
gtp = gtp.next;
|
||||
gi += 1;
|
||||
};
|
||||
@@ -26474,7 +26559,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp.lhs);
|
||||
// C-t0/#22: slot
|
||||
// stride (tupeslot).
|
||||
foff += tupeslotn(tp.lhs);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
@@ -26510,7 +26597,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
for (gi < gidx) {
|
||||
if (gtp == nil) { gi = gidx; }
|
||||
else {
|
||||
gfoff += slotsize(c, gtp.lhs);
|
||||
// C-t0/#22: slot
|
||||
// stride (tupeslot).
|
||||
gfoff += tupeslotn(gtp.lhs);
|
||||
gtp = gtp.next;
|
||||
gi += 1;
|
||||
};
|
||||
@@ -26963,10 +27052,14 @@ 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); };
|
||||
let eb: i32 = tupebytes(wide);
|
||||
let eb: i32 = 1;
|
||||
if (wide) { eb = (tyslicesize() / 8i64): i32; };
|
||||
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);
|
||||
@@ -31050,9 +31143,36 @@ fn tupsse(i: i32) str = {
|
||||
return "X1";
|
||||
};
|
||||
|
||||
fn tupebytes(wide: bool) i32 = {
|
||||
if (wide) { return (tyslicesize() / 8i64): i32; };
|
||||
return 1;
|
||||
// tupeslot — THE tuple element-stride accessor (#22): the slot a tuple
|
||||
// element occupies, in bytes. slot = roundup8(size(elem)), 8B a FLOOR
|
||||
// not a ceiling (user-ratified 2026-06-04): str/slice carry their 24B
|
||||
// header, a tagged element its full tag+payload box ((str,str)=48B
|
||||
// predates this; tagged was the one truncated >8B kind — the #237
|
||||
// fieldslotsize precedent), narrow scalars pad UP to one 8B eightbyte.
|
||||
// Every tuple walk (cursor send/receive, t.N read, destructure, sret
|
||||
// classify, DATA emit) takes its stride and its eightbyte count
|
||||
// (eslot/8) from here — the per-site wide=(STR||SLICE)-else-8
|
||||
// predicates this absorbs were the #22 neighbor-slot/zeros miscompile.
|
||||
// Checker twin: check.ww tupleelemslot / check.c N_TTUPLE; cstage twin:
|
||||
// tuple_eslot (cmd/w6c/cgen.c).
|
||||
export fn tupeslot(ti: *tinfo) i32 = {
|
||||
let t: *tinfo = ti;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t == nil) { return 8; };
|
||||
if (t.kind == tykind.TY_VOID) { return 0; };
|
||||
// a literal tuple's stamped element can be untyped_str (size 0) —
|
||||
// it occupies the str header slot (the C-t2 type_isstr lesson).
|
||||
if (t.kind == tykind.TY_UNTYPED_STR) { return tyslicesize(): i32; };
|
||||
if (t.kind == tykind.TY_STR || t.kind == tykind.TY_SLICE ||
|
||||
t.kind == tykind.TY_TAGGED) {
|
||||
return ((t.size + 7u64) & ~7u64): i32;
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
|
||||
export fn tupeslotn(n: *node) i32 = {
|
||||
if (n == nil) { return 8; };
|
||||
return tupeslot(n.type_: *tinfo);
|
||||
};
|
||||
|
||||
// rettupleof — the N_TTUPLE return-type node of an N_CALL rhs (else nil).
|
||||
@@ -31119,29 +31239,24 @@ fn nodetuplearg(c: *cgen, a: *node) *node = {
|
||||
};
|
||||
|
||||
// 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
|
||||
// INTEGER cursor registers; a float rides the SSE cursor (X0,X1); a
|
||||
// scalar stores 1 INTEGER word. The caller owns the dual cursor
|
||||
// (validated + advanced). Byte-identical to the cstage tuple_store
|
||||
// (cmd/w6c/cgen.c).
|
||||
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node) void = {
|
||||
if (wide) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 0));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 1));
|
||||
emitline(", ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + 2));
|
||||
emitline(", ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
// BP-relative slot at `off`. A >8B element (slice/str 3-word
|
||||
// {ptr,len,cap} header, ref/hare/rt/ensure.ha:4-8; tagged tag+payload
|
||||
// box, #22) stores its eslot/8 words from consecutive INTEGER cursor
|
||||
// registers; a float rides the SSE cursor (X0,X1); a scalar stores 1
|
||||
// INTEGER word. The caller owns the dual cursor (validated +
|
||||
// advanced). Byte-identical to the cstage tuple_store (cmd/w6c/cgen.c).
|
||||
fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, eslot: i32, tn: *node) void = {
|
||||
if (eslot == 0) { return; }; // void element: the checker's 0-slot
|
||||
if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(gpcur + k));
|
||||
emitline(", ");
|
||||
emitoff((off + k * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// #105 / #164 (#107): an f64/f32 element rides the SSE cursor reg
|
||||
@@ -31195,6 +31310,67 @@ fn tupstore(c: *cgen, gpcur: i32, ssecur: i32, off: i32, wide: bool, tn: *node)
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
|
||||
// tuplitgpwords — INTEGER cursor words an N_TUPLE literal element
|
||||
// occupies. MUST mirror the literal push arms (tuplitpushelem) exactly
|
||||
// — the count drives the POP fill, so a count/push skew silently
|
||||
// shifts every later element (#22 class). A float rides the SSE row
|
||||
// (0 GP words); str/slice push their 3-word header; a tagged element
|
||||
// its tupeslot/8 box words; a void element pushes nothing (the
|
||||
// checker's 0-slot); a scalar 1. Mirror of cstage tuple_lit_gpwords.
|
||||
fn tuplitgpwords(c: *cgen, e: *node) i32 = {
|
||||
if (isfloattype(c, e)) { return 0; };
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
return (tyslicesize() / 8i64): i32;
|
||||
};
|
||||
let t: *tinfo = e.type_: *tinfo;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t != nil && (t.kind == tykind.TY_TAGGED ||
|
||||
t.kind == tykind.TY_VOID)) {
|
||||
return tupeslotn(e) / 8;
|
||||
};
|
||||
return 1;
|
||||
};
|
||||
|
||||
// tuplitpushelem — evaluate one N_TUPLE literal element and push its
|
||||
// INTEGER cursor words L->R (the pop side fills tupreg in reverse). A
|
||||
// tagged element loads its box words straight from its local slot —
|
||||
// cgexpr's ident load is word0-only for tagged (every tagged consumer
|
||||
// reads memory), so the cursor fill must too; any other tagged source
|
||||
// shape is loud (rule 7; the cursor-receive arm for call results is
|
||||
// with #22b's tupstore work, widening literals with #23). Mirror of
|
||||
// cstage tuple_lit_push_elem — count (tuplitgpwords) and push live or
|
||||
// die together.
|
||||
fn tuplitpushelem(c: *cgen, e: *node) void = {
|
||||
let t: *tinfo = e.type_: *tinfo;
|
||||
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
|
||||
if (t != nil && t.kind == tykind.TY_TAGGED) {
|
||||
let eslot: i32 = tupeslotn(e);
|
||||
let eoff: i32 = 0;
|
||||
if (e.kind == nkind.N_IDENT) { eoff = localfind(c, e.str); };
|
||||
if (eoff == 0) {
|
||||
let m22: str = "#22a: tagged tuple element from a non-local source shape unwired (ident locals only; rule 7, #22b/#23 follow-ups)\n";
|
||||
os.write(2, m22.ptr, m22.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let k: i32 = 0;
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((eoff + k * 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
k += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
cgexpr(c, e);
|
||||
if (t != nil && t.kind == tykind.TY_VOID) { return; };
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
};
|
||||
};
|
||||
|
||||
// cgtuplelittocursor — #241: materialise an N_TUPLE literal's elements into
|
||||
// the SysV register-return cursor (integer words L->R over tupreg AX/DX/CX/
|
||||
// R8, floats over tupsse X0/X1, a slice/str's {ptr,len,cap} over three
|
||||
@@ -31214,8 +31390,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
if (isfloattype(c, e)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tuplitgpwords(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -31232,8 +31407,8 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
e = tuple.list;
|
||||
for (e != nil) {
|
||||
let isflt: bool = isfloattype(c, e);
|
||||
cgexpr(c, e);
|
||||
if (isflt) {
|
||||
cgexpr(c, e);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
@@ -31241,11 +31416,7 @@ fn cgtuplelittocursor(c: *cgen, tuple: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
sseidx = sseidx + 1;
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
};
|
||||
tuplitpushelem(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -31292,8 +31463,7 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
if (et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslot(el.type_) / 8;
|
||||
};
|
||||
el = el.tnext;
|
||||
};
|
||||
@@ -31310,7 +31480,7 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
let et: *tinfo = el.type_;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
let isflt: bool = et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64);
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
let eslot: i32 = tupeslot(el.type_);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
if (et.kind == tykind.TY_F32) { mov = "MOVSS"; };
|
||||
@@ -31321,9 +31491,9 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
emitline("\n");
|
||||
sse = sse + 1;
|
||||
foff += 8;
|
||||
} else { if (wide) {
|
||||
} else {
|
||||
let k: i32 = 0;
|
||||
for (k < 3) {
|
||||
for (k < eslot / 8) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff + k * 8): i64);
|
||||
emitline("(BP), ");
|
||||
@@ -31331,17 +31501,9 @@ fn cgtupleslottocursor(c: *cgen, srcoff: i32, tu: *tinfo) void = {
|
||||
emitline("\n");
|
||||
k += 1;
|
||||
};
|
||||
gp += 3;
|
||||
foff += et.size: i32;
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff): i64);
|
||||
emitline("(BP), ");
|
||||
emitline(tupreg(gp));
|
||||
emitline("\n");
|
||||
gp += 1;
|
||||
foff += 8;
|
||||
}; };
|
||||
gp += eslot / 8;
|
||||
foff += eslot;
|
||||
};
|
||||
el = el.tnext;
|
||||
};
|
||||
};
|
||||
@@ -31361,9 +31523,8 @@ fn cgtaggedtuplepayloadshift(c: *cgen, tup: *tinfo) void = {
|
||||
let et: *tinfo = el.type_;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
let isflt: bool = et != nil && (et.kind == tykind.TY_F32 || et.kind == tykind.TY_F64);
|
||||
let wide: bool = et != nil && (et.kind == tykind.TY_SLICE || et.kind == tykind.TY_STR);
|
||||
if (isflt || wide) {
|
||||
let msg: str = "tuple-in-union ? unwrap: float/slice/str payload element needs SysV per-eightbyte classification (see #243); only integer tuple payloads supported\n";
|
||||
if (isflt || tupeslot(el.type_) != 8) {
|
||||
let msg: str = "tuple-in-union ? unwrap: float/slice/str/tagged payload element needs SysV per-eightbyte classification (see #243); only integer tuple payloads supported\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
@@ -31420,12 +31581,28 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, e)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = nodeisstr(c, e) || nodeisslice(c, e);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tuplitgpwords(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
|
||||
// #22a (rule 7): a tagged element's box can't
|
||||
// ride the scalar/wide store arms below — the
|
||||
// MEMORY-class tagged element store is #22b
|
||||
// (task #28); pre-guard it silently stored
|
||||
// word0 of the box.
|
||||
let gq: *node = nil;
|
||||
if (c.fnret != nil) { gq = c.fnret.list; };
|
||||
for (gq != nil) {
|
||||
let gqt: *tinfo = gq.lhs.type_: *tinfo;
|
||||
for (gqt != nil && gqt.kind == tykind.TY_NAMED) { gqt = gqt.under; };
|
||||
if (gqt != nil && gqt.kind == tykind.TY_TAGGED) {
|
||||
let m22b: str = "#22b: tagged element in an over-cap (sret) tuple return unwired (rule 7)\n";
|
||||
os.write(2, m22b.ptr, m22b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
gq = gq.next;
|
||||
};
|
||||
// #10 Fold A: over-cap tuple returns via sret. The
|
||||
// prologue wired @sretarg (sretretsize agrees on the
|
||||
// caps — TUPLE_GPCAP/TUPLE_SSECAP, the shared SSoT),
|
||||
@@ -31485,7 +31662,12 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("\n");
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
// C-t0: the sret buffer is slot-laid like
|
||||
// every tuple home (checker size, t.N
|
||||
// reader, mlet receive agree); esz keeps
|
||||
// the store WIDTH natural. Mirrors cstage
|
||||
// cgen.c N_RETURN over-cap arm.
|
||||
if (wide) { foff += esz; } else { foff += 8; };
|
||||
we = we.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
@@ -31506,8 +31688,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
e = rhs.list;
|
||||
for (e != nil) {
|
||||
let isflt: bool = isfloattype(c, e);
|
||||
cgexpr(c, e);
|
||||
if (isflt) {
|
||||
cgexpr(c, e);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, e)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
@@ -31517,11 +31699,9 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
sseidx = sseidx + 1;
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n"); // scalar / .ptr
|
||||
if (nodeisstr(c, e) || nodeisslice(c, e)) {
|
||||
emitline("\tPUSHQ\tBX\n"); // .len
|
||||
emitline("\tPUSHQ\tCX\n"); // .cap
|
||||
};
|
||||
// scalar=AX; slice/str=AX,BX,CX; tagged
|
||||
// box from its slot (tuplitpushelem)
|
||||
tuplitpushelem(c, e);
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
@@ -32962,20 +33142,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
for (q != nil) {
|
||||
let qt: *node = q.lhs;
|
||||
let isflt: bool = isfloattype(c, qt);
|
||||
let wide: bool = isstrtype(c, qt)
|
||||
|| isslicetype(c, qt);
|
||||
let eslot: i32 = tupeslotn(qt);
|
||||
tupstore(c, gpcur, ssecur,
|
||||
off + eoff, wide, qt);
|
||||
off + eoff, eslot, qt);
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
};
|
||||
if (wide) {
|
||||
eoff = eoff + (tyslicesize(): i32);
|
||||
} else {
|
||||
eoff = eoff + 8;
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
eoff = eoff + eslot;
|
||||
q = q.next;
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
@@ -33816,7 +33991,10 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
// C-t0: slot stride — must mirror the N_RETURN
|
||||
// over-cap SEND's buffer layout (cstage N_MASSIGN
|
||||
// twin strides tuple_eslot).
|
||||
foff += tupeslotn(tn);
|
||||
lb = lb.next;
|
||||
if (pt2 != nil) { pt2 = pt2.next; };
|
||||
};
|
||||
@@ -33836,8 +34014,7 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, tn)) {
|
||||
ssetotal = ssetotal + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(tn) / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -33864,18 +34041,18 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
let tn: *node = nil;
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let off: i32 = 0;
|
||||
if (l.kind == nkind.N_IDENT) { off = localfind(c, l.str); };
|
||||
// harec `_` (off==0): skip the store but CONSUME the cursor
|
||||
// slot so the next element stays aligned.
|
||||
if (off != 0) {
|
||||
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||
tupstore(c, gpcur, ssecur, off, eslot, tn);
|
||||
};
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -33938,13 +34115,13 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
for (lb != nil) {
|
||||
let tn: *node = lb.lhs;
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let esz: i32 = 8;
|
||||
let eti: *tinfo = nil;
|
||||
if (tn != nil) { eti = tn.type_: *tinfo; };
|
||||
if (eti != nil) { esz = eti.size: i32; };
|
||||
let bsz: i32 = 8;
|
||||
if (wide) { bsz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { bsz = eslot; };
|
||||
let off: i32 = localadd(c, lb.str, bsz, tn);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
@@ -33954,9 +34131,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), X0\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
} else { if (wide) {
|
||||
} else { if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < esz) {
|
||||
for (k < eslot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((srcoff + foff + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -33974,7 +34151,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("\t"); emitline(sop); emitline("\tAX, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
}; };
|
||||
if (wide) { foff += 24; } else { foff += 8; };
|
||||
foff += eslot;
|
||||
lb = lb.next;
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
@@ -33995,14 +34172,14 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let tn: *node = nil;
|
||||
if (pt2 != nil) { tn = pt2.lhs; };
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let esz: i32 = 8;
|
||||
if (pt2 != nil) {
|
||||
let eti: *tinfo = pt2.lhs.type_: *tinfo;
|
||||
if (eti != nil) { esz = eti.size: i32; };
|
||||
};
|
||||
let bsz: i32 = 8;
|
||||
if (wide) { bsz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { bsz = eslot; };
|
||||
let off: i32 = localadd(c, lb.str, bsz, tn);
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
@@ -34013,9 +34190,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, ");
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
} else {
|
||||
if (wide) {
|
||||
if (eslot > 8) {
|
||||
let k: i32 = 0;
|
||||
for (k < esz) {
|
||||
for (k < eslot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scr + foff + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -34035,7 +34212,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitoff(off: i64); emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
foff += eslot;
|
||||
lb = lb.next;
|
||||
if (pt2 != nil) { pt2 = pt2.next; };
|
||||
};
|
||||
@@ -34057,8 +34234,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
if (isfloattype(c, tn)) {
|
||||
ssetotal = ssetotal + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
gptotal = gptotal + tupeslotn(tn) / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -34087,15 +34263,15 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
if (pt != nil) { tn = pt.lhs; };
|
||||
};
|
||||
let isflt: bool = isfloattype(c, tn);
|
||||
let wide: bool = isstrtype(c, tn) || isslicetype(c, tn);
|
||||
let eslot: i32 = tupeslotn(tn);
|
||||
let sz: i32 = 8;
|
||||
if (wide) { sz = tyslicesize(): i32; };
|
||||
if (eslot > 8) { sz = eslot; };
|
||||
let off: i32 = localadd(c, l.str, sz, tn);
|
||||
tupstore(c, gpcur, ssecur, off, wide, tn);
|
||||
tupstore(c, gpcur, ssecur, off, eslot, tn);
|
||||
if (isflt) {
|
||||
ssecur = ssecur + 1;
|
||||
} else {
|
||||
gpcur = gpcur + tupebytes(wide);
|
||||
gpcur = gpcur + eslot / 8;
|
||||
};
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
@@ -34745,8 +34921,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
fidx += 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
let eb: i32 = tupebytes(wide);
|
||||
let eb: i32 = tupeslotn(et) / 8;
|
||||
if (idx + eb > 6) {
|
||||
let msg: str = "tuple param 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);
|
||||
@@ -34763,7 +34938,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
k += 1;
|
||||
};
|
||||
};
|
||||
eoff += slotsize(c, et);
|
||||
eoff += tupeslotn(et);
|
||||
te = te.next;
|
||||
};
|
||||
p = p.next;
|
||||
@@ -37456,15 +37631,12 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
|
||||
fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool = {
|
||||
if (tt == nil) { return false; };
|
||||
if (rhs == nil) {
|
||||
// #22: slot-sum via the accessor so the zero-fill matches
|
||||
// the checker size (cstage zero-emits u->size).
|
||||
let zsz: i32 = 0;
|
||||
let p0: *node = tt.list;
|
||||
for (p0 != nil) {
|
||||
let et0: *node = p0.lhs;
|
||||
if (isstrtype(c, et0) || isslicetype(c, et0)) {
|
||||
zsz += (tyslicesize(): i32);
|
||||
} else {
|
||||
zsz += 8;
|
||||
};
|
||||
zsz += tupeslotn(p0.lhs);
|
||||
p0 = p0.next;
|
||||
};
|
||||
emitline("DATAW ");
|
||||
@@ -37486,6 +37658,21 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool =
|
||||
let ev: *node = e;
|
||||
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||
if (ev == nil) { return false; };
|
||||
// #22a (rule 7): a tagged element slot has no static-init
|
||||
// shape (tag word + payload widening) — reject so the caller
|
||||
// loud-stops; pre-guard an int init would have emitted one
|
||||
// 8B word into the 16B+ box (silent layout skew). Mirrors
|
||||
// cstage emit_tuple_data.
|
||||
{
|
||||
let eti: *tinfo = nil;
|
||||
if (et != nil) { eti = et.type_: *tinfo; };
|
||||
for (eti != nil && eti.kind == tykind.TY_NAMED) {
|
||||
eti = eti.under;
|
||||
};
|
||||
if (eti != nil && eti.kind == tykind.TY_TAGGED) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
if (wide) {
|
||||
if (ev.kind != nkind.N_STRLIT) { return false; };
|
||||
@@ -37557,7 +37744,10 @@ fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool =
|
||||
};
|
||||
foff += (tyslicesize(): i32);
|
||||
} else {
|
||||
foff += 8;
|
||||
// #22: slot stride via the accessor (tagged is
|
||||
// rejected upstream; non-wide is 8 today — keeps the
|
||||
// stride on the accessor scale).
|
||||
foff += tupeslotn(et);
|
||||
};
|
||||
e = e.next;
|
||||
if (tp != nil) { tp = tp.next; };
|
||||
|
||||
@@ -209,6 +209,285 @@ static const struct row rows[] = {
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
|
||||
/* ---- #22a: TAGGED elements in tuples. slot = roundup8(size(elem))
|
||||
* — 8B is a FLOOR, not a ceiling (user-ratified 2026-06-04).
|
||||
* Pre-#22a the checker truncated a tagged element to one 8B slot
|
||||
* (cstage SIZE 16 vs wwstage astsize 24, cs≠ww) and every cgen
|
||||
* transport walk strode wide=(STR||SLICE)-else-8 — cstage read the
|
||||
* NEIGHBOR slot, wwstage read ZEROS (both-wrong-differently,
|
||||
* byte-cmp-blind). tuple_eslot/tupeslot is now the one stride
|
||||
* accessor. In-cap shapes work end-to-end; over-cap (>4 GP
|
||||
* eightbytes — the fold-5b 3-elem shape) stays LOUD until #22b.
|
||||
* Tagged inits use the CAST form (`5: size`): the bare untyped-int
|
||||
* widen-store mis-tag is a separate pre-existing bug (task #33). */
|
||||
{ "t22_sizefold_tagged",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (size(((void | size), size)) != 24) { return 1; };\n"
|
||||
" if (size((size, (void | size))) != 24) { return 2; };\n"
|
||||
" if (size(((void | size), (void | size), size)) != 40) { return 3; };\n"
|
||||
" if (align(((void | size), size)) != 8) { return 4; };\n"
|
||||
" if (size((u64, void)) != 8) { return 5; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t22_roundtrip",
|
||||
"package main;\n"
|
||||
"fn mk() ((void | size), size) = {\n"
|
||||
" let mn: (void | size) = 5: size;\n"
|
||||
" return (mn, 4);\n"
|
||||
"};\n"
|
||||
"fn mk2() (size, (void | size)) = {\n"
|
||||
" let mx: (void | size) = 7: size;\n"
|
||||
" return (9, mx);\n"
|
||||
"};\n"
|
||||
"fn mkv() ((void | size), size) = {\n"
|
||||
" let mn: (void | size) = void;\n"
|
||||
" return (mn, 3);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t = mk();\n"
|
||||
" if (!(t.0 is size)) { return 1; };\n"
|
||||
" if (t.0 as size != 5) { return 2; };\n"
|
||||
" if (t.1 != 4) { return 3; };\n"
|
||||
" let u = mk2();\n"
|
||||
" if (u.0 != 9) { return 4; };\n"
|
||||
" if (!(u.1 is size)) { return 5; };\n"
|
||||
" if (u.1 as size != 7) { return 6; };\n"
|
||||
" let v = mkv();\n"
|
||||
" if (!(v.0 is void)) { return 7; };\n"
|
||||
" if (v.1 != 3) { return 8; };\n"
|
||||
" let (a, b) = mk();\n"
|
||||
" if (!(a is size)) { return 9; };\n"
|
||||
" if (a as size != 5) { return 10; };\n"
|
||||
" if (b != 4) { return 11; };\n"
|
||||
" let mn: (void | size) = 6: size;\n"
|
||||
" let w: ((void | size), size) = (mn, 2);\n"
|
||||
" if (!(w.0 is size)) { return 12; };\n"
|
||||
" if (w.0 as size != 6) { return 13; };\n"
|
||||
" if (w.1 != 2) { return 14; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* mixed SSE/GP with a tagged box: the f64 rides X0 on the SSE
|
||||
* counter while the tagged element takes 2 consecutive GP
|
||||
* eightbytes (GP total 3, in-cap) — pins the independent-counter
|
||||
* interplay the scalar-only rows can't. */
|
||||
{ "t22_float_mix",
|
||||
"package main;\n"
|
||||
"fn mk() (f64, (void | size), i64) = {\n"
|
||||
" let mn: (void | size) = 6: size;\n"
|
||||
" return (2.5, mn, 9);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t = mk();\n"
|
||||
" if (t.0 != 2.5) { return 1; };\n"
|
||||
" if (!(t.1 is size)) { return 2; };\n"
|
||||
" if (t.1 as size != 6) { return 3; };\n"
|
||||
" if (t.2 != 9) { return 4; };\n"
|
||||
" let (x, y, z) = mk();\n"
|
||||
" if (x != 2.5) { return 5; };\n"
|
||||
" if (!(y is size)) { return 6; };\n"
|
||||
" if (y as size != 6) { return 7; };\n"
|
||||
" if (z != 9) { return 8; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* TWO tagged elements in-cap (2+2 GP eightbytes == TUPLE_GPCAP):
|
||||
* narrow u32 payload, negative i64 payload, void variant —
|
||||
* regression-pins ken's k1 probe (probes evaporate, rows don't). */
|
||||
{ "t22_two_tagged",
|
||||
"package main;\n"
|
||||
"fn mk() ((void | u32), (void | i64)) = {\n"
|
||||
" let a: (void | u32) = 9: u32;\n"
|
||||
" let b: (void | i64) = -5: i64;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"fn mkv() ((void | u32), (void | i64)) = {\n"
|
||||
" let a: (void | u32) = void;\n"
|
||||
" let b: (void | i64) = -41: i64;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t = mk();\n"
|
||||
" if (!(t.0 is u32)) { return 1; };\n"
|
||||
" if (t.0 as u32 != 9) { return 2; };\n"
|
||||
" if (!(t.1 is i64)) { return 3; };\n"
|
||||
" if (t.1 as i64 != -5) { return 4; };\n"
|
||||
" let v = mkv();\n"
|
||||
" if (!(v.0 is void)) { return 5; };\n"
|
||||
" if (v.1 as i64 != -41) { return 6; };\n"
|
||||
" let (a, b) = mk();\n"
|
||||
" if (a as u32 != 9) { return 7; };\n"
|
||||
" if (b as i64 != -5) { return 8; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* tagged FIRST, float second — the SSE/GP counter interplay in the
|
||||
* other element order (t22_float_mix covers float-first; ken k2
|
||||
* proved both orders, this pins the reverse). */
|
||||
{ "t22_float_mix_rev",
|
||||
"package main;\n"
|
||||
"fn mk() ((void | size), f64) = {\n"
|
||||
" let mn: (void | size) = 6: size;\n"
|
||||
" return (mn, 0.25);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t = mk();\n"
|
||||
" if (!(t.0 is size)) { return 1; };\n"
|
||||
" if (t.0 as size != 6) { return 2; };\n"
|
||||
" if (t.1 != 0.25) { return 3; };\n"
|
||||
" let (y, z) = mk();\n"
|
||||
" if (y as size != 6) { return 4; };\n"
|
||||
" if (z != 0.25) { return 5; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* tagged tuple element as a CALL ARG — the call-boundary SEND of
|
||||
* the ruled condition-(c) matrix. cstage rides its generic
|
||||
* node_istaggedarg cursor push; wwstage's kind-gated aistagged
|
||||
* missed N_DOT and mis-routed t.0 into the widening branch
|
||||
* (clamped tag 0 — callee read `void`, silent). reviewer-22 fix:
|
||||
* cgenutil.ww N_DOT arm (aistagged + pushargsrev). */
|
||||
{ "t22_elem_arg",
|
||||
"package main;\n"
|
||||
"fn takes(v: (void | size)) size = {\n"
|
||||
" if (v is size) { return v as size; };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let mn: (void | size) = 5: size;\n"
|
||||
" let t: ((void | size), size) = (mn, 4);\n"
|
||||
" if (takes(t.0) != 5) { return 1; };\n"
|
||||
" if (takes(t.0) + t.1 != 9) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
|
||||
/* rule-7 louds: every tagged-tuple route that is not
|
||||
* correct-via-accessor must die LOUD (the #22a exit invariant). */
|
||||
{ "t22_reject_arg",
|
||||
"package main;\n"
|
||||
"fn send(t: ((void | size), size)) size = { return t.1; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let mn: (void | size) = 5: size;\n"
|
||||
" let t: ((void | size), size) = (mn, 4);\n"
|
||||
" return send(t): i32;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "tuple arg element kind unsupported" },
|
||||
{ "t22_reject_overcap_return",
|
||||
"package main;\n"
|
||||
"fn pr() ((void | size), (void | size), size) = {\n"
|
||||
" let a: (void | size) = 1: size;\n"
|
||||
" let b: (void | size) = 2: size;\n"
|
||||
" return (a, b, 7);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t = pr();\n"
|
||||
" return t.2: i32;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "#22b: tagged element in an over-cap (sret) tuple "
|
||||
"return unwired" },
|
||||
/* ken R1: an OVER-CAP tuple literal init (a tagged box >16B pushes
|
||||
* the shape past TUPLE_GPCAP) fell past every cstage N_LET store
|
||||
* arm to NOTHING — silent uninitialized-frame reads — while
|
||||
* wwstage loud-rejected. Pre-existing for (str,str) literals; the
|
||||
* #22a tagged slots routed tagged shapes into it. Both pins. */
|
||||
{ "t22_reject_overcap_lit_tagged",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let e: (void | u32 | str) = \"abc\";\n"
|
||||
" let t: (u64, (void | u32 | str)) = (7u64, e);\n"
|
||||
" if (t.0 != 7u64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "tuple literal exceeds register-return ABI capacity" },
|
||||
{ "t22_reject_overcap_lit_strs",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (str, str) = (\"ab\", \"cde\");\n"
|
||||
" if (t.0.len != 2) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "tuple literal exceeds register-return ABI capacity" },
|
||||
{ "t22_reject_lit_call_elem",
|
||||
"package main;\n"
|
||||
"fn g() (void | size) = { return 5: size; };\n"
|
||||
"fn mk() ((void | size), size) = { return (g(), 4); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t = mk();\n"
|
||||
" return t.1: i32;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "tagged tuple element from a non-local source shape "
|
||||
"unwired" },
|
||||
{ "t22_reject_global_init",
|
||||
"package main;\n"
|
||||
"let g: ((void | size), i64) = (5, 4);\n"
|
||||
"export fn main() i32 = { return g.1: i32; };\n", 0,
|
||||
K_BUILDERR, "unsupported element init (int/str literals only; "
|
||||
"rule 7)" },
|
||||
/* pre-existing loud (no-regress pin): tuple element WRITE. */
|
||||
{ "t22_reject_elem_write",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let mn: (void | size) = 5: size;\n"
|
||||
" let t: ((void | size), size) = (mn, 4);\n"
|
||||
" t.1 = 9;\n"
|
||||
" return t.1: i32;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "unsupported assign target shape" },
|
||||
|
||||
/* ---- over-cap (sret) NARROW-scalar stride: the sret buffer is
|
||||
* slot-laid (C-t0) on EVERY side — send, let-receive, MLET, and
|
||||
* MASSIGN copy-out. Pre-review the wwstage SEND and MASSIGN
|
||||
* receive strode packed esz (4 for u32): self-consistent at base
|
||||
* (ran right, byte-diff), but #22a's MLET-receive slot flip alone
|
||||
* made ww read c at 32 while its send wrote 28 — a ww runtime
|
||||
* REGRESSION the suite had no row for (reviewer-22). (str,u32,str)
|
||||
* = 7 GP words, over-cap. ---- */
|
||||
{ "sret_narrow_mix_let",
|
||||
"package main;\n"
|
||||
"fn f() (str, u32, str) = {\n"
|
||||
" let a: str = \"abc\";\n"
|
||||
" let b: u32 = 7;\n"
|
||||
" let c: str = \"wxyz\";\n"
|
||||
" return (a, b, c);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t = f();\n"
|
||||
" if (len(t.0) != 3) { return 1; };\n"
|
||||
" if (t.1 != 7) { return 2; };\n"
|
||||
" if (len(t.2) != 4) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "sret_narrow_mix_mlet",
|
||||
"package main;\n"
|
||||
"fn f() (str, u32, str) = {\n"
|
||||
" let a: str = \"abc\";\n"
|
||||
" let b: u32 = 7;\n"
|
||||
" let c: str = \"wxyz\";\n"
|
||||
" return (a, b, c);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let (a, b, c) = f();\n"
|
||||
" if (len(a) != 3) { return 1; };\n"
|
||||
" if (b != 7) { return 2; };\n"
|
||||
" if (len(c) != 4) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "sret_narrow_mix_massign",
|
||||
"package main;\n"
|
||||
"fn f() (str, u32, str) = {\n"
|
||||
" let a: str = \"abc\";\n"
|
||||
" let b: u32 = 7;\n"
|
||||
" let c: str = \"wxyz\";\n"
|
||||
" return (a, b, c);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: str = \"\";\n"
|
||||
" let b: u32 = 0;\n"
|
||||
" let c: str = \"\";\n"
|
||||
" a, b, c = f();\n"
|
||||
" if (len(a) != 3) { return 1; };\n"
|
||||
" if (b != 7) { return 2; };\n"
|
||||
" if (len(c) != 4) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
|
||||
/* ---- C-t1 (#33): the let RECEIVE re-keyed onto the declared
|
||||
* type's register classify. Pre-C-t1 wwstage keyed on producer
|
||||
* SHAPE (mixed-str syntactic / rettupleof N_CALL) so a
|
||||
|
||||
@@ -67,6 +67,11 @@ static const struct ent ents[] = {
|
||||
{ .fixture = "lib/bytes/bytestest.ww", .mode = M_ID },
|
||||
{ .fixture = "lib/dirs/dirstest.ww", .mode = M_ID },
|
||||
{ .fixture = "lib/encoding/base32/base32_test.ww", .mode = M_ID },
|
||||
/* graduated from #59.4 DIVERGE by the #22a N_DOT tagged-arg
|
||||
* cursor arm (ww widened t.N args with a clamped tag 0 — the
|
||||
* base .s diff is exactly that PUSHQ pair, 2 sites);
|
||||
* runtime-correct per 979_hex_run */
|
||||
{ .fixture = "lib/encoding/hex/hextest.ww", .mode = M_ID },
|
||||
{ .fixture = "lib/encoding/utf8/utf8test.ww", .mode = M_ID },
|
||||
{ .fixture = "lib/getopt/getopttest.ww", .mode = M_ID },
|
||||
{ .fixture = "lib/hash/adler32/adler32_test.ww", .mode = M_ID },
|
||||
@@ -118,8 +123,7 @@ static const struct ent ents[] = {
|
||||
.mode = M_DIVERGE, .cite = "#59.2" },
|
||||
{ .fixture = "lib/encoding/base64/base64_test.ww",
|
||||
.mode = M_DIVERGE, .cite = "#59.3" },
|
||||
{ .fixture = "lib/encoding/hex/hextest.ww",
|
||||
.mode = M_DIVERGE, .cite = "#59.4" },
|
||||
/* #59.4 hextest graduated to M_ID above (#22a reviewer fixes) */
|
||||
{ .fixture = "lib/errors/errnotest.ww",
|
||||
.mode = M_DIVERGE, .cite = "#59.5" },
|
||||
{ .fixture = "lib/fmt/fmttest.ww",
|
||||
|
||||
Reference in New Issue
Block a user