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 {
|
||||
|
||||
@@ -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))`,
|
||||
|
||||
120
lib/fmt/fmt.ww
120
lib/fmt/fmt.ww
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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; };
|
||||
};
|
||||
|
||||
@@ -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,6 +5677,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
let widentag: i32 = 0;
|
||||
if (param != nil) {
|
||||
if (param.kind == nkind.N_PARAM) {
|
||||
// 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;
|
||||
@@ -5612,6 +5705,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (widensz == 8) {
|
||||
// Nullable fold: pointer value IS the discriminator. No
|
||||
// separate tag word.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +120,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
let widentag: i32 = 0;
|
||||
if (param != nil) {
|
||||
if (param.kind == nkind.N_PARAM) {
|
||||
// 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;
|
||||
@@ -64,6 +148,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (widensz == 8) {
|
||||
// Nullable fold: pointer value IS the discriminator. No
|
||||
// separate tag word.
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +5677,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
let widentag: i32 = 0;
|
||||
if (param != nil) {
|
||||
if (param.kind == nkind.N_PARAM) {
|
||||
// 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;
|
||||
@@ -5612,6 +5705,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (widensz == 8) {
|
||||
// Nullable fold: pointer value IS the discriminator. No
|
||||
// separate tag word.
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user