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:
101
cmd/w6c/cgen.c
101
cmd/w6c/cgen.c
@@ -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++) {
|
||||
|
||||
@@ -404,7 +404,17 @@ resolve_type(Checker *c, Node *n)
|
||||
}
|
||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||
tp->name = p->str;
|
||||
tp->type = resolve_type(c, p->lhs);
|
||||
Type *pt = resolve_type(c, p->lhs);
|
||||
/* Hare-style `T...` (marked on the param node via
|
||||
* Node.op == TK_ELLIPSIS): the param's effective type
|
||||
* inside the callee is []T, and call sites either
|
||||
* gather N args of type T or forward an `xs...` slice. */
|
||||
if (p->op == TK_ELLIPSIS) {
|
||||
tp->variadic = 1;
|
||||
tp->type = type_slice(c->a, pt);
|
||||
} else {
|
||||
tp->type = pt;
|
||||
}
|
||||
if (head == NULL) head = tp;
|
||||
else tail->next = tp;
|
||||
tail = tp;
|
||||
@@ -962,12 +972,38 @@ cexpr(Checker *c, Node *n)
|
||||
err(c, n->pos, "too many arguments");
|
||||
continue;
|
||||
}
|
||||
/* Hare-style variadic param: every remaining arg either
|
||||
* - flows into the gather (assignable to element T), or
|
||||
* - is a single `xs...` spread of `[]T` (forwarding).
|
||||
* Don't advance p — the variadic slot absorbs the tail. */
|
||||
if (p->variadic) {
|
||||
Type *elem = (p->type && p->type->kind == TY_SLICE)
|
||||
? p->type->sub : ty_err;
|
||||
if (a->kind == N_SPREAD) {
|
||||
if (at != ty_err && p->type != ty_err &&
|
||||
!type_assignable(p->type, at))
|
||||
err(c, a->pos,
|
||||
"spread arg: %s not assignable to %s",
|
||||
type_name(c->a, at),
|
||||
type_name(c->a, p->type));
|
||||
if (a->next != NULL)
|
||||
err(c, a->pos,
|
||||
"spread arg must be the last");
|
||||
} else if (elem != ty_err && at != ty_err) {
|
||||
if (!type_assignable(elem, at))
|
||||
err(c, a->pos,
|
||||
"variadic arg: %s not assignable to %s",
|
||||
type_name(c->a, at),
|
||||
type_name(c->a, elem));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!type_assignable(p->type, at) && at != ty_err && p->type != ty_err)
|
||||
err(c, a->pos, "argument type %s not assignable to %s",
|
||||
type_name(c->a, at), type_name(c->a, p->type));
|
||||
p = p->next;
|
||||
}
|
||||
if (p != NULL)
|
||||
if (p != NULL && !p->variadic)
|
||||
err(c, n->pos, "not enough arguments");
|
||||
return n->type = u->ret ? u->ret : ty_void;
|
||||
}
|
||||
@@ -1514,7 +1550,14 @@ build_fn_type(Checker *c, Node *fn)
|
||||
}
|
||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||
tp->name = p->str;
|
||||
tp->type = resolve_type(c, p->lhs);
|
||||
Type *pt = resolve_type(c, p->lhs);
|
||||
/* Hare-style `T...` — see resolve_type N_TFN. */
|
||||
if (p->op == TK_ELLIPSIS) {
|
||||
tp->variadic = 1;
|
||||
tp->type = type_slice(c->a, pt);
|
||||
} else {
|
||||
tp->type = pt;
|
||||
}
|
||||
if (head == NULL) head = tp;
|
||||
else tail->next = tp;
|
||||
tail = tp;
|
||||
|
||||
@@ -135,9 +135,18 @@ parseparams(Parser *p)
|
||||
n->strlen = 0;
|
||||
n->lhs = parsetype(p);
|
||||
}
|
||||
/* Hare-style variadic: `name: T...`. The trailing `...`
|
||||
* after the type promotes the param's type to []T at type-
|
||||
* resolution time; call sites gather N args or forward a
|
||||
* `xs...` spread. Marked on n->op so check.c and selfhost
|
||||
* recognise it without needing a new Node kind. */
|
||||
if (accept(p, TK_ELLIPSIS))
|
||||
n->op = TK_ELLIPSIS;
|
||||
if (head == NULL) head = n;
|
||||
else tail->next = n;
|
||||
tail = n;
|
||||
if (n->op == TK_ELLIPSIS)
|
||||
break; /* Hare-style variadic must be the last param */
|
||||
if (!accept(p, TK_COMMA))
|
||||
break;
|
||||
if (p->cur.kind == TK_RPAREN) /* trailing comma */
|
||||
|
||||
@@ -219,6 +219,7 @@ type_eq(Type *a, Type *b)
|
||||
if (!type_eq(a->ret, b->ret)) return 0;
|
||||
Tparam *pa = a->params, *pb = b->params;
|
||||
while (pa && pb) {
|
||||
if (pa->variadic != pb->variadic) return 0;
|
||||
if (!type_eq(pa->type, pb->type)) return 0;
|
||||
pa = pa->next; pb = pb->next;
|
||||
}
|
||||
|
||||
@@ -403,6 +403,9 @@ struct Tparam {
|
||||
const char *name;
|
||||
Type *type;
|
||||
Tparam *next;
|
||||
int variadic; /* Hare-style `T...` — `type` is []T,
|
||||
* call site gathers / forwards. Distinct
|
||||
* from Type.variadic (C-style FFI `...`). */
|
||||
};
|
||||
|
||||
struct Type {
|
||||
|
||||
Reference in New Issue
Block a user