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++) {

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -26,18 +26,14 @@ Signatures mirror Hare too, modulo:
buffer (`strings.dup`, `strings.concat`) still return an owned
`str` that callers free via `os.free(r.ptr, r.len: u64)`.
- Call-site variadic sugar (`fmt::println(42)`) doesn't land yet.
The receive side does — `fmt.formattable` is a tagged union of
the printable scalar types, and `fmt.printv` / `fmt.printlnv`
take an explicit `[]formattable` slice. Until the call-site
gather is implemented, callers either hand-build the slice:
let args: [2]fmt.formattable;
args[0] = "count: ": fmt.formattable;
args[1] = 42i64: fmt.formattable;
fmt.printlnv(args[0:2]);
or compose to a single str via strconv.i64tos + strings.concat:
fmt.println(strconv.i64tos(42, strconv.base.DEC));
`lib/fmt` is intentionally print-string-only — no `printf`-family.
- Call-site variadic sugar matches Hare. `fn f(args: T...)` declares
a Hare-style variadic; call sites either gather N args into a
fresh `[]T` (`fmt.println(42, "hi", true)`) or forward an existing
slice with `xs...` (`fprintln(fd, args...)`). The bare `T...` form
in tagged unions still means spread-flatten (`(...inner | E)`);
the two uses don't overlap because `T...` only attaches to a
*param* decl. `lib/fmt` is intentionally print-string-only — no
`printf`-family.
- `(T | U)` sum-typed parameters dispatch via `match` inside the
callee. `strings.byteindex(haystack: str, needle: (str | rune))`,

View File

@@ -1,37 +1,30 @@
// fmt — formatting writers. Goes through os.write to a file
// descriptor. Hare's variadic call-site sugar (`fmt::println(42)`
// gathering args into a `[]formattable`) isn't wired yet; until then,
// callers either:
//
// 1. Hand-build the slice:
// let args: [2]formattable;
// args[0] = 42i64: formattable;
// args[1] = " hi": formattable;
// fmt.print(args[0:2]);
//
// 2. Compose a single str via strconv.i64tos / strings.concat:
// fmt.println(strconv.i64tos(42, strconv.base.DEC));
//
// `print(s: str)` keeps the single-string form for the common case.
// fmt — formatting writers. Mirrors Hare's lib/fmt subset that fmt-
// prints values via [[io::handle]]-style fd writers. Call sites take
// Hare's variadic shape: `fmt.println(42, "hi", true)` gathers the
// args into a `[]formattable` slice; wrappers forward via `args...`.
use os;
use strconv;
use strings;
// formattable — tagged union of types fmt can render. Mirrors
// Hare's `fmt::formattable = (...types::numeric | uintptr | str |
// rune | bool | nullable *opaque | void)`, narrowed to the set ww
// actually has codegen for. Slot size is 24B (8 tag + 16 str
// payload).
// formattable — tagged union of types fmt can render. Mirrors Hare's
// `fmt::formattable = (...types::numeric | uintptr | str | rune |
// bool | nullable *opaque | void)`, narrowed to the set ww actually
// has codegen for. Slot size is 24B (8 tag + 16 str payload).
export type formattable = (i64 | str | bool | rune);
// vprint — write the formatted form of each element of `args` to
// `fd`. Returns total bytes written or the first negative os.write
// result.
fn vprint(fd: i32, args: []formattable) i64 = {
// fprint — write the formatted form of each `args` element to `fd`,
// separated by spaces. Returns total bytes written or the first
// negative os.write result. Hare's separator-by-space matches.
export fn fprint(fd: i32, args: formattable...) i64 = {
let total: i64 = 0;
let i: i32 = 0;
for (i < args.len) {
if (i > 0) {
let r: i64 = os.write(fd, " ".ptr, 1u64);
if (r < 0) { return r; };
total += r;
};
match (args[i]) {
case let n: i64 => {
let s: str = strconv.i64tos(n, strconv.base.DEC);
@@ -64,55 +57,40 @@ fn vprint(fd: i32, args: []formattable) i64 = {
return total;
};
// print(s: str) — single-string form for the common case. The
// variadic-style `print(args: []formattable)` lives as `printv`
// until call-site sugar lands.
export fn print(s: str) i64 = {
return os.write(1, s.ptr, s.len: u64);
};
// printv — Hare-shaped `print(args: formattable...)` modulo the
// call-site sugar. Callers pass an explicit `[]formattable` slice.
export fn printv(args: []formattable) i64 = {
return vprint(1, args);
};
export fn println(s: str) i64 = {
let n: i64 = os.write(1, s.ptr, s.len: u64);
if (n < 0) { return n; };
let m: i64 = os.write(1, "\n".ptr, 1u64);
if (m < 0) { return m; };
return n + m;
};
// printlnv — like printv but adds a trailing newline.
export fn printlnv(args: []formattable) i64 = {
let n: i64 = vprint(1, args);
if (n < 0) { return n; };
let m: i64 = os.write(1, "\n".ptr, 1u64);
if (m < 0) { return m; };
return n + m;
};
// errorln — write a message to stderr with a trailing newline.
export fn errorln(s: str) i64 = {
let n: i64 = os.write(2, s.ptr, s.len: u64);
if (n < 0) { return n; };
let m: i64 = os.write(2, "\n".ptr, 1u64);
if (m < 0) { return m; };
return n + m;
};
// fprint / fprintln — same as print/println but on an arbitrary fd.
// Used by the compiler to write to its -o output file.
export fn fprint(fd: i32, s: str) i64 = {
return os.write(fd, s.ptr, s.len: u64);
};
export fn fprintln(fd: i32, s: str) i64 = {
let n: i64 = os.write(fd, s.ptr, s.len: u64);
// fprintln — fprint plus a trailing newline.
export fn fprintln(fd: i32, args: formattable...) i64 = {
let n: i64 = fprint(fd, args...);
if (n < 0) { return n; };
let m: i64 = os.write(fd, "\n".ptr, 1u64);
if (m < 0) { return m; };
return n + m;
};
// print / println — fprint / fprintln on stdout. Direct counterparts
// of Hare's fmt::print / fmt::println.
export fn print(args: formattable...) i64 = {
return fprint(1, args...);
};
export fn println(args: formattable...) i64 = {
return fprintln(1, args...);
};
// errorln — fprintln on stderr. Hare's `fmt::error` (without -ln) is
// skipped here: the bare `error` name collides with strconv's
// `type error = !(invalid | overflow)` under the driver's flat
// concatenation namespace. Callers wanting the no-newline form use
// `fprint(2, args...)` directly.
export fn errorln(args: formattable...) i64 = {
return fprintln(2, args...);
};
// fatal — errorln then exit(255). `never` return marks the bottom
// type so flow-control checks treat callers as terminated. The
// fprintln result is dropped as an expression statement (Hare's
// `_ = fprintln(...)` wouldn't add safety here — process exit
// follows immediately).
export fn fatal(args: formattable...) never = {
fprintln(2, args...);
os.exit(255);
};

View File

@@ -104,8 +104,17 @@ fn parseparams(p: *parser) *node = {
n.str = id;
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
n.lhs = parsetype(p);
// Hare-style variadic: `name: T...`. Marker on n.op so check
// promotes the param's type to []T and call sites gather /
// forward. Mirrors cmd/wcc/parse.c parseparams.
if (accepttok(p, tkind.TK_ELLIPSIS)) {
n.op = tkind.TK_ELLIPSIS;
};
if (head == nil) { head = n; tail = n; }
else { tail.next = n; tail = n; };
if (n.op == tkind.TK_ELLIPSIS) {
break; // variadic must be the last param
};
if (!accepttok(p, tkind.TK_COMMA)) { break; };
if (p.curkind == tkind.TK_RPAREN) { break; };
};

View File

@@ -3639,8 +3639,17 @@ fn parseparams(p: *parser) *node = {
n.str = id;
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
n.lhs = parsetype(p);
// Hare-style variadic: `name: T...`. Marker on n.op so check
// promotes the param's type to []T and call sites gather /
// forward. Mirrors cmd/wcc/parse.c parseparams.
if (accepttok(p, tkind.TK_ELLIPSIS)) {
n.op = tkind.TK_ELLIPSIS;
};
if (head == nil) { head = n; tail = n; }
else { tail.next = n; tail = n; };
if (n.op == tkind.TK_ELLIPSIS) {
break; // variadic must be the last param
};
if (!accepttok(p, tkind.TK_COMMA)) { break; };
if (p.curkind == tkind.TK_RPAREN) { break; };
};
@@ -5568,6 +5577,81 @@ use typ;
use sym;
use strconv;
// ---- variadic-call helpers (Hare-style `T...` param) -----------------
// slicewrap — synthesise an N_TSLICE node wrapping the given element
// type AST. Used by the Hare-style variadic path so the local entry
// for the param (callee side) and the call-site slice descriptor
// (caller side) both advertise their effective type as []ELEM —
// every isslicetype / nodeisslice check then succeeds naturally.
fn slicewrap(c: *cgen, elem: *node) *node = {
let s: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
s.lhs = elem;
return s;
};
// findvariadicparam — walk a param-list head and return the variadic
// param node (the one with op == TK_ELLIPSIS) plus the count of
// non-variadic params before it. Returns nil/0 when no variadic.
// nfixed_out cannot be nil.
fn findvariadicparam(ps: *node, nfixed_out: *i32) *node = {
*nfixed_out = 0;
let p: *node = ps;
for (p != nil) {
if (p.kind == nkind.N_PARAM) {
if (p.op == tkind.TK_ELLIPSIS) {
return p;
};
*nfixed_out += 1;
};
p = p.next;
};
return nil;
};
// callee_variadic_param — convenience wrapper: looks up the callee
// by name and finds its variadic param + nfixed. Returns nil if the
// callee isn't registered or has no variadic param.
fn callee_variadic_param(c: *cgen, callee: *node, nfixed_out: *i32) *node = {
*nfixed_out = 0;
if (callee == nil) { return nil; };
let cnm: str;
cnm.ptr = nil; cnm.len = 0;
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
if (cnm.len == 0) { return nil; };
let ps: *node = fnparamslookup(c, cnm);
return findvariadicparam(ps, nfixed_out);
};
// mkvarargname — fresh local-slot name "<prefix><seq>". Used for
// the per-variadic-call scratch buffers (`@vararg_d_N` for the
// element-data buffer, `@vararg_sl_N` for the 24B slice descriptor)
// where N is recorded on the N_CALL node at scanlocals time so both
// the prologue reservation and the call-site emission agree.
fn mkvarargname(c: *cgen, prefix: str, seq: i32) str = {
let buf: [128]u8;
let i: i32 = 0;
let j: i32 = 0;
for (j < prefix.len) {
buf[i] = prefix[j];
i += 1; j += 1;
};
let ns: str = strconv.i64tos(seq: i64, strconv.base.DEC);
let n: i32 = ns.len;
let dk: i32 = 0;
for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; };
let total: i32 = i + n;
let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8;
let k: i32 = 0;
for (k < total) { p[k] = buf[k]; k += 1; };
p[total] = 0u8;
let r: str;
r.ptr = p;
r.len = total;
return r;
};
// ---- expression cgen -------------------------------------------------
// pushargsrev — recursively walks the arg list, evaluates rightmost
@@ -5593,21 +5677,31 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
let widentag: i32 = 0;
if (param != nil) {
if (param.kind == nkind.N_PARAM) {
let ptype: *node = param.lhs;
if (istaggedtype(c, ptype)) {
let aistagged: bool = false;
if (arg.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, arg.str);
if (lc != nil) {
aistagged = istaggedtype(c, lc.tnode);
// Hare-style variadic `T...`: effective param type is
// []T (slice). The arg here is the synthesised slice
// descriptor (or a forwarded `xs...` slice), not a
// value of T being widened into a tagged slot — skip
// the widening detection so the slice-ident fast path
// at the bottom of pushargsrev gets the push.
if (param.op == tkind.TK_ELLIPSIS) {
widensz = 0;
} else {
let ptype: *node = param.lhs;
if (istaggedtype(c, ptype)) {
let aistagged: bool = false;
if (arg.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, arg.str);
if (lc != nil) {
aistagged = istaggedtype(c, lc.tnode);
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
let t: i32 = taggedvariantindex(c, tagged, arg);
if (t < 0) { t = 0; };
widentag = t;
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
let t: i32 = taggedvariantindex(c, tagged, arg);
if (t < 0) { t = 0; };
widentag = t;
};
};
};
@@ -7035,6 +7129,16 @@ fn rhstargetname(c: *cgen, rhs: *node) str = {
return nm;
};
if (rhs.kind == nkind.N_STRLIT) { return "str"; };
if (rhs.kind == nkind.N_TRUE) { return "bool"; };
if (rhs.kind == nkind.N_FALSE) { return "bool"; };
if (rhs.kind == nkind.N_RUNELIT) { return "rune"; };
if (rhs.kind == nkind.N_INTLIT) {
// Typed int literal (`42i64`, `3u8`): suffix names the
// concrete variant so flatvariantidx finds it. Untyped
// literals (tsuffix=="") fall through to the isstr scan.
let s: str = rhs.tsuffix;
if (s.len > 0) { return s; };
};
// `T{}` carries its type name on the lhs N_IDENT — the parser
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
// Needed so `return eof{};` (variant of a tagged union) resolves
@@ -9735,6 +9839,147 @@ fn cgcall(c: *cgen, n: *node) void = {
calleeparams = fnparamslookup(c, callee.str);
};
};
// Hare-style variadic last param: gather N tail args into a
// frame-resident [N]T (`@vararg_d_<seq>`) plus a 24B slice
// descriptor (`@vararg_sl_<seq>`), then splice a synthesised
// N_IDENT pointing at the descriptor into n.list so the rest
// of the call machinery sees one slice slot for the variadic.
// Forwarding shape (`xs...`) skips the gather: the spread's
// inner slice expression replaces the wrapper in place. Empty
// (no trailing args) writes a {nil, 0, 0} descriptor. The seq
// matches the one scanlocals stamped on n.uval.
{
let nfixed_v: i32 = 0;
let varp: *node = callee_variadic_param(c, callee, &nfixed_v);
if (varp != nil) {
let nargs0: i32 = 0;
let aw: *node = n.list;
for (aw != nil) { nargs0 += 1; aw = aw.next; };
let nvar: i32 = nargs0 - nfixed_v;
if (nvar < 0) { nvar = 0; };
let forwarding: bool = false;
if (nvar == 1) {
let aaf: *node = n.list;
let kk: i32 = 0;
for (kk < nfixed_v) {
aaf = aaf.next;
kk += 1;
};
if (aaf != nil) {
if (aaf.kind == nkind.N_SPREAD) {
forwarding = true;
};
};
};
if (forwarding) {
let prev: *node = nil;
let cur2: *node = n.list;
let kk2: i32 = 0;
for (kk2 < nfixed_v) {
prev = cur2;
cur2 = cur2.next;
kk2 += 1;
};
let inner: *node = cur2.lhs;
if (inner != nil) { inner.next = nil; };
if (prev == nil) { n.list = inner; }
else { prev.next = inner; };
} else {
let seq: i32 = n.uval: i32;
let dname: str = mkvarargname(c, "@vararg_d_", seq);
let sname: str = mkvarargname(c, "@vararg_sl_", seq);
let esz: i32 = slotsize(c, varp.lhs);
if (esz < 1) { esz = 1; };
let velemtagged: bool = istaggedtype(c, varp.lhs);
let velemstr: bool = isstrtype(c, varp.lhs);
let velemslice: bool = isslicetype(c, varp.lhs);
let doff: i32 = 0;
if (nvar > 0) {
doff = localadd(c, dname, nvar * esz, nil);
};
let soff: i32 = localadd(c, sname, 24,
slicewrap(c, varp.lhs));
let aa2: *node = n.list;
let kk3: i32 = 0;
for (kk3 < nfixed_v) {
aa2 = aa2.next;
kk3 += 1;
};
let j: i32 = 0;
let prevarg: *node = n.list;
if (nfixed_v == 0) { prevarg = nil; }
else {
let kk4: i32 = 0;
for (kk4 < nfixed_v - 1) {
prevarg = prevarg.next;
kk4 += 1;
};
};
for (aa2 != nil) {
let slot: i32 = doff + j * esz;
if (velemtagged) {
cgwidentaggedstore(c, varp.lhs,
aa2, slot, esz);
} else { if (velemstr) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot + 8): i64);
emitline("(BP)\n");
} else { if (velemslice) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((slot + 16): i64);
emitline("(BP)\n");
} else {
cgexpr(c, aa2);
let op: str = "MOVQ";
if (esz == 1) { op = "MOVB"; }
else { if (esz == 4) { op = "MOVL"; }; };
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
}; }; };
j += 1;
aa2 = aa2.next;
};
if (nvar > 0) {
emitline("\tLEAQ\t");
emitoff(doff: i64);
emitline("(BP), AX\n");
} else {
emitline("\tXORQ\tAX, AX\n");
};
emitline("\tMOVQ\tAX, ");
emitoff(soff: i64);
emitline("(BP)\n");
emitline("\tMOVQ\t$");
emitint(nvar: i64);
emitline(", AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((soff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tAX, ");
emitoff((soff + 16): i64);
emitline("(BP)\n");
let sn: *node = newnode(c.a, nkind.N_IDENT,
"", 0, 0);
sn.str = sname;
if (prevarg == nil) { n.list = sn; }
else { prevarg.next = sn; };
};
};
};
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
// Pop forward. Float args were pushed as 8 bytes from X0 via
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
@@ -12393,6 +12638,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); };
@@ -12424,6 +12713,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.
@@ -12597,15 +12919,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) {
@@ -13022,6 +13349,11 @@ type cgen = struct {
yieldbuf: *str, // stack of match end labels for yield
defertop: i32,
deferbuf: **node, // stack of deferred exprs (LIFO at return)
// Variadic-call gather state. scanlocals walks the body in pre-
// order DFS and assigns per-call scratch names `@vararg_d_N` /
// `@vararg_sl_N` using this counter; cgcall resets and walks in
// the same order so the names line up at emission time.
varargseq: i32,
};
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
@@ -13042,6 +13374,7 @@ fn cgeninit(c: *cgen, a: *arena) void = {
c.frame = 0;
c.lastwasreturn = 0;
c.labelseq = 0;
c.varargseq = 0;
// Note: strlit_seq, strlits, ffis are *not* reset here; they
// persist across cgfn calls within one file. cgfile resets them
// at the start of each compilation unit.

View File

@@ -349,6 +349,11 @@ type cgen = struct {
yieldbuf: *str, // stack of match end labels for yield
defertop: i32,
deferbuf: **node, // stack of deferred exprs (LIFO at return)
// Variadic-call gather state. scanlocals walks the body in pre-
// order DFS and assigns per-call scratch names `@vararg_d_N` /
// `@vararg_sl_N` using this counter; cgcall resets and walks in
// the same order so the names line up at emission time.
varargseq: i32,
};
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
@@ -369,6 +374,7 @@ fn cgeninit(c: *cgen, a: *arena) void = {
c.frame = 0;
c.lastwasreturn = 0;
c.labelseq = 0;
c.varargseq = 0;
// Note: strlit_seq, strlits, ffis are *not* reset here; they
// persist across cgfn calls within one file. cgfile resets them
// at the start of each compilation unit.

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) {

View File

@@ -2142,6 +2142,147 @@ fn cgcall(c: *cgen, n: *node) void = {
calleeparams = fnparamslookup(c, callee.str);
};
};
// Hare-style variadic last param: gather N tail args into a
// frame-resident [N]T (`@vararg_d_<seq>`) plus a 24B slice
// descriptor (`@vararg_sl_<seq>`), then splice a synthesised
// N_IDENT pointing at the descriptor into n.list so the rest
// of the call machinery sees one slice slot for the variadic.
// Forwarding shape (`xs...`) skips the gather: the spread's
// inner slice expression replaces the wrapper in place. Empty
// (no trailing args) writes a {nil, 0, 0} descriptor. The seq
// matches the one scanlocals stamped on n.uval.
{
let nfixed_v: i32 = 0;
let varp: *node = callee_variadic_param(c, callee, &nfixed_v);
if (varp != nil) {
let nargs0: i32 = 0;
let aw: *node = n.list;
for (aw != nil) { nargs0 += 1; aw = aw.next; };
let nvar: i32 = nargs0 - nfixed_v;
if (nvar < 0) { nvar = 0; };
let forwarding: bool = false;
if (nvar == 1) {
let aaf: *node = n.list;
let kk: i32 = 0;
for (kk < nfixed_v) {
aaf = aaf.next;
kk += 1;
};
if (aaf != nil) {
if (aaf.kind == nkind.N_SPREAD) {
forwarding = true;
};
};
};
if (forwarding) {
let prev: *node = nil;
let cur2: *node = n.list;
let kk2: i32 = 0;
for (kk2 < nfixed_v) {
prev = cur2;
cur2 = cur2.next;
kk2 += 1;
};
let inner: *node = cur2.lhs;
if (inner != nil) { inner.next = nil; };
if (prev == nil) { n.list = inner; }
else { prev.next = inner; };
} else {
let seq: i32 = n.uval: i32;
let dname: str = mkvarargname(c, "@vararg_d_", seq);
let sname: str = mkvarargname(c, "@vararg_sl_", seq);
let esz: i32 = slotsize(c, varp.lhs);
if (esz < 1) { esz = 1; };
let velemtagged: bool = istaggedtype(c, varp.lhs);
let velemstr: bool = isstrtype(c, varp.lhs);
let velemslice: bool = isslicetype(c, varp.lhs);
let doff: i32 = 0;
if (nvar > 0) {
doff = localadd(c, dname, nvar * esz, nil);
};
let soff: i32 = localadd(c, sname, 24,
slicewrap(c, varp.lhs));
let aa2: *node = n.list;
let kk3: i32 = 0;
for (kk3 < nfixed_v) {
aa2 = aa2.next;
kk3 += 1;
};
let j: i32 = 0;
let prevarg: *node = n.list;
if (nfixed_v == 0) { prevarg = nil; }
else {
let kk4: i32 = 0;
for (kk4 < nfixed_v - 1) {
prevarg = prevarg.next;
kk4 += 1;
};
};
for (aa2 != nil) {
let slot: i32 = doff + j * esz;
if (velemtagged) {
cgwidentaggedstore(c, varp.lhs,
aa2, slot, esz);
} else { if (velemstr) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot + 8): i64);
emitline("(BP)\n");
} else { if (velemslice) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((slot + 16): i64);
emitline("(BP)\n");
} else {
cgexpr(c, aa2);
let op: str = "MOVQ";
if (esz == 1) { op = "MOVB"; }
else { if (esz == 4) { op = "MOVL"; }; };
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
}; }; };
j += 1;
aa2 = aa2.next;
};
if (nvar > 0) {
emitline("\tLEAQ\t");
emitoff(doff: i64);
emitline("(BP), AX\n");
} else {
emitline("\tXORQ\tAX, AX\n");
};
emitline("\tMOVQ\tAX, ");
emitoff(soff: i64);
emitline("(BP)\n");
emitline("\tMOVQ\t$");
emitint(nvar: i64);
emitline(", AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((soff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tAX, ");
emitoff((soff + 16): i64);
emitline("(BP)\n");
let sn: *node = newnode(c.a, nkind.N_IDENT,
"", 0, 0);
sn.str = sname;
if (prevarg == nil) { n.list = sn; }
else { prevarg.next = sn; };
};
};
};
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
// Pop forward. Float args were pushed as 8 bytes from X0 via
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else

View File

@@ -20,6 +20,81 @@ use typ;
use sym;
use strconv;
// ---- variadic-call helpers (Hare-style `T...` param) -----------------
// slicewrap — synthesise an N_TSLICE node wrapping the given element
// type AST. Used by the Hare-style variadic path so the local entry
// for the param (callee side) and the call-site slice descriptor
// (caller side) both advertise their effective type as []ELEM —
// every isslicetype / nodeisslice check then succeeds naturally.
fn slicewrap(c: *cgen, elem: *node) *node = {
let s: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
s.lhs = elem;
return s;
};
// findvariadicparam — walk a param-list head and return the variadic
// param node (the one with op == TK_ELLIPSIS) plus the count of
// non-variadic params before it. Returns nil/0 when no variadic.
// nfixed_out cannot be nil.
fn findvariadicparam(ps: *node, nfixed_out: *i32) *node = {
*nfixed_out = 0;
let p: *node = ps;
for (p != nil) {
if (p.kind == nkind.N_PARAM) {
if (p.op == tkind.TK_ELLIPSIS) {
return p;
};
*nfixed_out += 1;
};
p = p.next;
};
return nil;
};
// callee_variadic_param — convenience wrapper: looks up the callee
// by name and finds its variadic param + nfixed. Returns nil if the
// callee isn't registered or has no variadic param.
fn callee_variadic_param(c: *cgen, callee: *node, nfixed_out: *i32) *node = {
*nfixed_out = 0;
if (callee == nil) { return nil; };
let cnm: str;
cnm.ptr = nil; cnm.len = 0;
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
if (cnm.len == 0) { return nil; };
let ps: *node = fnparamslookup(c, cnm);
return findvariadicparam(ps, nfixed_out);
};
// mkvarargname — fresh local-slot name "<prefix><seq>". Used for
// the per-variadic-call scratch buffers (`@vararg_d_N` for the
// element-data buffer, `@vararg_sl_N` for the 24B slice descriptor)
// where N is recorded on the N_CALL node at scanlocals time so both
// the prologue reservation and the call-site emission agree.
fn mkvarargname(c: *cgen, prefix: str, seq: i32) str = {
let buf: [128]u8;
let i: i32 = 0;
let j: i32 = 0;
for (j < prefix.len) {
buf[i] = prefix[j];
i += 1; j += 1;
};
let ns: str = strconv.i64tos(seq: i64, strconv.base.DEC);
let n: i32 = ns.len;
let dk: i32 = 0;
for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; };
let total: i32 = i + n;
let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8;
let k: i32 = 0;
for (k < total) { p[k] = buf[k]; k += 1; };
p[total] = 0u8;
let r: str;
r.ptr = p;
r.len = total;
return r;
};
// ---- expression cgen -------------------------------------------------
// pushargsrev — recursively walks the arg list, evaluates rightmost
@@ -45,21 +120,31 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
let widentag: i32 = 0;
if (param != nil) {
if (param.kind == nkind.N_PARAM) {
let ptype: *node = param.lhs;
if (istaggedtype(c, ptype)) {
let aistagged: bool = false;
if (arg.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, arg.str);
if (lc != nil) {
aistagged = istaggedtype(c, lc.tnode);
// Hare-style variadic `T...`: effective param type is
// []T (slice). The arg here is the synthesised slice
// descriptor (or a forwarded `xs...` slice), not a
// value of T being widened into a tagged slot — skip
// the widening detection so the slice-ident fast path
// at the bottom of pushargsrev gets the push.
if (param.op == tkind.TK_ELLIPSIS) {
widensz = 0;
} else {
let ptype: *node = param.lhs;
if (istaggedtype(c, ptype)) {
let aistagged: bool = false;
if (arg.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, arg.str);
if (lc != nil) {
aistagged = istaggedtype(c, lc.tnode);
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
let t: i32 = taggedvariantindex(c, tagged, arg);
if (t < 0) { t = 0; };
widentag = t;
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
let t: i32 = taggedvariantindex(c, tagged, arg);
if (t < 0) { t = 0; };
widentag = t;
};
};
};
@@ -1487,6 +1572,16 @@ fn rhstargetname(c: *cgen, rhs: *node) str = {
return nm;
};
if (rhs.kind == nkind.N_STRLIT) { return "str"; };
if (rhs.kind == nkind.N_TRUE) { return "bool"; };
if (rhs.kind == nkind.N_FALSE) { return "bool"; };
if (rhs.kind == nkind.N_RUNELIT) { return "rune"; };
if (rhs.kind == nkind.N_INTLIT) {
// Typed int literal (`42i64`, `3u8`): suffix names the
// concrete variant so flatvariantidx finds it. Untyped
// literals (tsuffix=="") fall through to the isstr scan.
let s: str = rhs.tsuffix;
if (s.len > 0) { return s; };
};
// `T{}` carries its type name on the lhs N_IDENT — the parser
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
// Needed so `return eof{};` (variant of a tagged union) resolves

View File

@@ -3639,8 +3639,17 @@ fn parseparams(p: *parser) *node = {
n.str = id;
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
n.lhs = parsetype(p);
// Hare-style variadic: `name: T...`. Marker on n.op so check
// promotes the param's type to []T and call sites gather /
// forward. Mirrors cmd/wcc/parse.c parseparams.
if (accepttok(p, tkind.TK_ELLIPSIS)) {
n.op = tkind.TK_ELLIPSIS;
};
if (head == nil) { head = n; tail = n; }
else { tail.next = n; tail = n; };
if (n.op == tkind.TK_ELLIPSIS) {
break; // variadic must be the last param
};
if (!accepttok(p, tkind.TK_COMMA)) { break; };
if (p.curkind == tkind.TK_RPAREN) { break; };
};
@@ -5568,6 +5577,81 @@ use typ;
use sym;
use strconv;
// ---- variadic-call helpers (Hare-style `T...` param) -----------------
// slicewrap — synthesise an N_TSLICE node wrapping the given element
// type AST. Used by the Hare-style variadic path so the local entry
// for the param (callee side) and the call-site slice descriptor
// (caller side) both advertise their effective type as []ELEM —
// every isslicetype / nodeisslice check then succeeds naturally.
fn slicewrap(c: *cgen, elem: *node) *node = {
let s: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
s.lhs = elem;
return s;
};
// findvariadicparam — walk a param-list head and return the variadic
// param node (the one with op == TK_ELLIPSIS) plus the count of
// non-variadic params before it. Returns nil/0 when no variadic.
// nfixed_out cannot be nil.
fn findvariadicparam(ps: *node, nfixed_out: *i32) *node = {
*nfixed_out = 0;
let p: *node = ps;
for (p != nil) {
if (p.kind == nkind.N_PARAM) {
if (p.op == tkind.TK_ELLIPSIS) {
return p;
};
*nfixed_out += 1;
};
p = p.next;
};
return nil;
};
// callee_variadic_param — convenience wrapper: looks up the callee
// by name and finds its variadic param + nfixed. Returns nil if the
// callee isn't registered or has no variadic param.
fn callee_variadic_param(c: *cgen, callee: *node, nfixed_out: *i32) *node = {
*nfixed_out = 0;
if (callee == nil) { return nil; };
let cnm: str;
cnm.ptr = nil; cnm.len = 0;
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
if (cnm.len == 0) { return nil; };
let ps: *node = fnparamslookup(c, cnm);
return findvariadicparam(ps, nfixed_out);
};
// mkvarargname — fresh local-slot name "<prefix><seq>". Used for
// the per-variadic-call scratch buffers (`@vararg_d_N` for the
// element-data buffer, `@vararg_sl_N` for the 24B slice descriptor)
// where N is recorded on the N_CALL node at scanlocals time so both
// the prologue reservation and the call-site emission agree.
fn mkvarargname(c: *cgen, prefix: str, seq: i32) str = {
let buf: [128]u8;
let i: i32 = 0;
let j: i32 = 0;
for (j < prefix.len) {
buf[i] = prefix[j];
i += 1; j += 1;
};
let ns: str = strconv.i64tos(seq: i64, strconv.base.DEC);
let n: i32 = ns.len;
let dk: i32 = 0;
for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; };
let total: i32 = i + n;
let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8;
let k: i32 = 0;
for (k < total) { p[k] = buf[k]; k += 1; };
p[total] = 0u8;
let r: str;
r.ptr = p;
r.len = total;
return r;
};
// ---- expression cgen -------------------------------------------------
// pushargsrev — recursively walks the arg list, evaluates rightmost
@@ -5593,21 +5677,31 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
let widentag: i32 = 0;
if (param != nil) {
if (param.kind == nkind.N_PARAM) {
let ptype: *node = param.lhs;
if (istaggedtype(c, ptype)) {
let aistagged: bool = false;
if (arg.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, arg.str);
if (lc != nil) {
aistagged = istaggedtype(c, lc.tnode);
// Hare-style variadic `T...`: effective param type is
// []T (slice). The arg here is the synthesised slice
// descriptor (or a forwarded `xs...` slice), not a
// value of T being widened into a tagged slot — skip
// the widening detection so the slice-ident fast path
// at the bottom of pushargsrev gets the push.
if (param.op == tkind.TK_ELLIPSIS) {
widensz = 0;
} else {
let ptype: *node = param.lhs;
if (istaggedtype(c, ptype)) {
let aistagged: bool = false;
if (arg.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, arg.str);
if (lc != nil) {
aistagged = istaggedtype(c, lc.tnode);
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
let t: i32 = taggedvariantindex(c, tagged, arg);
if (t < 0) { t = 0; };
widentag = t;
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
let t: i32 = taggedvariantindex(c, tagged, arg);
if (t < 0) { t = 0; };
widentag = t;
};
};
};
@@ -7035,6 +7129,16 @@ fn rhstargetname(c: *cgen, rhs: *node) str = {
return nm;
};
if (rhs.kind == nkind.N_STRLIT) { return "str"; };
if (rhs.kind == nkind.N_TRUE) { return "bool"; };
if (rhs.kind == nkind.N_FALSE) { return "bool"; };
if (rhs.kind == nkind.N_RUNELIT) { return "rune"; };
if (rhs.kind == nkind.N_INTLIT) {
// Typed int literal (`42i64`, `3u8`): suffix names the
// concrete variant so flatvariantidx finds it. Untyped
// literals (tsuffix=="") fall through to the isstr scan.
let s: str = rhs.tsuffix;
if (s.len > 0) { return s; };
};
// `T{}` carries its type name on the lhs N_IDENT — the parser
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
// Needed so `return eof{};` (variant of a tagged union) resolves
@@ -9735,6 +9839,147 @@ fn cgcall(c: *cgen, n: *node) void = {
calleeparams = fnparamslookup(c, callee.str);
};
};
// Hare-style variadic last param: gather N tail args into a
// frame-resident [N]T (`@vararg_d_<seq>`) plus a 24B slice
// descriptor (`@vararg_sl_<seq>`), then splice a synthesised
// N_IDENT pointing at the descriptor into n.list so the rest
// of the call machinery sees one slice slot for the variadic.
// Forwarding shape (`xs...`) skips the gather: the spread's
// inner slice expression replaces the wrapper in place. Empty
// (no trailing args) writes a {nil, 0, 0} descriptor. The seq
// matches the one scanlocals stamped on n.uval.
{
let nfixed_v: i32 = 0;
let varp: *node = callee_variadic_param(c, callee, &nfixed_v);
if (varp != nil) {
let nargs0: i32 = 0;
let aw: *node = n.list;
for (aw != nil) { nargs0 += 1; aw = aw.next; };
let nvar: i32 = nargs0 - nfixed_v;
if (nvar < 0) { nvar = 0; };
let forwarding: bool = false;
if (nvar == 1) {
let aaf: *node = n.list;
let kk: i32 = 0;
for (kk < nfixed_v) {
aaf = aaf.next;
kk += 1;
};
if (aaf != nil) {
if (aaf.kind == nkind.N_SPREAD) {
forwarding = true;
};
};
};
if (forwarding) {
let prev: *node = nil;
let cur2: *node = n.list;
let kk2: i32 = 0;
for (kk2 < nfixed_v) {
prev = cur2;
cur2 = cur2.next;
kk2 += 1;
};
let inner: *node = cur2.lhs;
if (inner != nil) { inner.next = nil; };
if (prev == nil) { n.list = inner; }
else { prev.next = inner; };
} else {
let seq: i32 = n.uval: i32;
let dname: str = mkvarargname(c, "@vararg_d_", seq);
let sname: str = mkvarargname(c, "@vararg_sl_", seq);
let esz: i32 = slotsize(c, varp.lhs);
if (esz < 1) { esz = 1; };
let velemtagged: bool = istaggedtype(c, varp.lhs);
let velemstr: bool = isstrtype(c, varp.lhs);
let velemslice: bool = isslicetype(c, varp.lhs);
let doff: i32 = 0;
if (nvar > 0) {
doff = localadd(c, dname, nvar * esz, nil);
};
let soff: i32 = localadd(c, sname, 24,
slicewrap(c, varp.lhs));
let aa2: *node = n.list;
let kk3: i32 = 0;
for (kk3 < nfixed_v) {
aa2 = aa2.next;
kk3 += 1;
};
let j: i32 = 0;
let prevarg: *node = n.list;
if (nfixed_v == 0) { prevarg = nil; }
else {
let kk4: i32 = 0;
for (kk4 < nfixed_v - 1) {
prevarg = prevarg.next;
kk4 += 1;
};
};
for (aa2 != nil) {
let slot: i32 = doff + j * esz;
if (velemtagged) {
cgwidentaggedstore(c, varp.lhs,
aa2, slot, esz);
} else { if (velemstr) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot + 8): i64);
emitline("(BP)\n");
} else { if (velemslice) {
cgexpr(c, aa2);
emitline("\tMOVQ\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((slot + 16): i64);
emitline("(BP)\n");
} else {
cgexpr(c, aa2);
let op: str = "MOVQ";
if (esz == 1) { op = "MOVB"; }
else { if (esz == 4) { op = "MOVL"; }; };
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff(slot: i64);
emitline("(BP)\n");
}; }; };
j += 1;
aa2 = aa2.next;
};
if (nvar > 0) {
emitline("\tLEAQ\t");
emitoff(doff: i64);
emitline("(BP), AX\n");
} else {
emitline("\tXORQ\tAX, AX\n");
};
emitline("\tMOVQ\tAX, ");
emitoff(soff: i64);
emitline("(BP)\n");
emitline("\tMOVQ\t$");
emitint(nvar: i64);
emitline(", AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((soff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tAX, ");
emitoff((soff + 16): i64);
emitline("(BP)\n");
let sn: *node = newnode(c.a, nkind.N_IDENT,
"", 0, 0);
sn.str = sname;
if (prevarg == nil) { n.list = sn; }
else { prevarg.next = sn; };
};
};
};
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
// Pop forward. Float args were pushed as 8 bytes from X0 via
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
@@ -12393,6 +12638,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); };
@@ -12424,6 +12713,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.
@@ -12597,15 +12919,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) {
@@ -13022,6 +13349,11 @@ type cgen = struct {
yieldbuf: *str, // stack of match end labels for yield
defertop: i32,
deferbuf: **node, // stack of deferred exprs (LIFO at return)
// Variadic-call gather state. scanlocals walks the body in pre-
// order DFS and assigns per-call scratch names `@vararg_d_N` /
// `@vararg_sl_N` using this counter; cgcall resets and walks in
// the same order so the names line up at emission time.
varargseq: i32,
};
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
@@ -13042,6 +13374,7 @@ fn cgeninit(c: *cgen, a: *arena) void = {
c.frame = 0;
c.lastwasreturn = 0;
c.labelseq = 0;
c.varargseq = 0;
// Note: strlit_seq, strlits, ffis are *not* reset here; they
// persist across cgfn calls within one file. cgfile resets them
// at the start of each compilation unit.

View File

@@ -1558,6 +1558,75 @@ static const struct row rows[] = {
" };\n"
" return s;\n"
"};", 42 }, /* 1 + 2 + 39 = 42 */
/* Hare-style variadic gather: `args: T...` declares an N-arg
* variadic; the call-site materialises N values into a fresh
* `[N]T` and synthesises a {ptr,len,cap} slice for the param.
* Plain element type (i64): no widening, MOVQ-per-element. */
{ "fn sum(args: i64...) i64 = {\n"
" let s: i64 = 0i64;\n"
" let i: i32 = 0;\n"
" for (i < args.len) { s += args[i]; i += 1; };\n"
" return s;\n"
"};\n"
"fn main() i32 = {\n"
" return sum(1i64, 2i64, 3i64, 7i64, 9i64, 20i64): i32;\n"
"};", 42 },
/* Variadic with zero args: empty-slice descriptor {nil,0,0}.
* Confirms the gather path doesn't crash on N=0. */
{ "fn sum(args: i64...) i64 = {\n"
" let s: i64 = 0i64;\n"
" let i: i32 = 0;\n"
" for (i < args.len) { s += args[i]; i += 1; };\n"
" return s;\n"
"};\n"
"fn main() i32 = {\n"
" let a: i64 = sum();\n"
" let b: i64 = sum(42i64);\n"
" return (a + b): i32;\n"
"};", 42 },
/* Variadic with tagged-union element type: each gathered arg
* widens to the variant's slot shape (tag@+0, payload@+8). The
* runtime match-dispatch reads (i64=1)+(str.len=2)+(bool=39)=42. */
{ "type formattable = (i64 | str | bool);\n"
"fn sumtag(args: formattable...) i64 = {\n"
" let s: i64 = 0i64;\n"
" let i: i32 = 0;\n"
" for (i < args.len) {\n"
" match (args[i]) {\n"
" case let n: i64 => s += n;\n"
" case let v: str => s += v.len: i64;\n"
" case let b: bool => { if (b) { s += 39i64; }; };\n"
" };\n"
" i += 1;\n"
" };\n"
" return s;\n"
"};\n"
"fn main() i32 = { return sumtag(1i64, \"hi\", true): i32; };", 42 },
/* Variadic forwarding: `wrap(args...)` passes the local slice
* directly to `sum`, no re-gather. Mirrors Hare's wrapper shape
* (`fn println(args: formattable...) = fprintln(os.stdout, args...)`). */
{ "fn sum(args: i64...) i64 = {\n"
" let s: i64 = 0i64;\n"
" let i: i32 = 0;\n"
" for (i < args.len) { s += args[i]; i += 1; };\n"
" return s;\n"
"};\n"
"fn wrap(prefix: i64, args: i64...) i64 = {\n"
" return prefix + sum(args...);\n"
"};\n"
"fn main() i32 = {\n"
" return wrap(2i64, 1i64, 2i64, 3i64, 4i64, 5i64, 7i64, 18i64): i32;\n"
"};", 42 },
/* lib/fmt user-side: `fmt.println(args: formattable...)` gathers
* mixed-type args at the call site. End-to-end exercises the
* lib/fmt graduation: the wrapper-chain `println → fprintln →
* fprint` is itself variadic-forwarding, so this validates both
* gather (at main) and `args...` forward (inside lib/fmt). The
* exit code is bytes printed (`hello 7\n` = 8). */
{ "use fmt;\n"
"fn main() i32 = {\n"
" return fmt.println(\"hello\", 7i64): i32;\n"
"};", 8 },
{ NULL, 0 }
};