shithub: sl

Download patch

ref: 814112a0ab5f9346b3cf765dc25c0e84f4fed36e
parent: a9dacbaed106828e03ca74be373171610fd9d452
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Sat Apr 19 21:18:12 EDT 2025

back to fully dynamic stack; fix a few issues along the way

--- a/meson.build
+++ b/meson.build
@@ -391,9 +391,9 @@
 test('mp.sl', sl, args: ['mp.sl'], workdir: tests_dir)
 test('perf.sl', sl, args: ['perf.sl'], workdir: tests_dir, timeout: -1)
 test('tme.sl', sl, args: ['tme.sl'], workdir: tests_dir, timeout: -1)
-test('torture.sl', sl, args: ['-S', '8m', 'torture.sl'], workdir: tests_dir, timeout: -1)
+test('torture.sl', sl, args: ['torture.sl'], workdir: tests_dir, timeout: -1)
 test('torus.sl', sl, args: ['torus.sl'], workdir: tests_dir)
-test('unit.sl', sl, args: ['-S', '1m', 'unittest.sl'], workdir: tests_dir)
+test('unit.sl', sl, args: ['unittest.sl'], workdir: tests_dir)
 test('defstruct.sl', sl, args: ['defstruct.sl'], workdir: tests_dir)
 test('crash.sl', sl, args: ['crash.sl'], workdir: tests_dir)
 
--- /dev/null
+++ b/src/maxstack.h
@@ -1,0 +1,171 @@
+#if BYTE_ORDER == BIG_ENDIAN
+#undef SWAP_INT32
+#undef SWAP_INT16
+#define SWAP_INT32(a) \
+	do{ \
+		uint8_t *x = (void*)a, y; \
+		y = x[0]; x[0] = x[3]; x[3] = y; \
+		y = x[1]; x[1] = x[2]; x[2] = y; \
+	}while(0)
+#define SWAP_INT16(a) \
+	do{ \
+		uint8_t *x = (void*)a, y; \
+		y = x[0]; x[0] = x[1]; x[1] = y; \
+	}while(0)
+#else
+#define SWAP_INT32(a)
+#define SWAP_INT16(a)
+#endif
+
+sl_purefn
+static int
+compute_maxstack(u8int *code, usize len)
+{
+	u8int *ip = code, *end = code+len;
+	int i, n, sp = 0, maxsp = 0;
+
+	while(ip < end){
+		sl_op op = *ip++;
+		switch(op){
+		case OP_LOADA: case OP_LOADI8: case OP_LOADV: case OP_LOADG:
+			ip++; // fallthrough
+		case OP_LOADA0: case OP_LOADA1:
+		case OP_DUP: case OP_LOADT: case OP_LOADNIL: case OP_LOADVOID:
+		case OP_LOAD0:
+		case OP_LOAD1: case OP_LOADC0:
+		case OP_LOADC1:
+			sp++;
+			break;
+
+		case OP_POP: case OP_RET:
+		case OP_CONS: case OP_SETCAR: case OP_SETCDR:
+		case OP_EQP: case OP_EQVP: case OP_EQUALP: case OP_ADD2: case OP_SUB2:
+		case OP_DIV0: case OP_COMPARE:
+		case OP_AREF2: case OP_TRYCATCH:
+			sp--;
+			break;
+
+		case OP_AREF:
+			n = 2 + *ip++;
+			sp -= n;
+			break;
+
+		case OP_ARGC: case OP_SETG: case OP_SETA: case OP_BOX:
+			ip++;
+			continue;
+
+		case OP_TCALL: case OP_CALL: case OP_CLOSURE: case OP_SHIFT:
+			n = *ip++;  // nargs
+			sp -= n;
+			break;
+
+		case OP_LOADVL: case OP_LOADGL: case OP_LOADAL:
+			sp++; // fallthrough
+		case OP_SETGL: case OP_SETAL: case OP_ARGCL: case OP_BOXL:
+			SWAP_INT32(ip);
+			ip += 4;
+			break;
+
+		case OP_LOADC:
+			sp++;
+			ip++;
+			break;
+
+		case OP_VARGC:
+			n = *ip++;
+			sp += n+2;
+			break;
+		case OP_VARGCL:
+			SWAP_INT32(ip);
+			n = GET_S32(ip); ip += 4;
+			sp += n+2;
+			break;
+		case OP_OPTARGS:
+			SWAP_INT32(ip);
+			i = GET_S32(ip); ip += 4;
+			SWAP_INT32(ip);
+			n = abs(GET_S32(ip)); ip += 4;
+			sp += n-i;
+			break;
+		case OP_KEYARGS:
+			SWAP_INT32(ip);
+			i = GET_S32(ip); ip += 4;
+			SWAP_INT32(ip);
+			ip += 4;
+			SWAP_INT32(ip);
+			n = abs(GET_S32(ip)); ip += 4;
+			sp += n-i;
+			break;
+		case OP_BOUNDA:
+			SWAP_INT32(ip);
+			ip += 4;
+			sp++;
+			break;
+		case OP_TCALLL: case OP_CALLL:
+			SWAP_INT32(ip);
+			n = GET_S32(ip); ip+=4;
+			sp -= n;
+			break;
+		case OP_JMP:
+			SWAP_INT16(ip);
+			ip += 2;
+			continue;
+		case OP_JMPL:
+			SWAP_INT32(ip);
+			ip += 4;
+			continue;
+		case OP_BRNE:
+			SWAP_INT16(ip);
+			ip += 2;
+			sp -= 2;
+			break;
+		case OP_BRNEL:
+			SWAP_INT32(ip);
+			ip += 4;
+			sp -= 2;
+			break;
+		case OP_BRNN: case OP_BRN:
+			SWAP_INT16(ip);
+			ip += 2;
+			sp--;
+			break;
+		case OP_BRNNL: case OP_BRNL:
+			SWAP_INT32(ip);
+			ip += 4; // fallthrough
+		case OP_TAPPLY: case OP_APPLY:
+		case OP_LIST: case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV:
+		case OP_VEC: case OP_LT: case OP_NUMEQP:
+			n = *ip++;
+			sp -= n-1;
+			break;
+
+		case OP_FOR:
+			if(maxsp < sp+2)
+				maxsp = sp+2; // fallthrough
+		case OP_ASET:
+			sp -= 2;
+			break;
+
+		case OP_LOADCL:
+			sp++; // fallthrough
+			SWAP_INT32(ip);
+			ip += 4;
+			break;
+
+		case OP_CAR: case OP_CDR: case OP_CADR:
+		case OP_NOT: case OP_NEG: case OP_NUMP:
+		case OP_CONSP: case OP_ATOMP: case OP_SYMP:
+		case OP_FIXNUMP: case OP_BOUNDP: case OP_BUILTINP:
+		case OP_FNP: case OP_VECP: case OP_NANP:
+			continue;
+
+		case N_OPCODES:
+			return -1;
+		}
+		if((s32int)sp > (s32int)maxsp)
+			maxsp = sp;
+	}
+	assert(ip == end);
+	assert(maxsp >= 0);
+	return maxsp+4;
+}
--- a/src/mem.h
+++ b/src/mem.h
@@ -1,9 +1,10 @@
 #if !defined(HEAP_SIZE0)
+// NOTE: has to be at least enough to start with no gc calls
 #define HEAP_SIZE0 4*1024*1024
 #endif
 
 #if !defined(STACK_SIZE0)
-#define STACK_SIZE0 64*1024
+#define STACK_SIZE0 4*1024
 #endif
 
 #if !defined(ALLOC_LIMIT_TRIGGER)
--- a/src/print.c
+++ b/src/print.c
@@ -446,7 +446,7 @@
 			outsc(f, "#<eof>");
 		else if(v == sl_void)
 			outsc(f, "#<void>");
-		else
+		else // unbound, should never happen
 			abort();
 		break;
 	case TAG_FN:
--- a/src/read.c
+++ b/src/read.c
@@ -396,7 +396,7 @@
 {
 	usize i, s = vec_size(v);
 	usize d = vec_grow_amt(s);
-	PUSH(v);
+	PUSH_SAFE(v);
 	assert(s+d > s);
 	sl_v newv = alloc_vec(s+d, true);
 	v = sl.sp[-1];
@@ -417,7 +417,7 @@
 {
 	sl_v v = sl_emptyvec, elt;
 	u32int i = 0;
-	PUSH(v);
+	PUSH_SAFE(v);
 	if(label != sl_unbound)
 		ptrhash_put(&sl.readstate->backrefs, (void*)label, (void*)v);
 	while(peek(ctx) != closer){
@@ -425,7 +425,8 @@
 			parse_error(ctx, "unexpected EOI");
 		v = sl.sp[-1]; // reload after possible alloc in peek()
 		if(i >= vec_size(v)){
-			v = sl.sp[-1] = vec_grow(v, label != sl_unbound);
+			v = vec_grow(v, label != sl_unbound);
+			sl.sp[-1] = v;
 			if(label != sl_unbound)
 				ptrhash_put(&sl.readstate->backrefs, (void*)label, (void*)v);
 		}
@@ -584,16 +585,15 @@
 static void
 read_list(Rctx *ctx, sl_v label, u32int closer)
 {
-	sl_v c, *pc, *pval, *ipval, *ipc;
-	u32int t;
-	ios_loc loc0;
+	usize pc, pval;
+	sl_v c;
 
-	loc0 = RS->loc;
+	ios_loc loc0 = RS->loc;
 	loc0.colno--;
-	ipval = sl.sp-1;
-	PUSH(sl_nil);
-	ipc = sl.sp-1; // to keep track of current cons cell
-	t = peek(ctx);
+	usize ipval = sl.sp-sl.stack-1;
+	usize ipc = ipval+1; // to keep track of current cons cell
+	PUSH_SAFE(sl_nil);
+	u32int t = peek(ctx);
 	while(t != closer){
 		if(ios_eof(RS)){
 			parse_error(
@@ -606,18 +606,18 @@
 		}
 		c = alloc_cons(); car_(c) = cdr_(c) = sl_nil;
 		pc = ipc;
-		if(iscons(*pc))
-			cdr_(*pc) = c;
+		if(iscons(sl.stack[pc]))
+			cdr_(sl.stack[pc]) = c;
 		else{
 			pval = ipval;
-			*pval = c;
+			sl.stack[pval] = c;
 			if(label != sl_unbound)
 				ptrhash_put(&sl.readstate->backrefs, (void*)label, (void*)c);
 		}
-		*pc = c;
+		sl.stack[pc] = c;
 		c = do_read_sexpr(ctx, sl_unbound);
 		pc = ipc;
-		car_(*pc) = c;
+		car_(sl.stack[pc]) = c;
 
 		t = peek(ctx);
 		if(t == TOK_DOT){
@@ -624,7 +624,7 @@
 			take(ctx);
 			c = do_read_sexpr(ctx, sl_unbound);
 			pc = ipc;
-			cdr_(*pc) = c;
+			cdr_(sl.stack[pc]) = c;
 			t = peek(ctx);
 			if(ios_eof(RS))
 				parse_error(ctx, "unexpected EOI");
@@ -658,7 +658,7 @@
 	take(ctx);
 	switch(t){
 	case TOK_OPEN:
-		PUSH(sl_nil);
+		PUSH_SAFE(sl_nil);
 		read_list(ctx, label, TOK_CLOSE);
 		return POP();
 	case TOK_SYM:
@@ -665,11 +665,11 @@
 	case TOK_NUM:
 		return ctx->tokval;
 	case TOK_OPENB:
-		PUSH(sl_nil);
+		PUSH_SAFE(sl_nil);
 		read_list(ctx, label, TOK_CLOSEB);
 		return POP();
 	case TOK_OPENC:
-		PUSH(sl_nil);
+		PUSH_SAFE(sl_nil);
 		read_list(ctx, label, TOK_CLOSEC);
 		return POP();
 	case TOK_COMMA:
@@ -687,7 +687,7 @@
 		car_(v) = *head;
 		cdr_(v) = tagptr((sl_cons*)ptr(v)+1, TAG_CONS);
 		car_(cdr_(v)) = cdr_(cdr_(v)) = sl_nil;
-		PUSH(v);
+		PUSH_SAFE(v);
 		if(label != sl_unbound)
 			ptrhash_put(&sl.readstate->backrefs, (void*)label, (void*)v);
 		v = do_read_sexpr(ctx, sl_unbound);
@@ -706,11 +706,12 @@
 			take(ctx);
 			parse_error(ctx, "expected argument list for %s", sym_name(ctx->tokval));
 		}
-		PUSH(sl_nil);
+		PUSH_SAFE(sl_nil);
 		read_list(ctx, sl_unbound, c == '(' ? TOK_CLOSE : (c == '[' ? TOK_CLOSEB : TOK_CLOSEC));
 		if(sym == sl_vu8sym){
 			sym = sl_arrsym;
-			sl.sp[-1] = mk_cons(sl_u8sym, sl.sp[-1]);
+			v = mk_cons(sl_u8sym, sl.sp[-1]);
+			sl.sp[-1] = v;
 		}
 		v = sym_value(sym);
 		sl.readstate->errloc = loc0;
--- a/src/sl.c
+++ b/src/sl.c
@@ -83,13 +83,15 @@
 
 #define SL_TRY \
 	sl_exctx _ctx; int l__tr, l__ca; \
-	_ctx.sp = sl.sp; _ctx.frame = sl.curr_frame; _ctx.rdst = sl.readstate; _ctx.prev = sl.exctx; \
+	_ctx.osp = sl.sp - sl.stack; _ctx.frame = sl.curr_frame; \
+	_ctx.rdst = sl.readstate; _ctx.prev = sl.exctx; \
 	_ctx.ngchnd = slg.ngchandles; sl.exctx = &_ctx; \
 	if(!sl_setjmp(_ctx.buf)) \
 		for(l__tr = 1; l__tr; l__tr = 0, (void)(sl.exctx = sl.exctx->prev))
 
 #define SL_CATCH_INC \
-	l__ca = 0, sl.lasterror = sl_nil, sl.throwing_frame = 0, sl.sp = _ctx.sp, sl.curr_frame = _ctx.frame
+	l__ca = 0, sl.lasterror = sl_nil, sl.throwing_frame = 0, \
+	sl.sp = sl.stack + _ctx.osp, sl.curr_frame = _ctx.frame
 
 #define SL_CATCH \
 	else \
@@ -102,7 +104,7 @@
 void
 sl_savestate(sl_exctx *_ctx)
 {
-	_ctx->sp = sl.sp;
+	_ctx->osp = sl.sp - sl.stack;
 	_ctx->frame = sl.curr_frame;
 	_ctx->rdst = sl.readstate;
 	_ctx->prev = sl.exctx;
@@ -114,7 +116,7 @@
 {
 	sl.lasterror = sl_nil;
 	sl.throwing_frame = 0;
-	sl.sp = _ctx->sp;
+	sl.sp = sl.stack + _ctx->osp;
 	sl.curr_frame = _ctx->frame;
 }
 
@@ -337,18 +339,17 @@
 }
 
 void *
-alloc_words(int n)
+alloc_words(usize n)
 {
 	sl_v *first;
 
-#if !defined(BITS64)
-	// force 8-byte alignment
+	// force 8-byte alignment (32-bit) and always two cells (to forward)
+	assert(n > 0);
 	if(n & 1)
 		n++;
-#endif
 	if(sl_unlikely((sl_v*)slg.curheap > (sl_v*)slg.lim+2-n)){
 		sl_gc(false);
-		while(sl_unlikely((sl_v*)slg.curheap > ((sl_v*)slg.lim)+2-n))
+		while(sl_unlikely((sl_v*)slg.curheap > (sl_v*)slg.lim+2-n))
 			sl_gc(true);
 	}
 	first = (sl_v*)slg.curheap;
@@ -371,6 +372,18 @@
 	return v;
 }
 
+void
+sl_stack_grow(void)
+{
+	sl_v *s = MEM_REALLOC(sl.stack, 2*sl.nstack*sizeof(sl.stack));
+	if(s == nil)
+		sl_raise(sl_erroom);
+	sl.nstack *= 2;
+	sl.sp = s + (sl.sp - sl.stack);
+	sl.stackend = s + sl.nstack;
+	sl.stack = s;
+}
+
 // collector ------------------------------------------------------------------
 
 void
@@ -451,17 +464,20 @@
 		return nc;
 	}
 	if(t == TAG_FN){
-		sl_fn *fn = ptr(v);
+		sl_fn *fn = ptr(v), f;
+		f.vals = sl_relocate(fn->vals);
+		f.bcode = sl_relocate(fn->bcode);
+		f.env = sl_relocate(fn->env);
+		f.name = sl_relocate(fn->name);
+		f.maxstack = fn->maxstack;
 		sl_fn *nfn = alloc_words(sizeof(sl_fn)/sizeof(sl_v));
-		nfn->vals = fn->vals;
-		nfn->bcode = fn->bcode;
+		*nfn = f;
+		assert(!ismanaged(nfn->vals));
+		assert(!ismanaged(nfn->bcode));
+		assert(!ismanaged(nfn->env));
+		assert(!ismanaged(nfn->name));
 		nc = tagptr(nfn, TAG_FN);
 		forward(v, nc);
-		nfn->vals = sl_relocate(nfn->vals);
-		nfn->bcode = sl_relocate(nfn->bcode);
-		nfn->env = sl_relocate(fn->env);
-		assert(!ismanaged(fn->name));
-		nfn->name = fn->name;
 		return nc;
 	}
 	if(t == TAG_SYM){
@@ -497,21 +513,23 @@
 	slg.lim = slg.curheap = slg.tospace;
 	slg.lim += slg.heapsize * (slg.grew ? 2 : 1) - sizeof(sl_cons);
 
-	sl_v *top, *f;
+	usize top, f;
 	if(sl.throwing_frame > sl.curr_frame){
 		top = sl.throwing_frame - 3;
-		f = (sl_v*)*top;
+		f = sl.stack[top];
 	}else{
-		top = sl.sp;
+		top = sl.sp - sl.stack;
 		f = sl.curr_frame;
 	}
 	for(;;){
-		for(sl_v *p = f; p < top; p++)
-			*p = sl_relocate(*p);
-		if(f == sl.stack)
+		for(usize p = f; p < top; p++){
+			sl_v v = sl_relocate(sl.stack[p]);
+			sl.stack[p] = v;
+		}
+		if(f == 0)
 			break;
 		top = f - 3;
-		f = (sl_v*)*top;
+		f = sl.stack[top];
 	}
 	for(int i = 0; i < slg.ngchandles; i++)
 		*slg.gchandles[i] = sl_relocate(*slg.gchandles[i]);
@@ -550,7 +568,7 @@
 	// if we're using > 80% of the space, resize tospace so we have
 	// more space to fill next time. if we grew tospace last time,
 	// grow the other half of the heap this time to catch up.
-	if(slg.grew || ((intptr)(slg.lim-slg.curheap) < (intptr)slg.heapsize/5) || mustgrow){
+	if(slg.grew || mustgrow || ((intptr)(slg.lim-slg.curheap) < (intptr)slg.heapsize/5)){
 		sl_segfree(slg.tospace, slg.heapsize);
 		slg.tospace = sl_segalloc(slg.heapsize*2);
 		if(sl_unlikely(slg.tospace == nil)){
@@ -586,16 +604,16 @@
 static sl_v
 _applyn(int n)
 {
-	sl_v *saveSP = sl.sp;
-	sl_v f = saveSP[-n-1];
+	sl_v f = sl.sp[-n-1];
+	usize osp = sl.sp - sl.stack;
 	sl_v v;
-	if(iscbuiltin(f))
-		v = ((sl_cv*)ptr(f))->cbuiltin(saveSP-n, n);
-	else if(sl_likely(isbuiltin(f))){
+	if(iscbuiltin(f)){
+		v = ((sl_cv*)ptr(f))->cbuiltin(sl.sp-n, n);
+	}else if(sl_likely(isbuiltin(f))){
 		sl_v tab = sym_value(sl_builtinssym);
 		if(sl_unlikely(ptr(tab) == nil))
 			cthrow(unbound_error(tab), n);
-		saveSP[-n-1] = vec_elt(tab, uintval(f));
+		sl.sp[-n-1] = vec_elt(tab, uintval(f));
 		v = apply_cl(n);
 	}else if(isfn(f)){
 		v = apply_cl(n);
@@ -602,7 +620,7 @@
 	}else{
 		cthrow(type_error(nil, "fn", f), n);
 	}
-	sl.sp = saveSP;
+	sl.sp = sl.stack + osp;
 	return v;
 }
 
@@ -609,18 +627,16 @@
 sl_v
 sl_apply(sl_v f, sl_v v)
 {
-	sl_v *saveSP = sl.sp;
-
-	PUSH(f);
+	PUSH_SAFE(f);
 	int n;
 	for(n = 0; iscons(v); n++){
-		PUSH(car_(v));
+		PUSH_SAFE(car_(v));
 		v = cdr_(v);
 	}
 	if(v != sl_nil)
 		cthrow(lerrorf(sl_errarg, "apply: last argument: not a list"), f);
 	v = _applyn(n);
-	sl.sp = saveSP;
+	POPN(1+n);
 	return v;
 }
 
@@ -630,6 +646,7 @@
 	va_list ap;
 	va_start(ap, f);
 
+	stack_for(1+n);
 	PUSH(f);
 	for(int i = 0; i < n; i++){
 		sl_v a = va_arg(ap, sl_v);
@@ -636,7 +653,7 @@
 		PUSH(a);
 	}
 	sl_v v = _applyn(n);
-	POPN(n+1);
+	POPN(1+n);
 	va_end(ap);
 	return v;
 }
@@ -646,8 +663,9 @@
 {
 	va_list ap;
 	va_start(ap, n);
-	sl_v *si = sl.sp;
+	sl_v si = sl.sp - sl.stack;
 
+	stack_for(n);
 	for(int i = 0; i < n; i++){
 		sl_v a = va_arg(ap, sl_v);
 		PUSH(a);
@@ -655,7 +673,7 @@
 	sl_cons *c = alloc_words(n*2);
 	sl_cons *l = c;
 	for(int i = 0; i < n; i++){
-		c->car = *si++;
+		c->car = sl.stack[si++];
 		c->cdr = tagptr(c+1, TAG_CONS);
 		c++;
 	}
@@ -733,23 +751,23 @@
 {
 	if(!iscons(L))
 		return sl_nil;
-	sl_v *plcons = sl.sp;
-	sl_v *pL = plcons+1;
+	usize plcons = sl.sp - sl.stack;
+	usize pL = plcons+1;
 	PUSH(sl_nil);
 	PUSH(L);
 	sl_v c;
 	c = alloc_cons(); PUSH(c);  // save first cons
-	car_(c) = car_(*pL);
+	car_(c) = car_(sl.stack[pL]);
 	cdr_(c) = sl_nil;
-	*plcons = c;
-	*pL = cdr_(*pL);
-	while(iscons(*pL)){
+	sl.stack[plcons] = c;
+	sl.stack[pL] = cdr_(sl.stack[pL]);
+	while(iscons(sl.stack[pL])){
 		c = alloc_cons();
-		car_(c) = car_(*pL);
+		car_(c) = car_(sl.stack[pL]);
 		cdr_(c) = sl_nil;
-		cdr_(*plcons) = c;
-		*plcons = c;
-		*pL = cdr_(*pL);
+		cdr_(sl.stack[plcons]) = c;
+		sl.stack[plcons] = c;
+		sl.stack[pL] = cdr_(sl.stack[pL]);
 	}
 	c = POP();  // first cons
 	POPN(2);
@@ -759,10 +777,10 @@
 static sl_v
 do_trycatch(void)
 {
-	sl_v *saveSP = sl.sp;
+	usize osp = sl.sp - sl.stack;
 	sl_v v = sl_nil;
-	sl_v thunk = saveSP[-2];
-	sl.sp[-2] = saveSP[-1];
+	sl_v thunk = sl.sp[-2];
+	sl.sp[-2] = sl.sp[-1];
 	sl.sp[-1] = thunk;
 
 	SL_TRY{
@@ -769,12 +787,12 @@
 		v = apply_cl(0);
 	}
 	SL_CATCH{
-		v = saveSP[-2];
-		PUSH(v);
-		PUSH(sl.lasterror);
+		v = sl.stack[osp-2];
+		PUSH_SAFE(v);
+		PUSH_SAFE(sl.lasterror);
 		v = apply_cl(1);
 	}
-	sl.sp = saveSP;
+	sl.sp = sl.stack + osp;
 	return v;
 }
 
@@ -783,7 +801,7 @@
   |--required args--|--opt args--|--kw args--|--rest args...
 */
 static int
-process_keys(sl_v kwtable, int nreq, int nkw, int nopt, sl_v *bp, int nargs, int va)
+process_keys(sl_v kwtable, int nreq, int nkw, int nopt, usize bp, int nargs, int va)
 {
 	int extr = nopt+nkw;
 	int ntot = nreq+extr;
@@ -799,7 +817,7 @@
 	for(i = 0; i < extr; i++)
 		args[i] = sl_unbound;
 	for(i = nreq; i < nargs; i++){
-		v = bp[i];
+		v = sl.stack[bp+i];
 		if(issym(v) && iskeyword((sl_sym*)ptr(v)))
 			break;
 		if(a >= nopt)
@@ -823,7 +841,7 @@
 			idx += nopt;
 			if(args[idx] == sl_unbound){
 				// if duplicate key, keep first value
-				args[idx] = bp[i];
+				args[idx] = sl.stack[bp+i];
 			}
 		}else{
 			cthrow(lerrorf(sl_errarg, "unsupported keyword %s", sym_name(v)), kwtable);
@@ -831,7 +849,7 @@
 		i++;
 		if(i >= nargs)
 			break;
-		v = bp[i];
+		v = sl.stack[bp+i];
 	}while(issym(v) && iskeyword((sl_sym*)ptr(v)));
 no_kw:
 	nrestargs = nargs - i;
@@ -839,15 +857,15 @@
 		cthrow(lerrorf(sl_errarg, "too many arguments"), kwtable);
 	nargs = ntot + nrestargs;
 	if(nrestargs)
-		memmove(bp+ntot, bp+i, nrestargs*sizeof(sl_v));
-	memmove(bp+nreq, args, extr*sizeof(sl_v));
-	sl.sp = bp + nargs;
-	assert((intptr)(sl.sp-sl.stack) < (intptr)sl.nstack-4);
+		memmove(&sl.stack[bp+ntot], &sl.stack[bp+i], nrestargs*sizeof(sl_v));
+	memmove(&sl.stack[bp+nreq], args, extr*sizeof(sl_v));
+	sl.sp = &sl.stack[bp+nargs];
+	assert(sl.sp < sl.stackend-4);
 	PUSH(s4);
 	PUSH(s3);
 	PUSH(nargs);
 	PUSH(s1);
-	sl.curr_frame = sl.sp;
+	sl.curr_frame = sl.sp - sl.stack;
 	return nargs;
 }
 
@@ -859,6 +877,8 @@
 #define GET_S16(a) (s16int)((a)[0]<<0 | (a)[1]<<8)
 #endif
 
+#include "maxstack.h"
+
 /*
   stack on entry: <fn> <nargs args...>
   caller's responsibility:
@@ -875,8 +895,7 @@
 static sl_v
 apply_cl(int nargs)
 {
-	sl_v *top_frame = sl.curr_frame, *bp, *ipd;
-	register sl_v *sp = sl.sp;
+	usize obp, ipd, top_frame = sl.curr_frame;
 	const u8int *ip;
 	bool tail;
 	int n;
@@ -891,7 +910,7 @@
 #include "vm_goto.h"
 #undef GOTO_OP_OFFSET
 	};
-#define NEXT_OP goto *ops[*ip++]
+#define NEXT_OP do{ goto *ops[*ip++]; }while(0)
 #define LABEL(x) x
 #define OP(x) op_##x:
 #include "vm.h"
@@ -918,17 +937,16 @@
 
 // top = top frame pointer to start at
 static sl_v
-_stacktrace(sl_v *top)
+_stacktrace(usize top)
 {
 	sl_v lst = sl_nil, v = sl_nil;
-	sl_v *stack = sl.stack;
 
 	sl_gc_handle(&lst);
 	sl_gc_handle(&v);
-	while(top > stack){
-		const u8int *ip1 = (void*)top[-1];
-		int sz = top[-2]+1;
-		sl_v *bp = top-4-sz;
+	while(top > 0){
+		const u8int *ip1 = (void*)sl.stack[top-1];
+		int sz = sl.stack[top-2]+1;
+		sl_v *bp = sl.stack+top-4-sz;
 		sl_v fn = bp[0];
 		v = alloc_vec(sz+1, 0);
 		if(iscbuiltin(fn))
@@ -946,7 +964,7 @@
 			vec_elt(v, i+1) = si == sl_unbound ? sl_void : si;
 		}
 		lst = mk_cons(v, lst);
-		top = (sl_v*)top[-3];
+		top = sl.stack[top-3];
 	}
 	sl_free_gc_handles(2);
 	return lst;
@@ -979,10 +997,10 @@
 	sl_cv *arr = ptr(args[0]);
 	cv_pin(arr);
 	u8int *data = cv_data(arr);
+	usize sz = cv_len(arr);
 	if(slg.loading){
 		// read syntax, shifted 48 for compact text representation
-		usize i, sz = cv_len(arr);
-		for(i = 0; i < sz; i++)
+		for(usize i = 0; i < sz; i++)
 			data[i] -= 48;
 	}
 	sl_fn *fn = alloc_words(sizeof(sl_fn)/sizeof(sl_v));
@@ -991,6 +1009,7 @@
 	fn->vals = vals;
 	fn->env = sl_nil;
 	fn->name = sl_lambda;
+	fn->maxstack = compute_maxstack(data, sz);
 	if(nargs > 2){
 		if(issym(args[2])){
 			fn->name = args[2];
@@ -1063,16 +1082,27 @@
 	bthrow(type_error(nil, "fn", v));
 }
 
+BUILTIN("fn-maxstack", fn_maxstack)
+{
+	argcount(nargs, 1);
+	sl_v v = args[0];
+	if(isbuiltin(v) || iscbuiltin(v))
+		return fixnum(0);
+	if(isfn(v))
+		return size_wrap(fn_maxstack(v));
+	bthrow(type_error(nil, "fn", v));
+}
+
 BUILTIN("append", append)
 {
-	sl_v first = sl_nil, lst, lastcons = sl_nil;
-	int i;
 	if(nargs == 0)
 		return sl_nil;
+
+	sl_v first = sl_nil, lastcons = sl_nil;
 	sl_gc_handle(&first);
 	sl_gc_handle(&lastcons);
-	for(i = 0; i < nargs; i++){
-		lst = args[i];
+	for(int i = 0; i < nargs; i++){
+		sl_v lst = args[i];
 		if(iscons(lst)){
 			lst = copy_list(lst);
 			if(first == sl_nil)
@@ -1142,14 +1172,16 @@
 	}else{
 		bthrow(type_error("result-type", "sequence type specifier", rt));
 	}
-	sl_v *k = sl.sp;
+	usize k = sl.sp - sl.stack;
+	usize oargs = args - sl.stack;
+	stack_for(2+nargs*2+2);
 	PUSH(sl_nil);
 	PUSH(sl_nil);
 	for(usize n = 0;; n++){
-		PUSH(args[0]);
+		PUSH(sl.stack[oargs+0]);
 		int pargs = 0;
-		for(sl_v *a = args+1; a < args+nargs; a++, pargs++){
-			sl_v v = *a;
+		for(int i = 1; i < nargs; i++, pargs++){
+			sl_v v = sl.stack[oargs+i];
 			if(iscons(v)){
 				if(!rtdecl){
 					if(rtype == RT_AUTO)
@@ -1158,7 +1190,7 @@
 						bthrow(lerrorf(sl_errarg, "sequence type mismatch"));
 				}
 				PUSH(car_(v));
-				*a = cdr_(v);
+				sl.stack[oargs+i] = cdr_(v);
 				continue;
 			}else if(isvec(v)){
 				if(!rtdecl){
@@ -1234,39 +1266,39 @@
 			default:
 				break;
 			case RT_VEC:
-				k[1] = alloc_vec(n, 0);
-				memcpy(&vec_elt(k[1], 0), sl.sp-n, n*sizeof(sl_v));
+				sl.stack[k+1] = alloc_vec(n, 0);
+				memmove(&vec_elt(sl.stack[k+1], 0), sl.sp-n, n*sizeof(sl_v));
 				POPN(n);
 				break;
 			case RT_ARR:
-				k[0] = sym_value(sl_arrsym);
-				k[1] = arrtype;
-				k[1] = _applyn(n+1);
+				sl.stack[k+0] = sym_value(sl_arrsym);
+				sl.stack[k+1] = arrtype;
+				sl.stack[k+1] = _applyn(n+1);
 				POPN(n);
 				break;
 			case RT_STR:
-				k[1] = sym_value(sl_strsym);
-				k[1] = _applyn(n);
+				sl.stack[k+1] = sym_value(sl_strsym);
+				sl.stack[k+1] = _applyn(n);
 				POPN(n);
 				break;
 			case RT_TBL:
-				k[1] = sym_value(sl_tablesym);
-				k[1] = _applyn(2*n);
+				sl.stack[k+1] = sym_value(sl_tablesym);
+				sl.stack[k+1] = _applyn(2*n);
 				POPN(2*n);
 				break;
 			case RT_NIL:
-				k[1] = sl_nil;
+				sl.stack[k+1] = sl_nil;
 				break;
 			}
 			POPN(2);
-			return k[1];
+			return sl.stack[k+1];
 		}
-		sl_v v = _applyn(pargs);
+		sl_v c, v = _applyn(pargs);
 		POPN(pargs+1);
 		switch(rtype){
-		sl_v c;
 		default:
 			PUSH(v);
+			break;
 		case RT_NIL:
 			break;
 		case RT_TBL:
@@ -1274,14 +1306,16 @@
 			PUSH(cdr(v));
 			break;
 		case RT_LIST:
+			sl_gc_handle(&v);
 			c = alloc_cons();
+			sl_free_gc_handles(1);
 			car_(c) = v;
 			cdr_(c) = sl_nil;
 			if(n == 0)
-				k[1] = c;
+				sl.stack[k+1] = c;
 			else
-				cdr_(k[0]) = c;
-			k[0] = c;
+				cdr_(sl.stack[k+0]) = c;
+			sl.stack[k+0] = c;
 			break;
 		}
 	}
@@ -1341,7 +1375,7 @@
 // initialization -------------------------------------------------------------
 
 int
-sl_init(usize heapsize, usize stacksize)
+sl_init(usize heapsize)
 {
 	int i;
 
@@ -1356,9 +1390,9 @@
 		MEM_FREE(sl.consflags);
 		MEM_FREE(slg.finalizers);
 		MEM_FREE(slg.gchandles);
+		MEM_FREE(sl.stack);
 		sl_segfree(slg.fromspace, slg.heapsize);
 		sl_segfree(slg.tospace, slg.heapsize);
-		sl_segfree(sl.stack, stacksize*sizeof(sl_v));
 		htable_free(&sl.printconses);
 		MEM_FREE(slp);
 		return -1;
@@ -1369,10 +1403,11 @@
 	slg.curheap = slg.fromspace;
 	slg.lim = slg.curheap+slg.heapsize-sizeof(sl_cons);
 
-	if((sl.stack = sl_segalloc(stacksize*sizeof(sl_v))) == nil)
+	sl.nstack = STACK_SIZE0;
+	if((sl.stack = MEM_ALLOC(sl.nstack*sizeof(sl_v))) == nil)
 		goto failed;
-	sl.curr_frame = sl.sp = sl.stack;
-	sl.nstack = stacksize;
+	sl.sp = sl.stack;
+	sl.stackend = sl.stack + sl.nstack - 4;
 
 	slg.maxfinalizers = 512;
 	if((slg.finalizers = MEM_ALLOC(slg.maxfinalizers * sizeof(*slg.finalizers))) == nil)
@@ -1449,7 +1484,7 @@
 	sl.lasterror = sl_nil;
 
 	for(i = 0; i < nelem(builtins); i++){
-		if(builtins[i].name)
+		if(builtins[i].name != nil)
 			set(mk_sym(builtins[i].name, false), builtin(i));
 	}
 
@@ -1487,10 +1522,10 @@
 {
 	slg.loading = true;
 	PUSH(sys_image_io);
-	sl_v *saveSP = sl.sp;
+	usize osp = sl.sp - sl.stack;
 	SL_TRY{
 		while(1){
-			sl.sp = saveSP;
+			sl.sp = sl.stack + osp;
 			sl_v e = sl_read_sexpr(sl.sp[-1], false);
 			if(ios_eof(value2c(ios*, sl.sp[-1])))
 				break;
@@ -1518,7 +1553,7 @@
 		ios_putc(ios_stderr, '\n');
 		return -1;
 	}
-	sl.sp = saveSP-1;
+	sl.sp = sl.stack + osp - 1;
 	slg.loading = false;
 	return 0;
 }
--- a/src/sl.h
+++ b/src/sl.h
@@ -62,10 +62,6 @@
 #define PRIdFIXNUM PRId32
 #endif
 
-#if !defined(FWD_BIT)
-#define FWD_BIT TOP_BIT
-#endif
-
 typedef struct {
 	sl_v car;
 	sl_v cdr;
@@ -147,11 +143,12 @@
 #define mark_cons(c) bitvector_set(sl.consflags, cons_index(c))
 #define unmark_cons(c) bitvector_reset(sl.consflags, cons_index(c))
 
-#define isforwarded(v) (*(sl_v*)ptr(v) & FWD_BIT)
-#define forwardloc(v) (*(sl_v*)ptr(v) ^ FWD_BIT)
+#define isforwarded(v) (((sl_v*)ptr(v))[0] == sl_unbound)
+#define forwardloc(v) (((sl_v*)ptr(v))[1])
 #define forward(v, to) \
 	do{ \
-		*(sl_v*)ptr(v) = (sl_v)(to) | FWD_BIT; \
+		((sl_v*)ptr(v))[0] = sl_unbound; \
+		((sl_v*)ptr(v))[1] = (sl_v)(to); \
 	}while(0)
 
 enum {
@@ -176,6 +173,7 @@
 #define fn_vals(f) (((sl_fn*)ptr(f))->vals)
 #define fn_env(f) (((sl_fn*)ptr(f))->env)
 #define fn_name(f) (((sl_fn*)ptr(f))->name)
+#define fn_maxstack(f) (((sl_fn*)ptr(f))->maxstack)
 #define set(s, v) (((sl_sym*)ptr(s))->binding = (v))
 #define setc(s, v) \
 	do{ \
@@ -199,27 +197,39 @@
 #define FOR_ARGS(i, i0, arg, args) for(i=i0; i<nargs && ((arg=args[i]) || 1); i++)
 #define N_BUILTINS ((int)N_OPCODES)
 
-#define PUSH(v) \
-	do{ \
-		*sl.sp++ = (v); \
-	}while(0)
-#define POPN(n) \
-	do{ \
-		sl.sp -= (n); \
-	}while(0)
+#define PUSH(v) do{ \
+	*sl.sp++ = (sl_v)(v); \
+}while(0)
+
+#define stack_for(n) do{ \
+	while(sl.sp+(n) >= sl.stackend) \
+		sl_stack_grow(); \
+}while(0)
+
+#define PUSH_SAFE(v) do{ \
+	if(sl.sp >= sl.stackend) \
+		sl_stack_grow(); \
+	PUSH(v); \
+}while(0)
+
+#define POPN(n) do{ \
+	sl.sp -= (n); \
+}while(0)
+
 #define POP() *(--sl.sp)
 
 bool isbuiltin(sl_v x) sl_constfn sl_hotfn;
-int sl_init(usize heapsize, usize stacksize);
+int sl_init(usize heapsize);
 int sl_load_system_image(sl_v ios);
 
 _Noreturn void sl_exit(const char *status);
 
-/* collector */
+/* memory */
 sl_v sl_relocate(sl_v v) sl_hotfn;
 void sl_gc(bool mustgrow);
 void sl_gc_handle(sl_v *pv);
 void sl_free_gc_handles(int n);
+void sl_stack_grow(void);
 
 /* symbol table */
 sl_v mk_gensym(void);
@@ -253,7 +263,7 @@
 
 /* conses */
 sl_v alloc_cons(void) sl_hotfn;
-void *alloc_words(int n) sl_hotfn;
+void *alloc_words(usize n) sl_hotfn;
 
 char *uint2str(char *dest, usize len, u64int num, int base);
 
@@ -270,8 +280,8 @@
 	sl_readstate *rdst;
 	struct _ectx_t *prev;
 	sl_jmp_buf buf;
-	sl_v *sp;
-	sl_v *frame;
+	usize osp;
+	usize frame;
 	int ngchnd;
 }sl_exctx;
 
@@ -347,6 +357,7 @@
 	sl_v bcode;
 	sl_v env;
 	sl_v name;
+	usize maxstack;
 }sl_aligned(8) sl_fn;
 
 #define cv_class(cv) ((sl_type*)(((uintptr)((sl_cv*)cv)->type)&~(uintptr)3))
@@ -385,16 +396,17 @@
 
 struct Sl {
 	sl_v *sp;
-	sl_v *curr_frame;
+	usize curr_frame;
 
 	// saved execution state for an unwind target
 	sl_exctx *exctx;
-	sl_v *throwing_frame;  // active frame when exception was thrown
+	usize throwing_frame;  // active frame when exception was thrown
 	sl_v lasterror;
 
 	sl_readstate *readstate;
 	u32int *consflags;
 	sl_v *stack;
+	sl_v *stackend;
 	u32int nstack;
 
 	sl_htable printconses;
--- a/src/sl_arith_any.h
+++ b/src/sl_arith_any.h
@@ -36,7 +36,7 @@
 			}
 			switch(pt){
 			case T_DBL: Faccum = ARITH_OP(Faccum, *(double*)a); inexact = true; continue;
-			case T_FLT:  Faccum = ARITH_OP(Faccum, *(float*)a); inexact = true; continue;
+			case T_FLT: Faccum = ARITH_OP(Faccum, *(float*)a); inexact = true; continue;
 			case T_S8:  x = *(s8int*)a; break;
 			case T_U8:  x = *(u8int*)a; break;
 			case T_S16: x = *(s16int*)a; break;
@@ -119,7 +119,7 @@
 		}
 		switch(pt){
 		case T_DBL: Faccum = ARITH_OP(Faccum, *(double*)a); inexact = true; continue;
-		case T_FLT:  Faccum = ARITH_OP(Faccum, *(float*)a); inexact = true; continue;
+		case T_FLT: Faccum = ARITH_OP(Faccum, *(float*)a); inexact = true; continue;
 		case T_S8:  x = *(s8int*)a; break;
 		case T_U8:  x = *(u8int*)a; break;
 		case T_S16: x = *(s16int*)a; break;
--- a/src/slmain.c
+++ b/src/slmain.c
@@ -64,7 +64,7 @@
 _Noreturn static void
 usage(void)
 {
-	ios_printf(ios_stderr, "%s: [-i] [-H heapsize] [-S stacksize] ...\n", argv0);
+	ios_printf(ios_stderr, "%s: [-i] [-H heapsize] ...\n", argv0);
 	exits("usage");
 }
 
@@ -71,7 +71,7 @@
 _Noreturn void
 slmain(const u8int *boot, int bootsz, int argc, char **argv)
 {
-	usize heapsize = HEAP_SIZE0, stacksize = STACK_SIZE0;
+	usize heapsize = HEAP_SIZE0;
 	sl_v interactive;
 	char *e;
 
@@ -86,10 +86,6 @@
 		heapsize = strtoull(EARGF(usage()), &e, 0);
 		sizesuffix(&heapsize, *e);
 		break;
-	case 'S':
-		stacksize = strtoull(EARGF(usage()), &e, 0);
-		sizesuffix(&stacksize, *e);
-		break;
 	case 'i':
 		interactive = sl_t;
 		break;
@@ -99,7 +95,7 @@
 		break;
 	}ARGEND
 
-	if(sl_init(heapsize, stacksize) != 0)
+	if(sl_init(heapsize) != 0)
 		sysfatal("init failed");
 
 	u8int *unpacked = nil;
--- a/src/vm.h
+++ b/src/vm.h
@@ -5,10 +5,17 @@
 		: (fits_fixnum(i64) ? fixnum(i64) : mk_bignum(vtomp(i64, nil))) \
 )
 
-#define SYNC do { *ipd = (uintptr)ip; sl.sp = sp; }while(0)
+#define ENTER do{ \
+	sl.stack[ipd] = (uintptr)ip; \
+}while(0)
 
+#define STACK(n) do{ \
+	while(sl.sp+(n) > sl.stackend-4) \
+		sl_stack_grow(); \
+}while(0)
+
 OP(OP_LOADA0)
-	*sp++ = bp[0];
+	PUSH(sl.stack[obp+0]);
 	NEXT_OP;
 
 OP(OP_CALL) {
@@ -29,28 +36,30 @@
 		ip += 4;
 	}
 LABEL(do_call):
-	SYNC;
-	sl_v v = sp[-n-1];
+	ENTER;
+	sl_v v = sl.sp[-n-1];
 	if(tag(v) == TAG_FN){
 		if(!isfnbuiltin(v)){
 			nargs = n;
 			if(tail){
-				sl.curr_frame = (sl_v*)sl.curr_frame[-3];
+				sl.curr_frame = sl.stack[sl.curr_frame-3];
 				for(sl_fx s = -1; s < (sl_fx)n; s++)
-					bp[s] = sp[s-n];
-				sp = bp+n;
+					sl.stack[obp+s] = sl.sp[s-n];
+				sl.sp = &sl.stack[obp+n];
 			}else{
 LABEL(apply_func):
-				bp = sp-nargs;
+				obp = sl.sp-sl.stack-nargs;
 			}
-			sl_fn *fn = (sl_fn*)ptr(bp[-1]);
+			sl_fn *fn = (sl_fn*)ptr(sl.stack[obp-1]);
+			assert(ismanaged(fn));
+			stack_for(fn->maxstack);
 			ip = cvalue_data(fn->bcode);
 			assert(!ismanaged((uintptr)ip));
-			*sp++ = fn->env;
-			*sp++ = (sl_v)sl.curr_frame;
-			*sp++ = nargs;
-			ipd = sp++;
-			sl.curr_frame = sp;
+			PUSH(fn->env);
+			PUSH(sl.curr_frame);
+			PUSH(nargs);
+			ipd = sl.sp++ - sl.stack;
+			sl.curr_frame = ipd + 1;
 			NEXT_OP;
 		}
 		int i = uintval(v);
@@ -62,9 +71,9 @@
 		else if(s != ANYARGS && n < -s)
 			argcount(n, -s);
 		// remove function arg
-		for(sl_v *p = sp-n-1; p < sp-1; p++)
+		for(sl_v *p = sl.sp-n-1; p < sl.sp-1; p++)
 			p[0] = p[1];
-		sp--;
+		POPN(1);
 		switch(i){
 		case OP_VEC: goto LABEL(apply_vec);
 		case OP_ADD: goto LABEL(apply_add);
@@ -87,17 +96,17 @@
 		}
 	}else if(sl_likely(iscbuiltin(v))){
 		builtin_t f = ((sl_cv*)ptr(v))->cbuiltin;
-		*sp++ = sl_nil; // fn->env;
-		*sp++ = (sl_v)sl.curr_frame;
-		*sp++ = n;
-		*sp++ = (sl_v)f; // ip
-		sl.curr_frame = sp;
-		sl.sp = sp;
-		v = f(sp-n-4, n);
-		sp = sl.curr_frame;
-		sl.curr_frame = (sl_v*)sp[-3];
-		sp -= 4+n;
-		sp[-1] = v;
+		stack_for(4);
+		PUSH(sl_nil); // fn->env;
+		PUSH(sl.curr_frame);
+		PUSH(n);
+		PUSH(f); // ip
+		sl.curr_frame = sl.sp - sl.stack;
+		v = f(sl.sp-n-4, n);
+		sl.sp = sl.stack + sl.curr_frame;
+		sl.curr_frame = sl.sp[-3];
+		sl.sp -= 4+n;
+		sl.sp[-1] = v;
 		NEXT_OP;
 	}
 	type_error(tail ? "tcall" : "call", "fn", v);
@@ -111,7 +120,7 @@
 		ip += 4;
 	}
 	if(sl_unlikely(nargs != na)){
-		SYNC;
+		ENTER;
 		arity_error(nargs, na);
 	}
 	NEXT_OP;
@@ -118,46 +127,44 @@
 }
 
 OP(OP_LOADA1)
-	*sp++ = bp[1];
+	PUSH(sl.stack[obp+1]);
 	NEXT_OP;
 
 OP(OP_RET) {
-	sl_v v = *(--sp);
-	sp = sl.curr_frame;
-	sl.curr_frame = (sl_v*)sp[-3];
-	if(sl.curr_frame == top_frame){
-		sl.sp = sp;
+	sl_v v = POP();
+	sl.sp = sl.stack + sl.curr_frame;
+	sl.curr_frame = sl.sp[-3];
+	if(sl.curr_frame == top_frame)
 		return v;
-	}
-	sp -= 4+nargs;
+	sl.sp -= 4+nargs;
 	ipd = sl.curr_frame-1;
-	ip = (u8int*)*ipd;
-	nargs = sl.curr_frame[-2];
-	bp = sl.curr_frame - 4 - nargs;
-	sp[-1] = v;
+	ip = (u8int*)sl.stack[ipd];
+	nargs = sl.stack[sl.curr_frame-2];
+	obp = sl.curr_frame-4-nargs;
+	sl.sp[-1] = v;
 	NEXT_OP;
 }
 
 OP(OP_LOAD1)
-	*sp++ = fixnum(1);
+	PUSH(fixnum(1));
 	NEXT_OP;
 
 OP(OP_LOADA)
-	*sp++ = bp[*ip++];
+	PUSH(sl.stack[obp + *ip++]);
 	NEXT_OP;
 
 OP(OP_BRN)
-	ip += *(--sp) == sl_nil ? GET_S16(ip) : 2;
+	ip += POP() == sl_nil ? GET_S16(ip) : 2;
 	NEXT_OP;
 
 OP(OP_LOADG) {
-	sl_v v = fn_vals(bp[-1]);
+	sl_v v = fn_vals(sl.stack[obp-1]);
 	assert(*ip < vec_size(v));
 	v = vec_elt(v, *ip);
 	ip++;
 	if(0){
 OP(OP_LOADGL)
-		v = fn_vals(bp[-1]);
+		v = fn_vals(sl.stack[obp-1]);
 		v = vec_elt(v, GET_S32(ip));
 		ip += 4;
 	}
@@ -164,10 +171,10 @@
 	assert(issym(v));
 	sl_sym *sym = ptr(v);
 	if(sl_unlikely(sym->binding == sl_unbound)){
-		SYNC;
+		ENTER;
 		unbound_error(v);
 	}
-	*sp++ = sym->binding;
+	PUSH(sym->binding);
 	NEXT_OP;
 }
 
@@ -175,10 +182,10 @@
 	n = *ip++;
 LABEL(apply_lt):;
 	int i = n;
-	sl_v a = sp[-i], b, v;
+	sl_v a = sl.sp[-i], b, v;
 	for(v = sl_t; i > 1; a = b){
 		i--;
-		b = sp[-i];
+		b = sl.sp[-i];
 		if(bothfixnums(a, b)){
 			if((sl_fx)a >= (sl_fx)b){
 				v = sl_nil;
@@ -194,15 +201,16 @@
 			}
 		}
 	}
-	sp -= n;
-	*sp++ = v;
+	sl.sp -= n;
+	PUSH(v);
 	NEXT_OP;
 }
 
 OP(OP_LOADV) {
-	sl_v v = fn_vals(bp[-1]);
+	sl_v v = fn_vals(sl.stack[obp-1]);
 	assert(*ip < vec_size(v));
-	*sp++ = vec_elt(v, *ip++);
+	PUSH(vec_elt(v, *ip++));
+	assert(sl.sp < sl.stackend);
 	NEXT_OP;
 }
 
@@ -210,59 +218,59 @@
 	sl_fx a, b, q;
 	sl_v v;
 LABEL(do_add2):
-	SYNC;
+	ENTER;
 	if(0){
 OP(OP_SUB2)
 LABEL(do_sub2):
-		SYNC;
-		v = sp[-1];
+		ENTER;
+		v = sl.sp[-1];
 		s64int i64;
 		b = isfixnum(v) ? fixnum_neg(v) : sl_neg(v);
 	}else{
-		b = sp[-1];
+		b = sl.sp[-1];
 	}
-	a = sp[-2];
+	a = sl.sp[-2];
 	if(bothfixnums(a, b) && !sadd_overflow(a, b, &q))
 		v = q;
 	else{
-		sp[-1] = b;
-		v = sl_add_any(sp-2, 2);
+		sl.sp[-1] = b;
+		v = sl_add_any(sl.sp-2, 2);
 	}
-	sp--;
-	sp[-1] = v;
+	POPN(1);
+	sl.sp[-1] = v;
 	NEXT_OP;
 }
 
 OP(OP_LOADI8)
-	*sp++ = fixnum((s8int)*ip++);
+	PUSH(fixnum((s8int)*ip++));
 	NEXT_OP;
 
 OP(OP_POP)
-	sp--;
+	POPN(1);
 	NEXT_OP;
 
 OP(OP_BRNN)
-	ip += *(--sp) != sl_nil ? GET_S16(ip) : 2;
+	ip += POP() != sl_nil ? GET_S16(ip) : 2;
 	NEXT_OP;
 
 OP(OP_DUP)
-	sp[0] = sp[-1];
-	sp++;
+	sl.sp[0] = sl.sp[-1];
+	sl.sp++;
 	NEXT_OP;
 
 OP(OP_LOADC0)
-	*sp++ = vec_elt(bp[nargs], 0);
+	PUSH(vec_elt(sl.stack[obp+nargs], 0));
 	NEXT_OP;
 
 OP(OP_CAR) {
-	sl_v v = sp[-1];
+	sl_v v = sl.sp[-1];
 	if(sl_likely(iscons(v)))
 		v = car_(v);
 	else if(sl_unlikely(v != sl_nil)){
-		SYNC;
+		ENTER;
 		type_error("car", "cons", v);
 	}
-	sp[-1] = v;
+	sl.sp[-1] = v;
 	NEXT_OP;
 }
 
@@ -269,75 +277,64 @@
 OP(OP_CLOSURE) {
 	int x = *ip++;
 	assert(x > 0);
-	SYNC;
-	sl_v *pv = alloc_words(
-		1+x+
-#if !defined(BITS64)
-		!(x&1)+
-#endif
-		sizeof(sl_fn)/sizeof(sl_v));
-	sl_v v = tagptr(pv, TAG_VEC);
-	*pv++ = fixnum(x);
+	ENTER;
+	sl_v v = alloc_vec(x, false);
 	for(int i = 0; i < x; i++)
-		*pv++ = sp[-x+i];
-	sp -= x;
-	*sp = v;
-#if !defined(BITS64)
-	if((x & 1) == 0)
-		pv++;
-#endif
-	sl_fn *f = (sl_fn*)pv;
-	sl_v e = sp[-1];  // closure to copy
-	sp[-1] = tagptr(f, TAG_FN);
+		vec_elt(v, i) = sl.sp[-x+i];
+	POPN(x);
+	PUSH(v);
+	sl_fn *fn = (sl_fn*)alloc_words(sizeof(sl_fn)/sizeof(sl_v));
+	sl_v e = sl.sp[-2];  // closure to copy
 	assert(isfn(e));
-	f->vals = fn_vals(e);
-	f->bcode = fn_bcode(e);
-	f->env = sp[0];
-	f->name = fn_name(e);
+	fn->vals = fn_vals(e);
+	fn->bcode = fn_bcode(e);
+	fn->env = sl.sp[-1];
+	fn->name = fn_name(e);
+	fn->maxstack = fn_maxstack(e);
+	POPN(1);
+	sl.sp[-1] = tagptr(fn, TAG_FN);
 	NEXT_OP;
 }
 
 OP(OP_CONS) {
-	if(slg.curheap > slg.lim){
-		SYNC;
-		sl_gc(0);
-	}
+	if(sl_unlikely((sl_v*)slg.curheap > (sl_v*)slg.lim))
+		sl_gc(false);
 	sl_cons *c = (sl_cons*)slg.curheap;
 	slg.curheap += sizeof(sl_cons);
-	c->car = sp[-2];
-	c->cdr = sp[-1];
-	sp[-2] = tagptr(c, TAG_CONS);
-	sp--;
+	c->car = sl.sp[-2];
+	c->cdr = sl.sp[-1];
+	sl.sp[-2] = tagptr(c, TAG_CONS);
+	POPN(1);
 	NEXT_OP;
 }
 
 OP(OP_BRNE)
-	ip += sp[-2] != sp[-1] ? GET_S16(ip) : 2;
-	sp -= 2;
+	ip += sl.sp[-2] != sl.sp[-1] ? GET_S16(ip) : 2;
+	sl.sp -= 2;
 	NEXT_OP;
 
 OP(OP_CDR) {
-	sl_v v = sp[-1];
+	sl_v v = sl.sp[-1];
 	if(sl_likely(iscons(v)))
 		v = cdr_(v);
 	else if(sl_unlikely(v != sl_nil)){
-		SYNC;
+		ENTER;
 		type_error("cdr", "cons", v);
 	}
-	sp[-1] = v;
+	sl.sp[-1] = v;
 	NEXT_OP;
 }
 
 OP(OP_LOADVOID)
-	*sp++ = sl_void;
+	PUSH(sl_void);
 	NEXT_OP;
 
 OP(OP_NOT)
-	sp[-1] = sp[-1] == sl_nil ? sl_t : sl_nil;
+	sl.sp[-1] = sl.sp[-1] == sl_nil ? sl_t : sl_nil;
 	NEXT_OP;
 
 OP(OP_SETA)
-	bp[*ip++] = sp[-1];
+	sl.stack[obp + *ip++] = sl.sp[-1];
 	NEXT_OP;
 
 OP(OP_VARGC) {
@@ -348,29 +345,29 @@
 		ip += 4;
 	}
 	sl_fx s = (sl_fx)nargs - (sl_fx)i;
+	ENTER;
 	if(s > 0){
-		sl_v v = list(bp+i, s, false);
-		bp[i] = v;
+		sl_v v = list(&sl.stack[obp+i], s, false);
+		sl.stack[obp+i] = v;
 		if(s > 1){
-			bp[i+1] = bp[nargs+0];
-			bp[i+2] = bp[nargs+1];
-			bp[i+3] = i+1;
-			bp[i+4] = 0;
-			sp = bp+i+5;
-			sl.curr_frame = sp;
+			sl.stack[obp+i+1] = sl.stack[obp+nargs+0];
+			sl.stack[obp+i+2] = sl.stack[obp+nargs+1];
+			sl.stack[obp+i+3] = i+1;
+			sl.stack[obp+i+4] = 0;
+			sl.sp = sl.stack+obp+i+5;
+			sl.curr_frame = sl.sp - sl.stack;
 		}
 	}else if(sl_unlikely(s < 0)){
-		SYNC;
 		lerrorf(sl_errarg, "too few arguments");
 	}else{
-		sp++;
-		sp[-2] = i+1;
-		sp[-3] = sp[-4];
-		sp[-4] = sp[-5];
-		sp[-5] = sl_nil;
-		sl.curr_frame = sp;
+		sl.sp++;
+		sl.sp[-2] = i+1;
+		sl.sp[-3] = sl.sp[-4];
+		sl.sp[-4] = sl.sp[-5];
+		sl.sp[-5] = sl_nil;
+		sl.curr_frame = sl.sp - sl.stack;
 	}
-	ipd = sp-1;
+	ipd = sl.curr_frame-1;
 	nargs = i+1;
 	NEXT_OP;
 }
@@ -377,33 +374,33 @@
 
 OP(OP_SHIFT) {
 	int i = *ip++;
-	sp[-1-i] = sp[-1];
-	sp -= i;
+	sl.sp[-1-i] = sl.sp[-1];
+	sl.sp -= i;
 	NEXT_OP;
 }
 
 OP(OP_SETCAR) {
-	sl_v v = sp[-2];
+	sl_v v = sl.sp[-2];
 	if(sl_unlikely(!iscons(v))){
-		SYNC;
+		ENTER;
 		type_error("set-car!", "cons", v);
 	}
-	car_(v) = sp[-1];
-	sp--;
+	car_(v) = sl.sp[-1];
+	POPN(1);
 	NEXT_OP;
 }
 
 OP(OP_LOADNIL)
-	*sp++ = sl_nil;
+	PUSH(sl_nil);
 	NEXT_OP;
 
 OP(OP_BOX) {
 	int i = *ip++;
-	SYNC;
+	ENTER;
 	sl_v v = alloc_cons();
-	car_(v) = bp[i];
+	car_(v) = sl.stack[obp+i];
 	cdr_(v) = sl_nil;
-	bp[i] = v;
+	sl.stack[obp+i] = v;
 	NEXT_OP;
 }
 
@@ -412,7 +409,7 @@
 	NEXT_OP;
 
 OP(OP_ATOMP)
-	sp[-1] = iscons(sp[-1]) ? sl_nil : sl_t;
+	sl.sp[-1] = iscons(sl.sp[-1]) ? sl_nil : sl_t;
 	NEXT_OP;
 
 OP(OP_AREF2) {
@@ -419,17 +416,17 @@
 	n = 2;
 	if(0){
 OP(OP_AREF)
-	SYNC;
+	ENTER;
 	n = 3 + *ip++;
 	}
 LABEL(apply_aref):;
-	sl_v v = sp[-n];
+	sl_v v = sl.sp[-n];
 	for(int i = n-1; i > 0; i--){
-		sl_v e = sp[-i];
+		sl_v e = sl.sp[-i];
 		usize isz = tosize(e);
 		if(isarr(v)){
-			sp[-i-1] = v;
-			v = cvalue_arr_aref(sp-i-1);
+			sl.sp[-i-1] = v;
+			v = cvalue_arr_aref(sl.sp-i-1);
 			continue;
 		}
 		if(isvec(v)){
@@ -450,13 +447,13 @@
 				bounds_error(v0, e);
 		}
 	}
-	sp -= n;
-	*sp++ = v;
+	sl.sp -= n;
+	PUSH(v);
 	NEXT_OP;
 }
 
 OP(OP_NANP) {
-	sl_v v = sp[-1];
+	sl_v v = sl.sp[-1];
 	if(!iscvalue(v))
 		v = sl_nil;
 	else{
@@ -473,43 +470,43 @@
 			break;
 		}
 	}
-	sp[-1] = v;
+	sl.sp[-1] = v;
 	NEXT_OP;
 }
 
 OP(OP_LOAD0)
-	*sp++ = fixnum(0);
+	PUSH(fixnum(0));
 	NEXT_OP;
 
 OP(OP_SETCDR) {
-	sl_v v = sp[-2];
+	sl_v v = sl.sp[-2];
 	if(sl_unlikely(!iscons(v))){
-		SYNC;
+		ENTER;
 		type_error("set-cdr!", "cons", v);
 	}
-	cdr_(v) = sp[-1];
-	sp--;
+	cdr_(v) = sl.sp[-1];
+	POPN(1);
 	NEXT_OP;
 }
 
 OP(OP_LOADC1)
-	*sp++ = vec_elt(bp[nargs], 1);
+	PUSH(vec_elt(sl.stack[obp+nargs], 1));
 	NEXT_OP;
 
 OP(OP_ASET) {
-	SYNC;
-	sl_v v = sp[-3];
+	ENTER;
+	sl_v v = sl.sp[-3];
 	n = 3;
 	if(0){
 LABEL(apply_aset):
-		v = sp[-n];
+		v = sl.sp[-n];
 		for(int i = n-1; i >= 3; i--){
 			if(isarr(v)){
-				sp[-i-1] = v;
-				v = cvalue_arr_aref(sp-i-1);
+				sl.sp[-i-1] = v;
+				v = cvalue_arr_aref(sl.sp-i-1);
 				continue;
 			}
-			sl_v e = sp[-i];
+			sl_v e = sl.sp[-i];
 			usize isz = tosize(e);
 			if(isvec(v)){
 				if(sl_unlikely(isz >= vec_size(v)))
@@ -529,18 +526,18 @@
 					bounds_error(v0, e);
 			}
 		}
-		sp[-3] = v;
+		sl.sp[-3] = v;
 	}
-	sl_v e = sp[-2];
+	sl_v e = sl.sp[-2];
 	usize isz = tosize(e);
 	if(isvec(v)){
 		if(sl_unlikely(isz >= vec_size(v)))
 			bounds_error(v, e);
-		vec_elt(v, isz) = (e = sp[-1]);
+		vec_elt(v, isz) = (e = sl.sp[-1]);
 	}else if(iscons(v)){
 		for(sl_v v0 = v;; isz--){
 			if(isz == 0){
-				car_(v) = (e = sp[-1]);
+				car_(v) = (e = sl.sp[-1]);
 				break;
 			}
 			v = cdr_(v);
@@ -548,45 +545,45 @@
 				bounds_error(v0, e);
 		}
 	}else if(isarr(v)){
-		e = cvalue_arr_aset(sp-3);
+		e = cvalue_arr_aset(sl.sp-3);
 	}else{
 		type_error("aset!", "sequence", v);
 	}
-	sp -= n;
-	*sp++ = e;
+	sl.sp -= n;
+	PUSH(e);
 	NEXT_OP;
 }
 
 OP(OP_EQUALP) {
-	sl_v a = sp[-2], b = sp[-1];
-	sp--;
-	sp[-1] = (a == b || sl_compare(a, b, true) == 0) ? sl_t : sl_nil;
+	sl_v a = sl.sp[-2], b = sl.sp[-1];
+	POPN(1);
+	sl.sp[-1] = (a == b || sl_compare(a, b, true) == 0) ? sl_t : sl_nil;
 	NEXT_OP;
 }
 
 OP(OP_CONSP)
-	sp[-1] = iscons(sp[-1]) ? sl_t : sl_nil;
+	sl.sp[-1] = iscons(sl.sp[-1]) ? sl_t : sl_nil;
 	NEXT_OP;
 
 OP(OP_LOADC) {
-	sl_v v = bp[nargs];
+	sl_v v = sl.stack[obp+nargs];
 	int i = *ip++;
 	assert(isvec(v));
 	assert(i < (int)vec_size(v));
-	*sp++ = vec_elt(v, i);
+	PUSH(vec_elt(v, i));
 	NEXT_OP;
 }
 
 OP(OP_SYMP)
-	sp[-1] = issym(sp[-1]) ? sl_t : sl_nil;
+	sl.sp[-1] = issym(sl.sp[-1]) ? sl_t : sl_nil;
 	NEXT_OP;
 
 OP(OP_NUMP)
-	sp[-1] = isnum(sp[-1]) ? sl_t : sl_nil;
+	sl.sp[-1] = isnum(sl.sp[-1]) ? sl_t : sl_nil;
 	NEXT_OP;
 
 OP(OP_BOUNDA)
-	*sp++ = bp[GET_S32(ip)] != sl_unbound ? sl_t : sl_nil;
+	PUSH(sl.stack[obp+GET_S32(ip)] != sl_unbound ? sl_t : sl_nil);
 	ip += 4;
 	NEXT_OP;
 
@@ -596,12 +593,12 @@
 	int x = GET_S32(ip);
 	ip += 4;
 	if(sl_unlikely(nargs < i)){
-		SYNC;
+		ENTER;
 		lerrorf(sl_errarg, "too few arguments");
 	}
 	if(x > 0){
 		if(sl_unlikely(nargs > x)){
-			SYNC;
+			ENTER;
 			lerrorf(sl_errarg, "too many arguments");
 		}
 	}else
@@ -608,15 +605,15 @@
 		x = -x;
 	if(sl_likely(x > nargs)){
 		x -= nargs;
-		sp += x;
-		sp[-1] = sp[-x-1];
-		sp[-2] = nargs+x;
-		sp[-3] = sp[-x-3];
-		sp[-4] = sp[-x-4];
-		sl.curr_frame = sp;
-		ipd = sp-1;
+		sl.sp += x;
+		sl.sp[-1] = sl.sp[-x-1];
+		sl.sp[-2] = nargs+x;
+		sl.sp[-3] = sl.sp[-x-3];
+		sl.sp[-4] = sl.sp[-x-4];
+		sl.curr_frame = sl.sp - sl.stack;
+		ipd = sl.curr_frame - 1;
 		for(i = 0; i < x; i++)
-			bp[nargs+i] = sl_unbound;
+			sl.stack[obp+nargs+i] = sl_unbound;
 		nargs += x;
 	}
 	NEXT_OP;
@@ -623,23 +620,24 @@
 }
 
 OP(OP_EQP)
-	sp[-2] = sp[-2] == sp[-1] ? sl_t : sl_nil;
-	sp--;
+	sl.sp[-2] = sl.sp[-2] == sl.sp[-1] ? sl_t : sl_nil;
+	POPN(1);
 	NEXT_OP;
 
 OP(OP_LIST) {
 	n = *ip++;
 LABEL(apply_list):;
-	sl_v v = list(sp-n, n, false);
-	sp -= n;
-	*sp++ = v;
+	ENTER;
+	sl_v v = list(sl.sp-n, n, false);
+	sl.sp -= n;
+	PUSH(v);
 	NEXT_OP;
 }
 
 OP(OP_BOUNDP) {
-	SYNC;
-	sl_sym *sym = tosym(sp[-1]);
-	sp[-1] = sym->binding == sl_unbound ? sl_nil : sl_t;
+	ENTER;
+	sl_sym *sym = tosym(sl.sp[-1]);
+	sl.sp[-1] = sym->binding == sl_unbound ? sl_nil : sl_t;
 	NEXT_OP;
 }
 
@@ -647,10 +645,10 @@
 	n = *ip++;
 LABEL(apply_numeqp):;
 	int i = n;
-	sl_v a = sp[-i], b, v;
+	sl_v a = sl.sp[-i], b, v;
 	for(v = sl_t; i > 1; a = b){
 		i--;
-		b = sp[-i];
+		b = sl.sp[-i];
 		if(bothfixnums(a, b)){
 			if(a != b){
 				v = sl_nil;
@@ -661,13 +659,13 @@
 			break;
 		}
 	}
-	sp -= n;
-	*sp++ = v;
+	sl.sp -= n;
+	PUSH(v);
 	NEXT_OP;
 }
 
 OP(OP_CADR) {
-	sl_v v = sp[-1];
+	sl_v v = sl.sp[-1];
 	if(sl_likely(iscons(v))){
 		v = cdr_(v);
 		if(sl_likely(iscons(v)))
@@ -677,11 +675,11 @@
 	}else{
 LABEL(cadr_nil):
 		if(sl_unlikely(v != sl_nil)){
-			SYNC;
+			ENTER;
 			type_error("cadr", "cons", v);
 		}
 	}
-	sp[-1] = v;
+	sl.sp[-1] = v;
 	NEXT_OP;
 }
 
@@ -693,58 +691,58 @@
 	}
 	n = *ip++;
 LABEL(apply_apply):;
-	sl_v v = *(--sp);	 // arglist
-	sl_v *p = sp-(n-2);  // n-2 == # leading arguments not in the list
+	sl_v v = POP(); // arglist
+	n -= 2; // n-2 == # leading arguments not in the list
 	while(iscons(v)){
-		*sp++ = car_(v);
+		n++;
+		PUSH_SAFE(car_(v));
 		v = cdr_(v);
 	}
 	if(v != sl_nil){
-		SYNC;
+		ENTER;
 		lerrorf(sl_errarg, "apply: last argument: not a list");
 	}
-	n = sp-p;
 	goto LABEL(do_call);
 }
 
 OP(OP_LOADT)
-	*sp++ = sl_t;
+	PUSH(sl_t);
 	NEXT_OP;
 
 OP(OP_BUILTINP) {
-	sl_v v = sp[-1];
-	sp[-1] = (isbuiltin(v) || iscbuiltin(v)) ? sl_t : sl_nil;
+	sl_v v = sl.sp[-1];
+	sl.sp[-1] = (isbuiltin(v) || iscbuiltin(v)) ? sl_t : sl_nil;
 	NEXT_OP;
 }
 
 OP(OP_NEG) {
 LABEL(do_neg):
-	SYNC;
-	sl_v v = sp[-1];
+	ENTER;
+	sl_v v = sl.sp[-1];
 	s64int i64;
-	sp[-1] = isfixnum(v) ? fixnum_neg(v) : sl_neg(v);
+	sl.sp[-1] = isfixnum(v) ? fixnum_neg(v) : sl_neg(v);
 	NEXT_OP;
 }
 
 OP(OP_FIXNUMP)
-	sp[-1] = isfixnum(sp[-1]) ? sl_t : sl_nil;
+	sl.sp[-1] = isfixnum(sl.sp[-1]) ? sl_t : sl_nil;
 	NEXT_OP;
 
 OP(OP_MUL) {
 	n = *ip++;
 LABEL(apply_mul):
-	SYNC;
-	sl_v v = sl_mul_any(sp-n, n);
-	sp -= n;
-	*sp++ = v;
+	ENTER;
+	sl_v v = sl_mul_any(sl.sp-n, n);
+	sl.sp -= n;
+	PUSH(v);
 	NEXT_OP;
 }
 
 OP(OP_DIV0) {
-	sl_v a = sp[-2];
-	sl_v b = sp[-1];
+	sl_v a = sl.sp[-2];
+	sl_v b = sl.sp[-1];
 	if(sl_unlikely(b == 0)){
-		SYNC;
+		ENTER;
 		divide_by_0_error();
 	}
 	sl_v v;
@@ -751,11 +749,11 @@
 	if(bothfixnums(a, b))
 		v = fixnum((sl_fx)a / (sl_fx)b);
 	else{
-		SYNC;
+		ENTER;
 		v = sl_idiv2(a, b);
 	}
-	sp--;
-	sp[-1] = v;
+	POPN(1);
+	sl.sp[-1] = v;
 	NEXT_OP;
 }
 
@@ -762,20 +760,20 @@
 OP(OP_DIV) {
 	n = *ip++;
 LABEL(apply_div):
-	SYNC;
-	sl_v *p = sp-n;
+	ENTER;
+	sl_v *p = sl.sp-n;
 	if(n == 1){
-		sp[-1] = sl_div2(fixnum(1), *p);
+		sl.sp[-1] = sl_div2(fixnum(1), *p);
 	}else{
 		if(sl_unlikely(n > 2)){
-			*sp++ = *p;
+			PUSH(*p);
 			*p = fixnum(1);
 			p[1] = sl_mul_any(p, n);
-			*p = *(--sp);
+			*p = POP();
 		}
 		sl_v v = sl_div2(p[0], p[1]);
-		sp -= n;
-		*sp++ = v;
+		sl.sp -= n;
+		PUSH(v);
 	}
 	NEXT_OP;
 }
@@ -783,46 +781,45 @@
 OP(OP_VEC) {
 	n = *ip++;
 LABEL(apply_vec):;
-	SYNC;
+	ENTER;
 	int type = VEC_VEC;
-	sp -= n;
-	if(*sp == sl_vecstructsym){
+	sl.sp -= n;
+	if(*sl.sp == sl_vecstructsym){
 		if(n < 2)
 			arity_error(n, 2);
-		sp++;
+		sl.sp++;
 		n--;
 		type = VEC_STRUCT;
 	}
 	sl_v v = alloc_vec(n, 0);
-	memcpy(&vec_elt(v, 0), sp, n*sizeof(sl_v));
+	memcpy(&vec_elt(v, 0), sl.sp, n*sizeof(sl_v));
 	if(type != VEC_VEC){
-		sp--;
+		POPN(1);
 		vec_setsize(v, vec_size(v), type);
 	}
-	*sp++ = v;
+	PUSH(v);
 	NEXT_OP;
 }
 
 OP(OP_COMPARE)
-	sp[-2] = sl_compare(sp[-2], sp[-1], false);
-	sp--;
+	sl.sp[-2] = sl_compare(sl.sp[-2], sl.sp[-1], false);
+	POPN(1);
 	NEXT_OP;
 
 OP(OP_FOR) {
-	SYNC;
-	sl_v *p = sp;
+	usize p = sl.sp - sl.stack;
 	sl_v v;
-	sl_fx s = tofixnum(p[-3]);
-	sl_fx hi = tofixnum(p[-2]);
-	sp += 2;
-	SYNC;
+	sl_fx s = tofixnum(sl.stack[p-3]);
+	sl_fx hi = tofixnum(sl.stack[p-2]);
+	sl.sp += 2;
+	ENTER;
 	for(v = sl_void; s <= hi; s++){
-		p[0] = p[-1];
-		p[1] = fixnum(s);
+		sl.stack[p+0] = sl.stack[p-1];
+		sl.stack[p+1] = fixnum(s);
 		v = _applyn(1);
 	}
-	sp -= 4;
-	p[1] = v;
+	sl.sp -= 4;
+	sl.stack[p+1] = v;
 	NEXT_OP;
 }
 
@@ -833,28 +830,28 @@
 		i = GET_S32(ip);
 		ip += 4;
 	}
-	sl_v v = fn_vals(bp[-1]);
+	sl_v v = fn_vals(sl.stack[obp-1]);
 	assert(i < (int)vec_size(v));
 	v = vec_elt(v, i);
 	assert(issym(v));
 	sl_sym *sym = ptr(v);
 	if(sl_unlikely(isconst(sym))){
-		SYNC;
+		ENTER;
 		const_error(v);
 	}
-	sym->binding = sp[-1];
+	sym->binding = sl.sp[-1];
 	NEXT_OP;
 }
 
 OP(OP_VECP)
-	sp[-1] = isvec(sp[-1]) && !isstruct(sp[-1]) ? sl_t : sl_nil;
+	sl.sp[-1] = isvec(sl.sp[-1]) && !isstruct(sl.sp[-1]) ? sl_t : sl_nil;
 	NEXT_OP;
 
 OP(OP_TRYCATCH) {
-	SYNC;
+	ENTER;
 	sl_v v = do_trycatch();
-	sp--;
-	sp[-1] = v;
+	POPN(1);
+	sl.sp[-1] = v;
 	NEXT_OP;
 }
 
@@ -863,28 +860,28 @@
 	if(n == 2)
 		goto LABEL(do_add2);
 LABEL(apply_add):
-	SYNC;
-	sl_v v = sl_add_any(sp-n, n);
-	sp -= n;
-	*sp++ = v;
+	ENTER;
+	sl_v v = sl_add_any(sl.sp-n, n);
+	sl.sp -= n;
+	PUSH(v);
 	NEXT_OP;
 }
 
 OP(OP_LOADAL)
 	assert(nargs > 0);
-	*sp++ = bp[GET_S32(ip)];
+	PUSH(sl.stack[obp+GET_S32(ip)]);
 	ip += 4;
 	NEXT_OP;
 
 OP(OP_EQVP) {
-	sl_v a = sp[-2], b = sp[-1];
-	sp[-2] = (a == b || (leafp(a) && leafp(b) && sl_compare(a, b, true) == 0)) ? sl_t : sl_nil;
-	sp--;
+	sl_v a = sl.sp[-2], b = sl.sp[-1];
+	sl.sp[-2] = (a == b || (leafp(a) && leafp(b) && sl_compare(a, b, true) == 0)) ? sl_t : sl_nil;
+	POPN(1);
 	NEXT_OP;
 }
 
 OP(OP_KEYARGS) {
-	sl_v v = fn_vals(bp[-1]);
+	sl_v v = fn_vals(sl.stack[obp-1]);
 	v = vec_elt(v, 0);
 	int i = GET_S32(ip);
 	ip += 4;
@@ -892,10 +889,9 @@
 	ip += 4;
 	sl_fx s = GET_S32(ip);
 	ip += 4;
-	SYNC;
-	nargs = process_keys(v, i, x, labs(s)-(i+x), bp, nargs, s<0);
-	sp = sl.sp;
-	ipd = sp-1;
+	ENTER;
+	nargs = process_keys(v, i, x, labs(s)-(i+x), obp, nargs, s<0);
+	ipd = sl.sp-sl.stack-1;
 	NEXT_OP;
 }
 
@@ -906,28 +902,28 @@
 		goto LABEL(do_sub2);
 	if(n == 1)
 		goto LABEL(do_neg);
-	SYNC;
-	sl_v *p = sp-n;
+	ENTER;
+	sl_v *p = sl.sp-n;
 	// we need to pass the full arglist on to sl_add_any
 	// so it can handle rest args properly
-	*sp++ = *p;
+	PUSH(*p);
 	*p = fixnum(0);
 	sl_v v = sl_add_any(p, n);
 	s64int i64;
 	p[1] = isfixnum(v) ? fixnum_neg(v) : sl_neg(v);
-	p[0] = *(--sp);
+	p[0] = POP();
 	v = sl_add_any(p, 2);
-	sp -= n;
-	*sp++ = v;
+	sl.sp -= n;
+	PUSH(v);
 	NEXT_OP;
 }
 
 OP(OP_BRNL)
-	ip += *(--sp) == sl_nil ? GET_S32(ip) : 4;
+	ip += POP() == sl_nil ? GET_S32(ip) : 4;
 	NEXT_OP;
 
 OP(OP_SETAL)
-	bp[GET_S32(ip)] = sp[-1];
+	sl.stack[obp+GET_S32(ip)] = sl.sp[-1];
 	ip += 4;
 	NEXT_OP;
 
@@ -934,16 +930,16 @@
 OP(OP_BOXL) {
 	int i = GET_S32(ip);
 	ip += 4;
-	SYNC;
+	ENTER;
 	sl_v v = alloc_cons();
-	car_(v) = bp[i];
+	car_(v) = sl.stack[obp+i];
 	cdr_(v) = sl_nil;
-	bp[i] = v;
+	sl.stack[obp+i] = v;
 	NEXT_OP;
 }
 
 OP(OP_FNP)
-	sp[-1] = isfn(sp[-1]) ? sl_t : sl_nil;
+	sl.sp[-1] = isfn(sl.sp[-1]) ? sl_t : sl_nil;
 	NEXT_OP;
 
 OP(OP_JMPL)
@@ -951,27 +947,27 @@
 	NEXT_OP;
 
 OP(OP_BRNEL)
-	ip += sp[-2] != sp[-1] ? GET_S32(ip) : 4;
-	sp -= 2;
+	ip += sl.sp[-2] != sl.sp[-1] ? GET_S32(ip) : 4;
+	sl.sp -= 2;
 	NEXT_OP;
 
 OP(OP_BRNNL)
-	ip += *(--sp) != sl_nil ? GET_S32(ip) : 4;
+	ip += POP() != sl_nil ? GET_S32(ip) : 4;
 	NEXT_OP;
 
 OP(OP_LOADCL)
 	ip += 4;
-	*sp++ = vec_elt(bp[nargs], GET_S32(ip));
+	PUSH(vec_elt(sl.stack[obp+nargs], GET_S32(ip)));
 	ip += 4;
 	NEXT_OP;
 
 OP(OP_LOADVL) {
-	sl_v v = fn_vals(bp[-1]);
+	sl_v v = fn_vals(sl.stack[obp-1]);
 	v = vec_elt(v, GET_S32(ip));
 	ip += 4;
-	*sp++ = v;
+	PUSH(v);
 	NEXT_OP;
 }
 
 #undef fixnum_neg
-#undef SYNC
+#undef ENTER
--- a/test/mkfile
+++ b/test/mkfile
@@ -16,5 +16,5 @@
 test:QV:
 	for(t in $TESTS){
 		echo $t
-		../$O.out -S 8m $t
+		../$O.out $t
 	}
--- a/tools/bootstrap.sh
+++ b/tools/bootstrap.sh
@@ -11,5 +11,5 @@
 $WRAPPER $F ../tools/mkboot0.sl builtins.sl instructions.sl system.sl compiler.sl > ../boot/sl.boot && \
 ninja -C ../build && \
 cd ../boot && \
-$WRAPPER $F -S 8m ../tools/mkboot1.sl && \
+$WRAPPER $F ../tools/mkboot1.sl && \
 ninja -C ../build || { cp "$P/boot/sl.boot.bak" "$P/boot/sl.boot"; exit 1; }