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

@@ -290,6 +290,50 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
};
};
};
// Hare-style variadic call: reserve @vararg_d_<seq> for the
// element data and @vararg_sl_<seq> for the 24B slice
// descriptor. The seq is recorded on the N_CALL node so
// cgcall picks the same names regardless of walk order
// (scanlocals descends LTR; pushargsrev evaluates RTL).
let nfixed: i32 = 0;
let varp: *node = callee_variadic_param(c, n.lhs, &nfixed);
if (varp != nil) {
let nargs: i32 = 0;
let aw: *node = n.list;
for (aw != nil) { nargs += 1; aw = aw.next; };
let nvar: i32 = nargs - nfixed;
if (nvar < 0) { nvar = 0; };
let forwarding: bool = false;
if (nvar == 1) {
let aa: *node = n.list;
let k0: i32 = 0;
for (k0 < nfixed) { aa = aa.next; k0 += 1; };
if (aa != nil) {
if (aa.kind == nkind.N_SPREAD) {
forwarding = true;
};
};
};
if (!forwarding) {
let seq: i32 = c.varargseq;
n.uval = seq: u64;
c.varargseq += 1;
let esz: i32 = slotsize(c, varp.lhs);
if (esz < 1) { esz = 1; };
let dname: str = mkvarargname(c, "@vararg_d_", seq);
let sname: str = mkvarargname(c, "@vararg_sl_", seq);
if (nvar > 0) {
if (!scanseenmark(c, dname)) {
let dsz: i32 = nvar * esz;
if ((dsz & 7) != 0) {
dsz = (dsz + 7) & ~7;
};
total += dsz;
};
};
if (!scanseenmark(c, sname)) { total += 24; };
};
};
};
if (n.lhs != nil) { total += scanlocals(c, n.lhs); };
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
@@ -321,6 +365,39 @@ fn cgfnparams(c: *cgen, params: *node) void = {
for (p != nil) {
if (p.kind == nkind.N_PARAM) {
let nm: str = p.str;
// Hare-style variadic `T...`: callee receives a []T
// slice (3 register words / 24B). Mirror the slice-
// param spill below but use a synthesised TSLICE
// tnode so body references see the slot as a slice.
if (p.op == tkind.TK_ELLIPSIS) {
let tn: *node = slicewrap(c, p.lhs);
if (idx + 3 <= 6) {
let off: i32 = localadd(c, nm, 24, tn);
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff(off: i64);
emitline("(BP)\n");
idx += 1;
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + 8): i64);
emitline("(BP)\n");
idx += 1;
emitline("\tMOVQ\t");
emitline(argregname(idx));
emitline(", ");
emitoff((off + 16): i64);
emitline("(BP)\n");
idx += 1;
} else {
localaddstack(c, nm, tn, 16 + stkcursor*8);
stkcursor += 3;
};
p = p.next;
continue;
};
if (isfloattype(c, p.lhs)) {
// Float param: SysV uses the XMM stream
// (X0..X7). 8B (f64) or 4B (f32) slot.
@@ -494,15 +571,20 @@ fn cgfn(c: *cgen, fn_: *node) void = {
let frame: i32 = 0;
for (scanp != nil) {
if (scanp.kind == nkind.N_PARAM) {
if (istaggedtype(c, scanp.lhs)) { frame += slotsize(c, scanp.lhs); }
// Hare-style variadic `T...`: param is []T inside
// the callee, so it occupies a 24B slice slot.
if (scanp.op == tkind.TK_ELLIPSIS) { frame += 24; }
else { if (istaggedtype(c, scanp.lhs)) { frame += slotsize(c, scanp.lhs); }
else { if (isslicetype(c, scanp.lhs)) { frame += 24; }
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
else { frame += 8; }; }; };
else { frame += 8; }; }; }; };
scanseenmark(c, scanp.str);
};
scanp = scanp.next;
};
c.varargseq = 0;
if (fn_.body != nil) { frame += scanlocals(c, fn_.body); };
c.varargseq = 0;
// Drop the stubs so emission rebuilds c.locals with real offsets.
c.locals = nil;
if ((frame & 15) != 0) {