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