cstage+test: store .len/.cap on every variadic-pack element (#16)

cstage variadic gather stored only AX (.ptr) per element; .len and
.cap read stack residue at the callee. Tagged-union variadic path
escaped because cg_widen_tagged_store wrote the full slot — but
primitive-type variadics (str..., slice...) silently dropped the
trailing fields. Selfhost only uses tagged-union variadics
(formattable...) so bootstrap byte-id ww2==ww3==ww4 stayed green;
the bug surfaced in worker-strings pre-flight (session 5) on the
Hare-faithful concat(strs: str...) shape.

Per-element store branch now mirrors selfhost/cmd/wcc/cgenexpr.ww
velemstr (AX→slot+0, BX→slot+8) and velemslice (AX→slot+0,
BX→slot+8, CX→slot+16). Also swap dname-before-sname allocation
order in the variadic-pack frame layout to match wwstage scanlocals
+ localadd order (cgendecl.ww:507-516 and cgenexpr.ww:2949-2954);
without the swap post-fix asm has correct stores at mismatched
offsets vs wwstage.

Rule-10 alignment: cstage UP to wwstage's already-correct primitive
variadic path.

743_variadic_pack pins the contract: asm-presence ≥3 ptr-stores +
≥3 len-stores in caller TEXT on both stages, plus cs-vs-ws cmp -s
byte-id per row. 117/117 ok. Bootstrap byte-id ww2==ww3==ww4 holds.

Unblocks: lib/bytes contains-variadic, lib/strings sub variadic,
and the concat/trim/contains family that c1 shipped non-variadic.
This commit is contained in:
2026-05-18 21:13:42 +09:00
parent c40edaa7f9
commit 3bd9b1d56f
3 changed files with 226 additions and 3 deletions

View File

@@ -4302,16 +4302,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
? vsu->sub : NULL;
int esz = (velem && velem->size)
? (int)velem->size : 8;
const char *slname = mklabel(c, "vararg_sl");
int sloff = localoff(c, &locals,
slname, 24, cg_frame);
/* Allocate dname BEFORE sname so the
* descriptor lives below the element
* buffer, matching the wwstage scanlocals
* reservation order (rule 10). */
int doff = 0;
if (nvar > 0) {
const char *dname = mklabel(c, "vararg_d");
doff = localoff(c, &locals,
dname, nvar * esz, cg_frame);
}
const char *slname = mklabel(c, "vararg_sl");
int sloff = localoff(c, &locals,
slname, 24, cg_frame);
if (nvar > 0) {
int v_is_tagged = velem &&
tagged_arg_size(velem) > 0;
int v_is_str = type_isstr(velem);
int v_is_slice = type_isslice(velem);
for (int j = 0; j < nvar; j++) {
Node *a = args[nfixed + j];
int slot = doff + j * esz;
@@ -4322,6 +4330,28 @@ cgexpr(Cg *c, Node *n, Local *locals)
continue;
}
cgexpr(c, a, locals);
/* str / slice element: cgexpr
* returns the full descriptor in
* AX/(BX)/(CX); a bare MOVQ AX
* stores .ptr only and the
* trailing fields read stack
* garbage at the callee. */
if (v_is_str) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, slot));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, slot + 8));
continue;
}
if (v_is_slice) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, slot));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, slot + 8));
ins2(c, A_MOVQ, areg(D_CX),
amem(D_BP, slot + 16));
continue;
}
int op = A_MOVQ;
if (esz == 1) op = A_MOVB;
else if (esz == 4) op = A_MOVL;