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;
|
break;
|
||||||
}
|
}
|
||||||
/* up to 6 integer + 8 float args via SysV registers.
|
/* 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;
|
int argcount = 0;
|
||||||
Node *args[16] = {0};
|
Node *args[64] = {0};
|
||||||
for (Node *a = n->list; a; a = a->next)
|
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
|
/* Resolve callee fn-type so we can match each arg against
|
||||||
* its declared parameter type — needed to detect implicit
|
* its declared parameter type — needed to detect implicit
|
||||||
* widening of a concrete variant into a tagged-union slot. */
|
* 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;
|
callee_t->under : callee_t;
|
||||||
Tparam *callee_params = (cu && cu->kind == TY_FN) ?
|
Tparam *callee_params = (cu && cu->kind == TY_FN) ?
|
||||||
cu->params : NULL;
|
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.
|
/* widen[i]: param is tagged and arg needs re-layout.
|
||||||
* - arg is a concrete variant (str/struct/scalar) — wrap
|
* - arg is a concrete variant (str/struct/scalar) — wrap
|
||||||
* in the param's slot shape.
|
* in the param's slot shape.
|
||||||
* - arg is itself a tagged union of a subset/different
|
* - arg is itself a tagged union of a subset/different
|
||||||
* variant set — copy the slot words and remap the tag.
|
* variant set — copy the slot words and remap the tag.
|
||||||
* Identical types pass through unchanged. */
|
* Identical types pass through unchanged. */
|
||||||
int widen[16] = {0};
|
int widen[64] = {0};
|
||||||
int widen_sz[16] = {0};
|
int widen_sz[64] = {0};
|
||||||
Type *widen_param[16] = {0};
|
Type *widen_param[64] = {0};
|
||||||
{
|
{
|
||||||
Tparam *p = callee_params;
|
Tparam *p = callee_params;
|
||||||
for (int i = 0; i < argcount; i++) {
|
for (int i = 0; i < argcount; i++) {
|
||||||
|
|||||||
@@ -404,7 +404,17 @@ resolve_type(Checker *c, Node *n)
|
|||||||
}
|
}
|
||||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||||
tp->name = p->str;
|
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;
|
if (head == NULL) head = tp;
|
||||||
else tail->next = tp;
|
else tail->next = tp;
|
||||||
tail = tp;
|
tail = tp;
|
||||||
@@ -962,12 +972,38 @@ cexpr(Checker *c, Node *n)
|
|||||||
err(c, n->pos, "too many arguments");
|
err(c, n->pos, "too many arguments");
|
||||||
continue;
|
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)
|
if (!type_assignable(p->type, at) && at != ty_err && p->type != ty_err)
|
||||||
err(c, a->pos, "argument type %s not assignable to %s",
|
err(c, a->pos, "argument type %s not assignable to %s",
|
||||||
type_name(c->a, at), type_name(c->a, p->type));
|
type_name(c->a, at), type_name(c->a, p->type));
|
||||||
p = p->next;
|
p = p->next;
|
||||||
}
|
}
|
||||||
if (p != NULL)
|
if (p != NULL && !p->variadic)
|
||||||
err(c, n->pos, "not enough arguments");
|
err(c, n->pos, "not enough arguments");
|
||||||
return n->type = u->ret ? u->ret : ty_void;
|
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);
|
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||||
tp->name = p->str;
|
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;
|
if (head == NULL) head = tp;
|
||||||
else tail->next = tp;
|
else tail->next = tp;
|
||||||
tail = tp;
|
tail = tp;
|
||||||
|
|||||||
@@ -135,9 +135,18 @@ parseparams(Parser *p)
|
|||||||
n->strlen = 0;
|
n->strlen = 0;
|
||||||
n->lhs = parsetype(p);
|
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;
|
if (head == NULL) head = n;
|
||||||
else tail->next = n;
|
else tail->next = n;
|
||||||
tail = n;
|
tail = n;
|
||||||
|
if (n->op == TK_ELLIPSIS)
|
||||||
|
break; /* Hare-style variadic must be the last param */
|
||||||
if (!accept(p, TK_COMMA))
|
if (!accept(p, TK_COMMA))
|
||||||
break;
|
break;
|
||||||
if (p->cur.kind == TK_RPAREN) /* trailing comma */
|
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;
|
if (!type_eq(a->ret, b->ret)) return 0;
|
||||||
Tparam *pa = a->params, *pb = b->params;
|
Tparam *pa = a->params, *pb = b->params;
|
||||||
while (pa && pb) {
|
while (pa && pb) {
|
||||||
|
if (pa->variadic != pb->variadic) return 0;
|
||||||
if (!type_eq(pa->type, pb->type)) return 0;
|
if (!type_eq(pa->type, pb->type)) return 0;
|
||||||
pa = pa->next; pb = pb->next;
|
pa = pa->next; pb = pb->next;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,6 +403,9 @@ struct Tparam {
|
|||||||
const char *name;
|
const char *name;
|
||||||
Type *type;
|
Type *type;
|
||||||
Tparam *next;
|
Tparam *next;
|
||||||
|
int variadic; /* Hare-style `T...` — `type` is []T,
|
||||||
|
* call site gathers / forwards. Distinct
|
||||||
|
* from Type.variadic (C-style FFI `...`). */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Type {
|
struct Type {
|
||||||
|
|||||||
@@ -26,18 +26,14 @@ Signatures mirror Hare too, modulo:
|
|||||||
buffer (`strings.dup`, `strings.concat`) still return an owned
|
buffer (`strings.dup`, `strings.concat`) still return an owned
|
||||||
`str` that callers free via `os.free(r.ptr, r.len: u64)`.
|
`str` that callers free via `os.free(r.ptr, r.len: u64)`.
|
||||||
|
|
||||||
- Call-site variadic sugar (`fmt::println(42)`) doesn't land yet.
|
- Call-site variadic sugar matches Hare. `fn f(args: T...)` declares
|
||||||
The receive side does — `fmt.formattable` is a tagged union of
|
a Hare-style variadic; call sites either gather N args into a
|
||||||
the printable scalar types, and `fmt.printv` / `fmt.printlnv`
|
fresh `[]T` (`fmt.println(42, "hi", true)`) or forward an existing
|
||||||
take an explicit `[]formattable` slice. Until the call-site
|
slice with `xs...` (`fprintln(fd, args...)`). The bare `T...` form
|
||||||
gather is implemented, callers either hand-build the slice:
|
in tagged unions still means spread-flatten (`(...inner | E)`);
|
||||||
let args: [2]fmt.formattable;
|
the two uses don't overlap because `T...` only attaches to a
|
||||||
args[0] = "count: ": fmt.formattable;
|
*param* decl. `lib/fmt` is intentionally print-string-only — no
|
||||||
args[1] = 42i64: fmt.formattable;
|
`printf`-family.
|
||||||
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.
|
|
||||||
|
|
||||||
- `(T | U)` sum-typed parameters dispatch via `match` inside the
|
- `(T | U)` sum-typed parameters dispatch via `match` inside the
|
||||||
callee. `strings.byteindex(haystack: str, needle: (str | rune))`,
|
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
|
// fmt — formatting writers. Mirrors Hare's lib/fmt subset that fmt-
|
||||||
// descriptor. Hare's variadic call-site sugar (`fmt::println(42)`
|
// prints values via [[io::handle]]-style fd writers. Call sites take
|
||||||
// gathering args into a `[]formattable`) isn't wired yet; until then,
|
// Hare's variadic shape: `fmt.println(42, "hi", true)` gathers the
|
||||||
// callers either:
|
// args into a `[]formattable` slice; wrappers forward via `args...`.
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
use os;
|
use os;
|
||||||
use strconv;
|
use strconv;
|
||||||
use strings;
|
use strings;
|
||||||
|
|
||||||
// formattable — tagged union of types fmt can render. Mirrors
|
// formattable — tagged union of types fmt can render. Mirrors Hare's
|
||||||
// Hare's `fmt::formattable = (...types::numeric | uintptr | str |
|
// `fmt::formattable = (...types::numeric | uintptr | str | rune |
|
||||||
// rune | bool | nullable *opaque | void)`, narrowed to the set ww
|
// bool | nullable *opaque | void)`, narrowed to the set ww actually
|
||||||
// actually has codegen for. Slot size is 24B (8 tag + 16 str
|
// has codegen for. Slot size is 24B (8 tag + 16 str payload).
|
||||||
// payload).
|
|
||||||
export type formattable = (i64 | str | bool | rune);
|
export type formattable = (i64 | str | bool | rune);
|
||||||
|
|
||||||
// vprint — write the formatted form of each element of `args` to
|
// fprint — write the formatted form of each `args` element to `fd`,
|
||||||
// `fd`. Returns total bytes written or the first negative os.write
|
// separated by spaces. Returns total bytes written or the first
|
||||||
// result.
|
// negative os.write result. Hare's separator-by-space matches.
|
||||||
fn vprint(fd: i32, args: []formattable) i64 = {
|
export fn fprint(fd: i32, args: formattable...) i64 = {
|
||||||
let total: i64 = 0;
|
let total: i64 = 0;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < args.len) {
|
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]) {
|
match (args[i]) {
|
||||||
case let n: i64 => {
|
case let n: i64 => {
|
||||||
let s: str = strconv.i64tos(n, strconv.base.DEC);
|
let s: str = strconv.i64tos(n, strconv.base.DEC);
|
||||||
@@ -64,55 +57,40 @@ fn vprint(fd: i32, args: []formattable) i64 = {
|
|||||||
return total;
|
return total;
|
||||||
};
|
};
|
||||||
|
|
||||||
// print(s: str) — single-string form for the common case. The
|
// fprintln — fprint plus a trailing newline.
|
||||||
// variadic-style `print(args: []formattable)` lives as `printv`
|
export fn fprintln(fd: i32, args: formattable...) i64 = {
|
||||||
// until call-site sugar lands.
|
let n: i64 = fprint(fd, args...);
|
||||||
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);
|
|
||||||
if (n < 0) { return n; };
|
if (n < 0) { return n; };
|
||||||
let m: i64 = os.write(fd, "\n".ptr, 1u64);
|
let m: i64 = os.write(fd, "\n".ptr, 1u64);
|
||||||
if (m < 0) { return m; };
|
if (m < 0) { return m; };
|
||||||
return n + 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;
|
n.str = id;
|
||||||
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
|
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
|
||||||
n.lhs = parsetype(p);
|
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; }
|
if (head == nil) { head = n; tail = n; }
|
||||||
else { tail.next = 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 (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||||
if (p.curkind == tkind.TK_RPAREN) { break; };
|
if (p.curkind == tkind.TK_RPAREN) { break; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3639,8 +3639,17 @@ fn parseparams(p: *parser) *node = {
|
|||||||
n.str = id;
|
n.str = id;
|
||||||
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
|
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
|
||||||
n.lhs = parsetype(p);
|
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; }
|
if (head == nil) { head = n; tail = n; }
|
||||||
else { tail.next = 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 (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||||
if (p.curkind == tkind.TK_RPAREN) { break; };
|
if (p.curkind == tkind.TK_RPAREN) { break; };
|
||||||
};
|
};
|
||||||
@@ -5568,6 +5577,81 @@ use typ;
|
|||||||
use sym;
|
use sym;
|
||||||
use strconv;
|
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 -------------------------------------------------
|
// ---- expression cgen -------------------------------------------------
|
||||||
|
|
||||||
// pushargsrev — recursively walks the arg list, evaluates rightmost
|
// 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;
|
let widentag: i32 = 0;
|
||||||
if (param != nil) {
|
if (param != nil) {
|
||||||
if (param.kind == nkind.N_PARAM) {
|
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;
|
let ptype: *node = param.lhs;
|
||||||
if (istaggedtype(c, ptype)) {
|
if (istaggedtype(c, ptype)) {
|
||||||
let aistagged: bool = false;
|
let aistagged: bool = false;
|
||||||
@@ -5612,6 +5705,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
if (widensz == 8) {
|
if (widensz == 8) {
|
||||||
// Nullable fold: pointer value IS the discriminator. No
|
// Nullable fold: pointer value IS the discriminator. No
|
||||||
// separate tag word.
|
// separate tag word.
|
||||||
@@ -7035,6 +7129,16 @@ fn rhstargetname(c: *cgen, rhs: *node) str = {
|
|||||||
return nm;
|
return nm;
|
||||||
};
|
};
|
||||||
if (rhs.kind == nkind.N_STRLIT) { return "str"; };
|
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
|
// `T{}` carries its type name on the lhs N_IDENT — the parser
|
||||||
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
|
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
|
||||||
// Needed so `return eof{};` (variant of a tagged union) resolves
|
// 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);
|
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);
|
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// 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.lhs != nil) { total += scanlocals(c, n.lhs); };
|
||||||
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
||||||
@@ -12424,6 +12713,39 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
|||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
if (p.kind == nkind.N_PARAM) {
|
if (p.kind == nkind.N_PARAM) {
|
||||||
let nm: str = p.str;
|
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)) {
|
if (isfloattype(c, p.lhs)) {
|
||||||
// Float param: SysV uses the XMM stream
|
// Float param: SysV uses the XMM stream
|
||||||
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
||||||
@@ -12597,15 +12919,20 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
|||||||
let frame: i32 = 0;
|
let frame: i32 = 0;
|
||||||
for (scanp != nil) {
|
for (scanp != nil) {
|
||||||
if (scanp.kind == nkind.N_PARAM) {
|
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 (isslicetype(c, scanp.lhs)) { frame += 24; }
|
||||||
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
||||||
else { frame += 8; }; }; };
|
else { frame += 8; }; }; }; };
|
||||||
scanseenmark(c, scanp.str);
|
scanseenmark(c, scanp.str);
|
||||||
};
|
};
|
||||||
scanp = scanp.next;
|
scanp = scanp.next;
|
||||||
};
|
};
|
||||||
|
c.varargseq = 0;
|
||||||
if (fn_.body != nil) { frame += scanlocals(c, fn_.body); };
|
if (fn_.body != nil) { frame += scanlocals(c, fn_.body); };
|
||||||
|
c.varargseq = 0;
|
||||||
// Drop the stubs so emission rebuilds c.locals with real offsets.
|
// Drop the stubs so emission rebuilds c.locals with real offsets.
|
||||||
c.locals = nil;
|
c.locals = nil;
|
||||||
if ((frame & 15) != 0) {
|
if ((frame & 15) != 0) {
|
||||||
@@ -13022,6 +13349,11 @@ type cgen = struct {
|
|||||||
yieldbuf: *str, // stack of match end labels for yield
|
yieldbuf: *str, // stack of match end labels for yield
|
||||||
defertop: i32,
|
defertop: i32,
|
||||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
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.
|
// 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.frame = 0;
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
c.labelseq = 0;
|
c.labelseq = 0;
|
||||||
|
c.varargseq = 0;
|
||||||
// Note: strlit_seq, strlits, ffis are *not* reset here; they
|
// Note: strlit_seq, strlits, ffis are *not* reset here; they
|
||||||
// persist across cgfn calls within one file. cgfile resets them
|
// persist across cgfn calls within one file. cgfile resets them
|
||||||
// at the start of each compilation unit.
|
// at the start of each compilation unit.
|
||||||
|
|||||||
@@ -349,6 +349,11 @@ type cgen = struct {
|
|||||||
yieldbuf: *str, // stack of match end labels for yield
|
yieldbuf: *str, // stack of match end labels for yield
|
||||||
defertop: i32,
|
defertop: i32,
|
||||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
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.
|
// 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.frame = 0;
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
c.labelseq = 0;
|
c.labelseq = 0;
|
||||||
|
c.varargseq = 0;
|
||||||
// Note: strlit_seq, strlits, ffis are *not* reset here; they
|
// Note: strlit_seq, strlits, ffis are *not* reset here; they
|
||||||
// persist across cgfn calls within one file. cgfile resets them
|
// persist across cgfn calls within one file. cgfile resets them
|
||||||
// at the start of each compilation unit.
|
// 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.lhs != nil) { total += scanlocals(c, n.lhs); };
|
||||||
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
||||||
@@ -321,6 +365,39 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
|||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
if (p.kind == nkind.N_PARAM) {
|
if (p.kind == nkind.N_PARAM) {
|
||||||
let nm: str = p.str;
|
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)) {
|
if (isfloattype(c, p.lhs)) {
|
||||||
// Float param: SysV uses the XMM stream
|
// Float param: SysV uses the XMM stream
|
||||||
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
||||||
@@ -494,15 +571,20 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
|||||||
let frame: i32 = 0;
|
let frame: i32 = 0;
|
||||||
for (scanp != nil) {
|
for (scanp != nil) {
|
||||||
if (scanp.kind == nkind.N_PARAM) {
|
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 (isslicetype(c, scanp.lhs)) { frame += 24; }
|
||||||
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
||||||
else { frame += 8; }; }; };
|
else { frame += 8; }; }; }; };
|
||||||
scanseenmark(c, scanp.str);
|
scanseenmark(c, scanp.str);
|
||||||
};
|
};
|
||||||
scanp = scanp.next;
|
scanp = scanp.next;
|
||||||
};
|
};
|
||||||
|
c.varargseq = 0;
|
||||||
if (fn_.body != nil) { frame += scanlocals(c, fn_.body); };
|
if (fn_.body != nil) { frame += scanlocals(c, fn_.body); };
|
||||||
|
c.varargseq = 0;
|
||||||
// Drop the stubs so emission rebuilds c.locals with real offsets.
|
// Drop the stubs so emission rebuilds c.locals with real offsets.
|
||||||
c.locals = nil;
|
c.locals = nil;
|
||||||
if ((frame & 15) != 0) {
|
if ((frame & 15) != 0) {
|
||||||
|
|||||||
@@ -2142,6 +2142,147 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
calleeparams = fnparamslookup(c, callee.str);
|
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);
|
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||||
|
|||||||
@@ -20,6 +20,81 @@ use typ;
|
|||||||
use sym;
|
use sym;
|
||||||
use strconv;
|
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 -------------------------------------------------
|
// ---- expression cgen -------------------------------------------------
|
||||||
|
|
||||||
// pushargsrev — recursively walks the arg list, evaluates rightmost
|
// 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;
|
let widentag: i32 = 0;
|
||||||
if (param != nil) {
|
if (param != nil) {
|
||||||
if (param.kind == nkind.N_PARAM) {
|
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;
|
let ptype: *node = param.lhs;
|
||||||
if (istaggedtype(c, ptype)) {
|
if (istaggedtype(c, ptype)) {
|
||||||
let aistagged: bool = false;
|
let aistagged: bool = false;
|
||||||
@@ -64,6 +148,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
if (widensz == 8) {
|
if (widensz == 8) {
|
||||||
// Nullable fold: pointer value IS the discriminator. No
|
// Nullable fold: pointer value IS the discriminator. No
|
||||||
// separate tag word.
|
// separate tag word.
|
||||||
@@ -1487,6 +1572,16 @@ fn rhstargetname(c: *cgen, rhs: *node) str = {
|
|||||||
return nm;
|
return nm;
|
||||||
};
|
};
|
||||||
if (rhs.kind == nkind.N_STRLIT) { return "str"; };
|
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
|
// `T{}` carries its type name on the lhs N_IDENT — the parser
|
||||||
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
|
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
|
||||||
// Needed so `return eof{};` (variant of a tagged union) resolves
|
// Needed so `return eof{};` (variant of a tagged union) resolves
|
||||||
|
|||||||
@@ -3639,8 +3639,17 @@ fn parseparams(p: *parser) *node = {
|
|||||||
n.str = id;
|
n.str = id;
|
||||||
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
|
expecttok(p, tkind.TK_COLON, "expected ':' in parameter");
|
||||||
n.lhs = parsetype(p);
|
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; }
|
if (head == nil) { head = n; tail = n; }
|
||||||
else { tail.next = 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 (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||||
if (p.curkind == tkind.TK_RPAREN) { break; };
|
if (p.curkind == tkind.TK_RPAREN) { break; };
|
||||||
};
|
};
|
||||||
@@ -5568,6 +5577,81 @@ use typ;
|
|||||||
use sym;
|
use sym;
|
||||||
use strconv;
|
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 -------------------------------------------------
|
// ---- expression cgen -------------------------------------------------
|
||||||
|
|
||||||
// pushargsrev — recursively walks the arg list, evaluates rightmost
|
// 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;
|
let widentag: i32 = 0;
|
||||||
if (param != nil) {
|
if (param != nil) {
|
||||||
if (param.kind == nkind.N_PARAM) {
|
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;
|
let ptype: *node = param.lhs;
|
||||||
if (istaggedtype(c, ptype)) {
|
if (istaggedtype(c, ptype)) {
|
||||||
let aistagged: bool = false;
|
let aistagged: bool = false;
|
||||||
@@ -5612,6 +5705,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
if (widensz == 8) {
|
if (widensz == 8) {
|
||||||
// Nullable fold: pointer value IS the discriminator. No
|
// Nullable fold: pointer value IS the discriminator. No
|
||||||
// separate tag word.
|
// separate tag word.
|
||||||
@@ -7035,6 +7129,16 @@ fn rhstargetname(c: *cgen, rhs: *node) str = {
|
|||||||
return nm;
|
return nm;
|
||||||
};
|
};
|
||||||
if (rhs.kind == nkind.N_STRLIT) { return "str"; };
|
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
|
// `T{}` carries its type name on the lhs N_IDENT — the parser
|
||||||
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
|
// builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`.
|
||||||
// Needed so `return eof{};` (variant of a tagged union) resolves
|
// 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);
|
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);
|
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// 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.lhs != nil) { total += scanlocals(c, n.lhs); };
|
||||||
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
||||||
@@ -12424,6 +12713,39 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
|||||||
for (p != nil) {
|
for (p != nil) {
|
||||||
if (p.kind == nkind.N_PARAM) {
|
if (p.kind == nkind.N_PARAM) {
|
||||||
let nm: str = p.str;
|
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)) {
|
if (isfloattype(c, p.lhs)) {
|
||||||
// Float param: SysV uses the XMM stream
|
// Float param: SysV uses the XMM stream
|
||||||
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
||||||
@@ -12597,15 +12919,20 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
|||||||
let frame: i32 = 0;
|
let frame: i32 = 0;
|
||||||
for (scanp != nil) {
|
for (scanp != nil) {
|
||||||
if (scanp.kind == nkind.N_PARAM) {
|
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 (isslicetype(c, scanp.lhs)) { frame += 24; }
|
||||||
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
||||||
else { frame += 8; }; }; };
|
else { frame += 8; }; }; }; };
|
||||||
scanseenmark(c, scanp.str);
|
scanseenmark(c, scanp.str);
|
||||||
};
|
};
|
||||||
scanp = scanp.next;
|
scanp = scanp.next;
|
||||||
};
|
};
|
||||||
|
c.varargseq = 0;
|
||||||
if (fn_.body != nil) { frame += scanlocals(c, fn_.body); };
|
if (fn_.body != nil) { frame += scanlocals(c, fn_.body); };
|
||||||
|
c.varargseq = 0;
|
||||||
// Drop the stubs so emission rebuilds c.locals with real offsets.
|
// Drop the stubs so emission rebuilds c.locals with real offsets.
|
||||||
c.locals = nil;
|
c.locals = nil;
|
||||||
if ((frame & 15) != 0) {
|
if ((frame & 15) != 0) {
|
||||||
@@ -13022,6 +13349,11 @@ type cgen = struct {
|
|||||||
yieldbuf: *str, // stack of match end labels for yield
|
yieldbuf: *str, // stack of match end labels for yield
|
||||||
defertop: i32,
|
defertop: i32,
|
||||||
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
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.
|
// 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.frame = 0;
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
c.labelseq = 0;
|
c.labelseq = 0;
|
||||||
|
c.varargseq = 0;
|
||||||
// Note: strlit_seq, strlits, ffis are *not* reset here; they
|
// Note: strlit_seq, strlits, ffis are *not* reset here; they
|
||||||
// persist across cgfn calls within one file. cgfile resets them
|
// persist across cgfn calls within one file. cgfile resets them
|
||||||
// at the start of each compilation unit.
|
// at the start of each compilation unit.
|
||||||
|
|||||||
@@ -1558,6 +1558,75 @@ static const struct row rows[] = {
|
|||||||
" };\n"
|
" };\n"
|
||||||
" return s;\n"
|
" return s;\n"
|
||||||
"};", 42 }, /* 1 + 2 + 39 = 42 */
|
"};", 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 }
|
{ NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user