summary refs log tree commit diff
diff options
context:
space:
mode:
authorQuentin Carbonneaux <quentin.carbonneaux@yale.edu>2017-02-07 23:01:24 -0500
committerQuentin Carbonneaux <quentin.carbonneaux@yale.edu>2017-02-10 11:05:54 -0500
commitb99a8b0d07d43b89d5e27883ee5a9a67c2645809 (patch)
tree9a3f4ebcc0bb971a7e361115b8d9b19529902cb7
parent8799dc30ac472545bc93957c22f070590ff44cb3 (diff)
downloadroux-b99a8b0d07d43b89d5e27883ee5a9a67c2645809.tar.gz
support variable argument lists
This change is backward compatible, calls to
"variadic" functions (like printf) must now be
annotated (with ...).
-rw-r--r--all.h5
-rw-r--r--emit.c22
-rw-r--r--main.c1
-rw-r--r--parse.c35
-rw-r--r--sysv.c219
-rw-r--r--test/abi5.ssa18
-rw-r--r--test/echo.ssa2
-rw-r--r--tools/lexh.c1
8 files changed, 256 insertions, 47 deletions
diff --git a/all.h b/all.h
index 1ccf053..d13ef5c 100644
--- a/all.h
+++ b/all.h
@@ -256,6 +256,9 @@ enum Op {
 	Oalloc,
 	Oalloc1 = Oalloc + NAlign-1,
 
+	Ovastart,
+	Ovaarg,
+
 	Ocopy,
 	NPubOp,
 
@@ -265,6 +268,7 @@ enum Op {
 	Oarg,
 	Oargc,
 	Ocall,
+	Ovacall,
 
 	/* reserved instructions */
 	Onop,
@@ -442,6 +446,7 @@ struct Fn {
 	bits reg;
 	int slot;
 	char export;
+	char vararg;
 	char name[NString];
 };
 
diff --git a/emit.c b/emit.c
index 2c0b3cc..ccbd516 100644
--- a/emit.c
+++ b/emit.c
@@ -137,13 +137,14 @@ slot(int s, Fn *fn)
 
 	/* sign extend s using a bitfield */
 	x.i = s;
+	assert(x.i <= fn->slot);
 	/* specific to NAlign == 3 */
 	if (x.i < 0)
 		return -4 * x.i;
-	else {
-		assert(fn->slot >= x.i);
+	else if (fn->vararg)
+		return -176 + -4 * (fn->slot - x.i);
+	else
 		return -4 * (fn->slot - x.i);
-	}
 }
 
 static void
@@ -481,7 +482,7 @@ framesz(Fn *fn)
 		o ^= 1 & (fn->reg >> rclob[i]);
 	f = fn->slot;
 	f = (f + 3) & -4;
-	return 4*f + 8*o;
+	return 4*f + 8*o + 176*fn->vararg;
 }
 
 void
@@ -504,20 +505,27 @@ emitfn(Fn *fn, FILE *f)
 	static int id0;
 	Blk *b, *s;
 	Ins *i, itmp;
-	int *r, c, fs;
+	int *r, c, fs, o, n;
 
 	fprintf(f, ".text\n");
 	if (fn->export)
 		fprintf(f, ".globl %s%s\n", symprefix, fn->name);
 	fprintf(f,
 		"%s%s:\n"
-		"\tpush %%rbp\n"
-		"\tmov %%rsp, %%rbp\n",
+		"\tpushq %%rbp\n"
+		"\tmovq %%rsp, %%rbp\n",
 		symprefix, fn->name
 	);
 	fs = framesz(fn);
 	if (fs)
 		fprintf(f, "\tsub $%d, %%rsp\n", fs);
+	if (fn->vararg) {
+		o = -176;
+		for (r=rsave; r-rsave<6; ++r, o+=8)
+			fprintf(f, "\tmovq %%%s, %d(%%rbp)\n", rname[*r][0], o);
+		for (n=0; n<8; ++n, o+=16)
+			fprintf(f, "\tmovaps %%xmm%d, %d(%%rbp)\n", n, o);
+	}
 	for (r=rclob; r-rclob < NRClob; r++)
 		if (fn->reg & BIT(*r)) {
 			itmp.arg[0] = TMP(*r);
diff --git a/main.c b/main.c
index b60ded6..f93af37 100644
--- a/main.c
+++ b/main.c
@@ -63,6 +63,7 @@ func(Fn *fn)
 	filluse(fn);
 	fold(fn);
 	abi(fn);
+	fillpreds(fn);
 	filluse(fn);
 	isel(fn);
 	fillrpo(fn);
diff --git a/parse.c b/parse.c
index f8fd705..913fc07 100644
--- a/parse.c
+++ b/parse.c
@@ -65,11 +65,14 @@ OpDesc opdesc[NOp] = {
 	[Oarg]    = { "arg",      0, {A(w,l,s,d), A(x,x,x,x)}, 0, 0, 0 },
 	[Oargc]   = { "argc",     0, {A(e,x,e,e), A(e,l,e,e)}, 0, 0, 0 },
 	[Ocall]   = { "call",     0, {A(m,m,m,m), A(x,x,x,x)}, 0, 0, 0 },
+	[Ovacall] = { "vacall",   0, {A(m,m,m,m), A(x,x,x,x)}, 0, 0, 0 },
 	[Oxsetnp] = { "xsetnp",   0, {A(x,x,e,e), A(x,x,e,e)}, 0, 0, 0 },
 	[Oxsetp]  = { "xsetp",    0, {A(x,x,e,e), A(x,x,e,e)}, 0, 0, 0 },
 	[Oalloc]   = { "alloc4",  1, {A(e,l,e,e), A(e,x,e,e)}, 0, 0, 0 },
 	[Oalloc+1] = { "alloc8",  1, {A(e,l,e,e), A(e,x,e,e)}, 0, 0, 0 },
 	[Oalloc+2] = { "alloc16", 1, {A(e,l,e,e), A(e,x,e,e)}, 0, 0, 0 },
+	[Ovaarg]   = { "vaarg",   0, {A(m,m,m,m), A(x,x,x,x)}, 0, 0, 0 },
+	[Ovastart] = { "vastart", 0, {A(m,m,m,m), A(x,x,x,x)}, 0, 0, 0 },
 #define X(c) \
 	[Ocmpw+IC##c] = { "c"    #c "w", 0, {A(w,w,e,e), A(w,w,e,e)}, 1, 0, 1 }, \
 	[Ocmpl+IC##c] = { "c"    #c "l", 0, {A(l,l,e,e), A(l,l,e,e)}, 1, 0, 1 }, \
@@ -139,6 +142,7 @@ enum {
 	Tlbrace,
 	Trbrace,
 	Tnl,
+	Tdots,
 	Teof,
 
 	Ntok
@@ -168,13 +172,14 @@ static char *kwmap[Ntok] = {
 	[Td] = "d",
 	[Ts] = "s",
 	[Tz] = "z",
+	[Tdots] = "...",
 };
 
 enum {
 	BMask = 8191, /* for blocks hash */
 
-	K = 2047061843, /* found using tools/lexh.c */
-	M = 24,
+	K = 3233235, /* found using tools/lexh.c */
+	M = 23,
 };
 
 static char lexh[1 << (32-M)];
@@ -326,7 +331,7 @@ lex()
 	if (0)
 Alpha:		c = fgetc(inf);
 	if (!isalpha(c) && c != '.' && c != '_')
-		err("lexing failure: invalid character %c (%d)", c, c);
+		err("invalid character %c (%d)", c, c);
 	i = 0;
 	do {
 		if (i >= NString-1)
@@ -485,14 +490,14 @@ parsecls(int *tyn)
 	}
 }
 
-static void
+static int
 parserefl(int arg)
 {
 	int k, ty;
 	Ref r;
 
 	expect(Tlparen);
-	while (peek() != Trparen) {
+	while (peek() != Trparen && peek() != Tdots) {
 		if (curi - insb >= NIns)
 			err("too many instructions (1)");
 		k = parsecls(&ty);
@@ -516,7 +521,11 @@ parserefl(int arg)
 			break;
 		expect(Tcomma);
 	}
-	next();
+	if (next() == Tdots) {
+		expect(Trparen);
+		return 1;
+	}
+	return 0;
 }
 
 static Blk *
@@ -561,7 +570,9 @@ parseline(PState ps)
 		err("label or } expected");
 	switch (t) {
 	default:
-		if (isstore(t) || t == Tcall) {
+		if (isstore(t)) {
+		case Tcall:
+		case Ovastart:
 			/* operations without result */
 			r = R;
 			k = Kw;
@@ -639,9 +650,11 @@ DoOp:
 	}
 	if (op == Tcall) {
 		arg[0] = parseref();
-		parserefl(1);
+		if (parserefl(1))
+			op = Ovacall;
+		else
+			op = Ocall;
 		expect(Tnl);
-		op = Ocall;
 		if (k == 4) {
 			k = Kl;
 			arg[1] = TYPE(ty);
@@ -825,7 +838,7 @@ parsefn(int export)
 	if (next() != Tglo)
 		err("function name expected");
 	strcpy(curf->name, tokval.str);
-	parserefl(0);
+	curf->vararg = parserefl(0);
 	if (nextnl() != Tlbrace)
 		err("function body must start with {");
 	ps = PLbl;
@@ -1142,7 +1155,7 @@ printref(Ref r, Fn *fn, FILE *f)
 		fprintf(f, "S%d", (r.val&(1<<28)) ? r.val-(1<<29) : r.val);
 		break;
 	case RCall:
-		fprintf(f, "%03x", r.val);
+		fprintf(f, "%04x", r.val);
 		break;
 	case RType:
 		fprintf(f, ":%s", typ[r.val].name);
diff --git a/sysv.c b/sysv.c
index b05510c..f4b167b 100644
--- a/sysv.c
+++ b/sysv.c
@@ -228,6 +228,17 @@ int rclob[] = {RBX, R12, R13, R14, R15};
 MAKESURE(rsave_has_correct_size, sizeof rsave == NRSave * sizeof(int));
 MAKESURE(rclob_has_correct_size, sizeof rclob == NRClob * sizeof(int));
 
+/* layout of call's second argument (RCall)
+ *
+ *  29     12    8    4  3  0
+ *  |0...00|x|xxxx|xxxx|xx|xx|                  range
+ *          |    |    |  |  ` gp regs returned (0..2)
+ *          |    |    |  ` sse regs returned   (0..2)
+ *          |    |    ` gp regs passed         (0..6)
+ *          |    ` sse regs passed             (0..8)
+ *          ` 1 if calling a vararg function   (0..1)
+ */
+
 bits
 retregs(Ref r, int p[2])
 {
@@ -257,21 +268,22 @@ bits
 argregs(Ref r, int p[2])
 {
 	bits b;
-	int j, ni, nf;
+	int j, ni, nf, va;
 
 	assert(rtype(r) == RCall);
 	b = 0;
 	ni = (r.val >> 4) & 15;
 	nf = (r.val >> 8) & 15;
+	va = (r.val >> 12) & 1;
 	for (j=0; j<ni; j++)
 		b |= BIT(rsave[j]);
 	for (j=0; j<nf; j++)
 		b |= BIT(XMM0+j);
 	if (p) {
-		p[0] = ni + 1;
+		p[0] = ni + va;
 		p[1] = nf;
 	}
-	return b | BIT(RAX);
+	return b | (va ? BIT(RAX) : 0);
 }
 
 static Ref
@@ -288,7 +300,7 @@ selcall(Fn *fn, Ins *i0, Ins *i1, RAlloc **rap)
 {
 	Ins *i;
 	AClass *ac, *a, aret;
-	int ca, ni, ns, al;
+	int ca, ni, ns, al, va;
 	uint stk, off;
 	Ref r, r1, r2, reg[2];
 	RAlloc *ra;
@@ -354,8 +366,11 @@ selcall(Fn *fn, Ins *i0, Ins *i1, RAlloc **rap)
 			ca += 1 << 2;
 		}
 	}
+	va = i1->op == Ovacall;
+	ca |= va << 12;
 	emit(Ocall, i1->cls, R, i1->arg[0], CALL(ca));
-	emit(Ocopy, Kw, TMP(RAX), getcon((ca >> 8) & 15, fn), R);
+	if (va)
+		emit(Ocopy, Kw, TMP(RAX), getcon((ca >> 8) & 15, fn), R);
 
 	ni = ns = 0;
 	if (ra && aret.inmem)
@@ -397,12 +412,12 @@ selcall(Fn *fn, Ins *i0, Ins *i1, RAlloc **rap)
 	emit(Osalloc, Kl, r, getcon(stk, fn), R);
 }
 
-static void
+static int
 selpar(Fn *fn, Ins *i0, Ins *i1)
 {
 	AClass *ac, *a, aret;
 	Ins *i;
-	int ni, ns, s, al;
+	int ni, ns, s, al, fa;
 	Ref r;
 
 	ac = alloc((i1-i0) * sizeof ac[0]);
@@ -411,9 +426,9 @@ selpar(Fn *fn, Ins *i0, Ins *i1)
 
 	if (fn->retty >= 0) {
 		typclass(&aret, &typ[fn->retty]);
-		argsclass(i0, i1, ac, Opar, &aret);
+		fa = argsclass(i0, i1, ac, Opar, &aret);
 	} else
-		argsclass(i0, i1, ac, Opar, 0);
+		fa = argsclass(i0, i1, ac, Opar, 0);
 
 	for (i=i0, a=ac; i<i1; i++, a++) {
 		if (i->op != Oparc || a->inmem)
@@ -462,6 +477,156 @@ selpar(Fn *fn, Ins *i0, Ins *i1)
 		} else
 			emit(Ocopy, i->cls, i->to, r, R);
 	}
+
+	return fa | (s*4)<<12;
+}
+
+static Blk *
+split(Fn *fn, Blk *b)
+{
+	Blk *bn;
+
+	++fn->nblk;
+	bn = blknew();
+	bn->nins = &insb[NIns] - curi;
+	idup(&bn->ins, curi, bn->nins);
+	curi = &insb[NIns];
+	bn->visit = ++b->visit;
+	snprintf(bn->name, NString, "%s.%d", b->name, b->visit);
+	bn->loop = b->loop;
+	bn->link = b->link;
+	b->link = bn;
+	return bn;
+}
+
+static void
+chpred(Blk *b, Blk *bp, Blk *bp1)
+{
+	Phi *p;
+	uint a;
+
+	for (p=b->phi; p; p=p->link) {
+		for (a=0; p->blk[a]!=bp; a++)
+			assert(a+1<p->narg);
+		p->blk[a] = bp1;
+	}
+}
+
+void
+selvaarg(Fn *fn, Blk *b, Ins *i)
+{
+	Ref loc, lreg, lstk, nr, r0, r1, c4, c8, c16, c, ap;
+	Blk *b0, *bstk, *breg;
+	int isint;
+
+	c4 = getcon(4, fn);
+	c8 = getcon(8, fn);
+	c16 = getcon(16, fn);
+	ap = i->arg[0];
+	isint = KBASE(i->cls) == 0;
+
+	/* @b [...]
+	       r0 =l add ap, (0 or 4)
+	       nr =l loadsw r0
+	       r1 =w cultw nr, (48 or 176)
+	       jnz r1, @breg, @bstk
+	   @breg
+	       r0 =l add ap, 16
+	       r1 =l loadl r0
+	       lreg =l add r1, nr
+	       r0 =w add nr, (8 or 16)
+	       r1 =l add ap, (0 or 4)
+	       storew r0, r1
+	   @bstk
+	       r0 =l add ap, 8
+	       lstk =l loadl r0
+	       r1 =l add lstk, 8
+	       storel r1, r0
+	   @b0
+	       %loc =l phi @breg %lreg, @bstk %lstk
+	       i->to =(i->cls) load %loc
+	*/
+
+	loc = newtmp("abi", Kl, fn);
+	emit(Oload, i->cls, i->to, loc, R);
+	b0 = split(fn, b);
+	b0->jmp = b->jmp;
+	b0->s1 = b->s1;
+	b0->s2 = b->s2;
+	if (b->s1)
+		chpred(b->s1, b, b0);
+	if (b->s2 && b->s2 != b->s1)
+		chpred(b->s2, b, b0);
+
+	lreg = newtmp("abi", Kl, fn);
+	nr = newtmp("abi", Kl, fn);
+	r0 = newtmp("abi", Kw, fn);
+	r1 = newtmp("abi", Kl, fn);
+	emit(Ostorew, Kw, R, r0, r1);
+	emit(Oadd, Kl, r1, ap, isint ? CON_Z : c4);
+	emit(Oadd, Kw, r0, nr, isint ? c8 : c16);
+	r0 = newtmp("abi", Kl, fn);
+	r1 = newtmp("abi", Kl, fn);
+	emit(Oadd, Kl, lreg, r1, nr);
+	emit(Oload, Kl, r1, r0, R);
+	emit(Oadd, Kl, r0, ap, c16);
+	breg = split(fn, b);
+	breg->jmp.type = Jjmp;
+	breg->s1 = b0;
+
+	lstk = newtmp("abi", Kl, fn);
+	r0 = newtmp("abi", Kl, fn);
+	r1 = newtmp("abi", Kl, fn);
+	emit(Ostorel, Kw, R, r1, r0);
+	emit(Oadd, Kl, r1, lstk, c8);
+	emit(Oload, Kl, lstk, r0, R);
+	emit(Oadd, Kl, r0, ap, c8);
+	bstk = split(fn, b);
+	bstk->jmp.type = Jjmp;
+	bstk->s1 = b0;
+
+	b0->phi = alloc(sizeof *b0->phi);
+	*b0->phi = (Phi){
+		.cls = Kl, .to = loc,
+		.narg = 2,
+		.blk = {bstk, breg},
+		.arg = {lstk, lreg},
+	};
+	r0 = newtmp("abi", Kl, fn);
+	r1 = newtmp("abi", Kw, fn);
+	b->jmp.type = Jjnz;
+	b->jmp.arg = r1;
+	b->s1 = breg;
+	b->s2 = bstk;
+	c = getcon(isint ? 48 : 176, fn);
+	emit(Ocmpw+ICult, Kw, r1, nr, c);
+	emit(Oloadsw, Kl, nr, r0, R);
+	emit(Oadd, Kl, r0, ap, isint ? CON_Z : c4);
+}
+
+void
+selvastart(Fn *fn, int fa, Ref ap)
+{
+	Ref r0, r1;
+	int gp, fp, sp;
+
+	gp = ((fa >> 4) & 15) * 8;
+	fp = 48 + ((fa >> 8) & 15) * 16;
+	sp = fa >> 12;
+	r0 = newtmp("abi", Kl, fn);
+	r1 = newtmp("abi", Kl, fn);
+	emit(Ostorel, Kw, R, r1, r0);
+	emit(Oadd, Kl, r1, TMP(RBP), getcon(-176, fn));
+	emit(Oadd, Kl, r0, ap, getcon(16, fn));
+	r0 = newtmp("abi", Kl, fn);
+	r1 = newtmp("abi", Kl, fn);
+	emit(Ostorel, Kw, R, r1, r0);
+	emit(Oadd, Kl, r1, TMP(RBP), getcon(sp, fn));
+	emit(Oadd, Kl, r0, ap, getcon(8, fn));
+	r0 = newtmp("abi", Kl, fn);
+	emit(Ostorew, Kw, R, getcon(fp, fn), r0);
+	emit(Oadd, Kl, r0, ap, getcon(4, fn));
+	emit(Ostorew, Kw, R, getcon(gp, fn), ap);
 }
 
 void
@@ -470,13 +635,16 @@ abi(Fn *fn)
 	Blk *b;
 	Ins *i, *i0, *ip;
 	RAlloc *ral;
-	int n;
+	int n, fa;
+
+	for (b=fn->start; b; b=b->link)
+		b->visit = 0;
 
-	/* lower arguments */
+	/* lower parameters */
 	for (b=fn->start, i=b->ins; i-b->ins < b->nins; i++)
 		if (i->op != Opar && i->op != Oparc)
 			break;
-	selpar(fn, b->ins, i);
+	fa = selpar(fn, b->ins, i);
 	n = b->nins - (i - b->ins) + (&insb[NIns] - curi);
 	i0 = alloc(n * sizeof(Ins));
 	ip = icpy(ip = i0, curi, &insb[NIns] - curi);
@@ -484,27 +652,40 @@ abi(Fn *fn)
 	b->nins = n;
 	b->ins = i0;
 
-	/* lower calls and returns */
+	/* lower calls, returns, and vararg instructions */
 	ral = 0;
 	b = fn->start;
 	do {
 		if (!(b = b->link))
 			b = fn->start; /* do it last */
+		if (b->visit)
+			continue;
 		curi = &insb[NIns];
 		selret(b, fn);
-		for (i=&b->ins[b->nins]; i!=b->ins;) {
-			if ((--i)->op == Ocall) {
+		for (i=&b->ins[b->nins]; i!=b->ins;)
+			switch ((--i)->op) {
+			default:
+				emiti(*i);
+				break;
+			case Ocall:
+			case Ovacall:
 				for (i0=i; i0>b->ins; i0--)
 					if ((i0-1)->op != Oarg)
 					if ((i0-1)->op != Oargc)
 						break;
 				selcall(fn, i0, i, &ral);
 				i = i0;
-				continue;
+				break;
+			case Ovastart:
+				selvastart(fn, fa, i->arg[0]);
+				break;
+			case Ovaarg:
+				selvaarg(fn, b, i);
+				break;
+			case Oarg:
+			case Oargc:
+				die("unreachable");
 			}
-			assert(i->op != Oarg && i->op != Oargc);
-			emiti(*i);
-		}
 		if (b == fn->start)
 			for (; ral; ral=ral->link)
 				emiti(ral->i);
diff --git a/test/abi5.ssa b/test/abi5.ssa
index c3d9046..edfda4e 100644
--- a/test/abi5.ssa
+++ b/test/abi5.ssa
@@ -25,41 +25,41 @@ export
 function $test() {
 @start
 	%r1 =:st1 call $t1()
-	%i1 =w call $printf(l $fmt1, l %r1)
+	%i1 =w call $printf(l $fmt1, l %r1, ...)
 
 	%r2 =:st2 call $t2()
 	%w2 =w loadw %r2
-	%i2 =w call $printf(l $fmt2, w %w2)
+	%i2 =w call $printf(l $fmt2, w %w2, ...)
 
 	%r3 =:st3 call $t3()
 	%s3 =s loads %r3
 	%r34 =l add %r3, 4
 	%w3 =w loadw %r34
 	%p3 =d exts %s3
-	%i3 =w call $printf(l $fmt3, d %p3, w %w3)
+	%i3 =w call $printf(l $fmt3, d %p3, w %w3, ...)
 
 	%r4 =:st4 call $t4()
 	%w4 =w loadw %r4
 	%r48 =l add 8, %r4
 	%d4 =d loadd %r48
-	%i4 =w call $printf(l $fmt4, w %w4, d %d4)
+	%i4 =w call $printf(l $fmt4, w %w4, d %d4, ...)
 
 	%r5 =:st5 call $t5()
 	%s5 =s loads %r5
 	%d5 =d exts %s5
 	%r58 =l add %r5, 8
 	%l5 =l loadl %r58
-	%i5 =w call $printf(l $fmt5, d %d5, l %l5)
+	%i5 =w call $printf(l $fmt5, d %d5, l %l5, ...)
 
 	%r6 =:st6 call $t6()
-	%i6 =w call $printf(l $fmt6, l %r6)
+	%i6 =w call $printf(l $fmt6, l %r6, ...)
 
 	%r7 =:st7 call $t7()
 	%s7 =s loads %r7
 	%d71 =d exts %s7
 	%r78 =l add %r7, 8
 	%d72 =d loadd %r78
-	%i7 =w call $printf(l $fmt7, d %d71, d %d72)
+	%i7 =w call $printf(l $fmt7, d %d71, d %d72, ...)
 
 	%r8 =:st8 call $t8()
 	%r84 =l add 4, %r8
@@ -69,14 +69,14 @@ function $test() {
 	%w82 =w loadw %r84
 	%w83 =w loadw %r88
 	%w84 =w loadw %r812
-	%i8 =w call $printf(l $fmt8, w %w81, w %w82, w %w83, w %w84)
+	%i8 =w call $printf(l $fmt8, w %w81, w %w82, w %w83, w %w84, ...)
 
 	%r9 =:st9 call $t9()
 	%r94 =l add 4, %r9
 	%w9 =w loadw %r9
 	%s9 =s loads %r94
 	%d9 =d exts %s9
-	%i9 =w call $printf(l $fmt9, w %w9, d %d9)
+	%i9 =w call $printf(l $fmt9, w %w9, d %d9, ...)
 
 	ret
 }
diff --git a/test/echo.ssa b/test/echo.ssa
index 6671a6a..6010986 100644
--- a/test/echo.ssa
+++ b/test/echo.ssa
@@ -20,7 +20,7 @@ function w $main(w %argc, l %argv) {
 @loop2
 	%sep =w phi @last 10, @nolast 32
 	%arg =l loadl %av
-	%r =w call $printf(l %fmt, l %arg, w %sep)
+	%r =w call $printf(l %fmt, l %arg, w %sep, ...)
 	%av1 =l add %av, 8
 	%ac1 =w sub %ac, 1
 	jmp @loop
diff --git a/tools/lexh.c b/tools/lexh.c
index a4ca937..db27f8f 100644
--- a/tools/lexh.c
+++ b/tools/lexh.c
@@ -22,6 +22,7 @@ char *tok[] = {
 	"ceql", "cnel", "cles", "clts", "cgts", "cges",
 	"cnes", "ceqs", "cos", "cuos", "cled", "cltd",
 	"cgtd", "cged", "cned", "ceqd", "cod", "cuod",
+	"vaarg", "vastart", "...",
 
 	"call", "phi", "jmp", "jnz", "ret", "export",
 	"function", "type", "data", "align", "l", "w",