w6c+selfhost+lib: Hare-style variadic call sites

Param-decl `name: T...` (Tparam.variadic=1, type []T), call-site
gather of N args into a fresh `[N]T`, forward via `xs...`, full
selfhost mirror, and lib/fmt graduated to the Hare shape.

Frontend:
  - parse: `T...` after a param's type stamps Node.op=TK_ELLIPSIS
    and breaks out (variadic must be last).
  - check: resolve_type N_TFN / build_fn_type wrap the param type
    as []T and set tp->variadic. N_CALL accepts either a tail of
    args assignable to T (gather) or a single `xs...` spread of
    []T (forward); both bypass the "too many args" check on the
    variadic slot.
  - type: type_eq compares Tparam.variadic.

Cgen (cstage):
  - call site: when the callee has a variadic last param,
    materialise the tail args into a frame-resident `[N]T` via
    localoff, write a 24B slice descriptor (ptr,len,cap), and
    splice a synthesised N_IDENT into args[] so the downstream
    widen/eval/pop loops see one slice slot. Tagged-element types
    route each store through cg_widen_tagged_store. Forwarding
    skips gather: the N_SPREAD wrapper is replaced with its inner
    slice expression. Empty form writes {nil,0,0}. args[] / widen[]
    bump from 16 to 64 to accommodate Hare's mixed-arg printers.

Selfhost mirror:
  - lib/ww/parse: `T...` mark on N_PARAM.op.
  - cgen: varargseq counter on Cg; scanlocals reserves
    @vararg_d_N + @vararg_sl_N per variadic call (seq recorded on
    N_CALL.uval so cgcall picks the same names). cgcall does the
    same gather/forward and N_IDENT splice. cgfnparams treats
    variadic params as 24B slice slots via a synthesised TSLICE
    tnode. pushargsrev skips the tagged-widen detection for
    variadic params (effective type is []T, not tagged).
  - rhstargetname now recognises N_TRUE/N_FALSE/N_RUNELIT and
    typed N_INTLIT so the variant-tag lookup finds bool/rune/iN
    variants instead of falling through to "first non-str" (which
    misassigned tag 0 to bool in tagged unions like formattable).

lib/fmt graduated: print/println/fprint/fprintln/errorln/fatal
take `args: formattable...`. Bare `error` (no -ln) is skipped —
the leaf name collides with strconv's `type error = !(invalid |
overflow)` under the driver's flat namespace.

Tests: 5 new e2e rows (plain gather, zero-arg, tagged element,
forwarding, fmt.println end-to-end). lib/CLAUDE.md workaround
paragraph replaced with the Hare-shape description.
This commit is contained in:
2026-05-13 08:56:01 +09:00
parent 46edb8db4a
commit b6cf68f2b8
15 changed files with 1327 additions and 140 deletions

View File

@@ -2546,11 +2546,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
/* up to 6 integer + 8 float args via SysV registers.
* str args occupy two integer eightbytes (ptr, len). */
* str args occupy two integer eightbytes (ptr, len). The
* arg-buffer cap accommodates Hare-style variadic gather
* (`fmt.println(a, b, c, ...)`) where N args of element
* type T fold into a single []T slice slot below. */
int argcount = 0;
Node *args[16] = {0};
Node *args[64] = {0};
for (Node *a = n->list; a; a = a->next)
if (argcount < 16) args[argcount++] = a;
if (argcount < 64) args[argcount++] = a;
/* Resolve callee fn-type so we can match each arg against
* its declared parameter type — needed to detect implicit
* widening of a concrete variant into a tagged-union slot. */
@@ -2559,15 +2562,101 @@ cgexpr(Cg *c, Node *n, Local *locals)
callee_t->under : callee_t;
Tparam *callee_params = (cu && cu->kind == TY_FN) ?
cu->params : NULL;
/* Hare-style variadic last param: gather N tail args into a
* stack-resident []T or forward an `xs...` spread, then
* splice in a single slice arg so the downstream widen/push/
* pop machinery sees one 24B slice slot for the variadic.
*
* Forward shape: `f(... , xs...)` becomes `f(... , xs)`.
* Gather shape: `f(... , e0, e1, eN)` materialises e0..eN
* into a frame-resident `[N]T` (widening each element when T
* is a tagged union), writes a 24B slice descriptor
* {ptr=&data, len=N, cap=N}, and replaces the tail args with
* an N_IDENT pointing at the descriptor. Empty form
* (`f(...)` with no variadic args) writes {0, 0, 0}. */
{
int nfixed = 0;
Tparam *var_p = NULL;
for (Tparam *p = callee_params; p; p = p->next) {
if (p->variadic) { var_p = p; break; }
nfixed++;
}
if (var_p != NULL) {
int nvar = argcount - nfixed;
if (nvar < 0) nvar = 0;
int forwarding = (nvar == 1 && args[nfixed] &&
args[nfixed]->kind == N_SPREAD);
if (forwarding) {
args[nfixed] = args[nfixed]->lhs;
argcount = nfixed + 1;
} else {
Type *vst = var_p->type;
Type *vsu = (vst && vst->kind == TY_NAMED)
? vst->under : vst;
Type *velem = (vsu && vsu->kind == TY_SLICE)
? 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);
int doff = 0;
if (nvar > 0) {
const char *dname = mklabel(c, "vararg_d");
doff = localoff(c, &locals,
dname, nvar * esz, cg_frame);
int v_is_tagged = velem &&
tagged_arg_size(velem) > 0;
for (int j = 0; j < nvar; j++) {
Node *a = args[nfixed + j];
int slot = doff + j * esz;
if (v_is_tagged) {
cg_widen_tagged_store(c,
&locals, velem,
a, slot, esz);
continue;
}
cgexpr(c, a, locals);
int op = A_MOVQ;
if (esz == 1) op = A_MOVB;
else if (esz == 4) op = A_MOVL;
ins2(c, op, areg(D_AX),
amem(D_BP, slot));
}
}
if (nvar > 0)
ins2(c, A_LEAQ,
amem(D_BP, doff),
areg(D_AX));
else
ins2(c, A_XORQ, areg(D_AX),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, sloff + 0));
ins2(c, A_MOVQ, aimm(nvar),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, sloff + 8));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, sloff + 16));
Node *sn = newnode(c->a, N_IDENT, n->pos);
sn->str = slname;
sn->strlen = 0;
sn->type = vst;
args[nfixed] = sn;
argcount = nfixed + 1;
}
}
}
/* widen[i]: param is tagged and arg needs re-layout.
* - arg is a concrete variant (str/struct/scalar) — wrap
* in the param's slot shape.
* - arg is itself a tagged union of a subset/different
* variant set — copy the slot words and remap the tag.
* Identical types pass through unchanged. */
int widen[16] = {0};
int widen_sz[16] = {0};
Type *widen_param[16] = {0};
int widen[64] = {0};
int widen_sz[64] = {0};
Type *widen_param[64] = {0};
{
Tparam *p = callee_params;
for (int i = 0; i < argcount; i++) {