shithub: femtolisp

ref: 17f2f68fb46834325bf81ae2b05907c89a7ec14d
dir: /flisp.c/

View raw version
/*
  femtoLisp

  by Jeff Bezanson (C) 2009
  Distributed under the BSD License
*/

#include "llt.h"
#include "flisp.h"
#include "operators.h"
#include "cvalues.h"
#include "opcodes.h"
#include "types.h"
#include "print.h"
#include "read.h"
#include "timefuncs.h"
#include "equal.h"
#include "hashing.h"
#include "table.h"
#include "iostream.h"
#include "fsixel.h"

typedef struct {
        char *name;
        builtin_t fptr;
}builtinspec_t;

__thread Fl *fl;

int
isbuiltin(value_t x)
{
        int i = uintval(x);
        return tag(x) == TAG_FUNCTION && i < nelem(builtins) && builtins[i].name != nil;
}

static value_t apply_cl(uint32_t nargs);

// error utilities ------------------------------------------------------------

void
free_readstate(fl_readstate_t *rs)
{
	htable_free(&rs->backrefs);
	htable_free(&rs->gensyms);
}

_Noreturn void
fl_exit(int status)
{
	fl->exiting = true;
	gc(0);
	exit(status);
}

#define FL_TRY \
	fl_exception_context_t _ctx; int l__tr, l__ca; \
	_ctx.sp = fl->SP; _ctx.frame = fl->curr_frame; _ctx.rdst = fl->readstate; _ctx.prev = fl->exctx; \
	_ctx.ngchnd = fl->N_GCHND; fl->exctx = &_ctx; \
	if(!setjmp(_ctx.buf)) \
		for(l__tr = 1; l__tr; l__tr = 0, (void)(fl->exctx = fl->exctx->prev))

#define FL_CATCH_INC \
	l__ca = 0, fl->lasterror = fl->FL_NIL, fl->throwing_frame = 0, fl->SP = _ctx.sp, fl->curr_frame = _ctx.frame

#define FL_CATCH \
	else \
		for(l__ca = 1; l__ca; FL_CATCH_INC)

#define FL_CATCH_NO_INC \
	else \
		for(l__ca = 1; l__ca;)

void
fl_savestate(fl_exception_context_t *_ctx)
{
	_ctx->sp = fl->SP;
	_ctx->frame = fl->curr_frame;
	_ctx->rdst = fl->readstate;
	_ctx->prev = fl->exctx;
	_ctx->ngchnd = fl->N_GCHND;
}

void
fl_restorestate(fl_exception_context_t *_ctx)
{
	fl->lasterror = fl->FL_NIL;
	fl->throwing_frame = 0;
	fl->SP = _ctx->sp;
	fl->curr_frame = _ctx->frame;
}

_Noreturn void
fl_raise(value_t e)
{
	fl->lasterror = e;
	// unwind read state
	while(fl->readstate != fl->exctx->rdst){
		free_readstate(fl->readstate);
		fl->readstate = fl->readstate->prev;
	}
	if(fl->throwing_frame == 0)
		fl->throwing_frame = fl->curr_frame;
	fl->N_GCHND = fl->exctx->ngchnd;
	fl_exception_context_t *thisctx = fl->exctx;
	if(fl->exctx->prev)   // don't throw past toplevel
		fl->exctx = fl->exctx->prev;
	longjmp(thisctx->buf, 1);
}

_Noreturn void
lerrorf(value_t e, char *format, ...)
{
	char msgbuf[256];
	va_list args;

	PUSH(e);
	va_start(args, format);
	vsnprintf(msgbuf, sizeof(msgbuf), format, args);
	value_t msg = string_from_cstr(msgbuf);
	va_end(args);

	e = POP();
	fl_raise(fl_list2(e, msg));
}

_Noreturn void
type_error(char *expected, value_t got)
{
	fl_raise(fl_listn(3, fl->TypeError, symbol(expected), got));
}

_Noreturn void
bounds_error(value_t arr, value_t ind)
{
	fl_raise(fl_listn(3, fl->BoundsError, arr, ind));
}

_Noreturn void
unbound_error(value_t sym)
{
	fl_raise(fl_listn(2, fl->UnboundError, sym));
}

// safe cast operators --------------------------------------------------------

#define isstring fl_isstring
#define SAFECAST_OP(type, ctype, cnvt) \
	ctype to##type(value_t v) \
	{ \
		if(is##type(v)) \
			return (ctype)cnvt(v); \
		type_error(#type, v); \
	}
SAFECAST_OP(cons, cons_t*, ptr)
SAFECAST_OP(symbol, symbol_t*, ptr)
SAFECAST_OP(fixnum, fixnum_t, numval)
//SAFECAST_OP(cvalue, cvalue_t*, ptr)
SAFECAST_OP(string, char*, cvalue_data)
#undef isstring

// symbol table ---------------------------------------------------------------

int
fl_is_keyword_name(char *str, size_t len)
{
	return (str[0] == ':' || str[len-1] == ':') && str[1] != '\0';
}

static symbol_t *
mk_symbol(char *str)
{
	symbol_t *sym;
	size_t len = strlen(str);

	sym = calloc(1, sizeof(*sym)-sizeof(void*) + len + 1);
	assert(((uintptr_t)sym & 0x7) == 0); // make sure malloc aligns 8
	sym->numtype = NONNUMERIC;
	if(fl_is_keyword_name(str, len)){
		value_t s = tagptr(sym, TAG_SYM);
		setc(s, s);
		sym->flags |= FLAG_KEYWORD;
	}else{
		sym->binding = UNBOUND;
	}
	sym->hash = memhash32(str, len)^0xAAAAAAAA;
	memmove(sym->name, str, len+1);
	return sym;
}

static symbol_t **
symtab_lookup(symbol_t **ptree, char *str)
{
	int x;
	while(*ptree != nil && (x = strcmp(str, (*ptree)->name)) != 0)
		ptree = x < 0 ? &(*ptree)->left : &(*ptree)->right;
	return ptree;
}

value_t
symbol(char *str)
{
	symbol_t **pnode;

	pnode = symtab_lookup(&fl->symtab, str);
	if(*pnode == nil)
		*pnode = mk_symbol(str);
	return tagptr(*pnode, TAG_SYM);
}

BUILTIN("gensym", gensym)
{
	argcount(nargs, 0);
	USED(args);
	gensym_t *gs = alloc_words(sizeof(gensym_t)/sizeof(void*));
	gs->id = fl->_gensym_ctr++;
	gs->binding = UNBOUND;
	gs->isconst = 0;
	gs->type = nil;
	return tagptr(gs, TAG_SYM);
}

value_t
gensym(void)
{
	return fn_builtin_gensym(nil, 0);
}

BUILTIN("gensym?", gensymp)
{
	argcount(nargs, 1);
	return isgensym(args[0]) ? fl->FL_T : fl->FL_F;
}

char *
uint2str(char *dest, size_t len, uint64_t num, uint32_t base)
{
	int i = len-1;
	uint64_t b = (uint64_t)base;
	char ch;
	dest[i--] = '\0';
	while(i >= 0){
		ch = (char)(num % b);
		if(ch < 10)
			ch += '0';
		else
			ch = ch-10+'a';
		dest[i--] = ch;
		num /= b;
		if(num == 0)
			break;
	}
	return &dest[i+1];
}

char *
symbol_name(value_t v)
{
	if(ismanaged(v)){
		gensym_t *gs = (gensym_t*)ptr(v);
		fl->gsnameno = 1-fl->gsnameno;
		char *n = uint2str(fl->gsname[fl->gsnameno]+1, sizeof(fl->gsname[0])-1, gs->id, 10);
		*(--n) = 'g';
		return n;
	}
	return ((symbol_t*)ptr(v))->name;
}

// conses ---------------------------------------------------------------------

value_t
mk_cons(void)
{
	cons_t *c;

	if(__unlikely(fl->curheap > fl->lim))
		gc(0);
	c = (cons_t*)fl->curheap;
	fl->curheap += sizeof(cons_t);
	return tagptr(c, TAG_CONS);
}

void *
alloc_words(int n)
{
	value_t *first;

	assert(n > 0);
	n = LLT_ALIGN(n, 2);   // only allocate multiples of 2 words
	if(__unlikely((value_t*)fl->curheap > ((value_t*)fl->lim)+2-n)){
		gc(0);
		while((value_t*)fl->curheap > ((value_t*)fl->lim)+2-n)
			gc(1);
	}
	first = (value_t*)fl->curheap;
	fl->curheap += (n*sizeof(value_t));
	return first;
}

value_t
alloc_vector(size_t n, int init)
{
	if(n == 0)
		return fl->the_empty_vector;
	value_t *c = alloc_words(n+1);
	value_t v = tagptr(c, TAG_VECTOR);
	vector_setsize(v, n);
	if(init){
		unsigned int i;
		for(i = 0; i < n; i++)
			vector_elt(v, i) = fl->FL_UNSPECIFIED;
	}
	return v;
}

// collector ------------------------------------------------------------------

void
fl_gc_handle(value_t *pv)
{
	if(fl->N_GCHND >= N_GC_HANDLES)
		lerrorf(fl->MemoryError, "out of gc handles");
	fl->GCHandleStack[fl->N_GCHND++] = pv;
}

void
fl_free_gc_handles(uint32_t n)
{
	assert(fl->N_GCHND >= n);
	fl->N_GCHND -= n;
}

value_t
relocate(value_t v)
{
	value_t a, d, nc, first, *pcdr;
	uintptr_t t = tag(v);

	if(t == TAG_CONS){
		// iterative implementation allows arbitrarily long cons chains
		pcdr = &first;
		do{
			if((a = car_(v)) == TAG_FWD){
				*pcdr = cdr_(v);
				return first;
			}
			*pcdr = nc = tagptr((cons_t*)fl->curheap, TAG_CONS);
			fl->curheap += sizeof(cons_t);
			d = cdr_(v);
			car_(v) = TAG_FWD;
			cdr_(v) = nc;
			car_(nc) = relocate(a);
			pcdr = &cdr_(nc);
			v = d;
		}while(iscons(v));
		*pcdr = d == fl->NIL ? fl->NIL : relocate(d);
		return first;
	}

	if((t&3) == 0)
		return v;
	if(!ismanaged(v))
		return v;
	if(isforwarded(v))
		return forwardloc(v);

	if(t == TAG_VECTOR){
		// N.B.: 0-length vectors secretly have space for a first element
		size_t i, sz = vector_size(v);
		if(vector_elt(v, -1) & 0x1){
			// grown vector
			nc = relocate(vector_elt(v, 0));
			forward(v, nc);
		}else{
			nc = tagptr(alloc_words(sz+1), TAG_VECTOR);
			vector_setsize(nc, sz);
			a = vector_elt(v, 0);
			forward(v, nc);
			if(sz > 0){
				vector_elt(nc, 0) = relocate(a);
				for(i = 1; i < sz; i++)
					vector_elt(nc, i) = relocate(vector_elt(v, i));
			}
		}
		return nc;
	}
	if(t == TAG_CPRIM){
		cprim_t *pcp = ptr(v);
		size_t nw = CPRIM_NWORDS-1+NWORDS(cp_class(pcp)->size);
		cprim_t *ncp = alloc_words(nw);
		while(nw--)
			((value_t*)ncp)[nw] = ((value_t*)pcp)[nw];
		nc = tagptr(ncp, TAG_CPRIM);
		forward(v, nc);
		return nc;
	}
	if(t == TAG_CVALUE)
		return cvalue_relocate(v);
	if(t == TAG_FUNCTION){
		function_t *fn = ptr(v);
		function_t *nfn = alloc_words(4);
		nfn->bcode = fn->bcode;
		nfn->vals = fn->vals;
		nc = tagptr(nfn, TAG_FUNCTION);
		forward(v, nc);
		nfn->env = relocate(fn->env);
		nfn->vals = relocate(nfn->vals);
		nfn->bcode = relocate(nfn->bcode);
		assert(!ismanaged(fn->name));
		nfn->name = fn->name;
		return nc;
	}
	if(t == TAG_SYM){
		gensym_t *gs = ptr(v);
		gensym_t *ng = alloc_words(sizeof(gensym_t)/sizeof(void*));
		ng->id = gs->id;
		ng->binding = gs->binding;
		ng->isconst = 0;
		nc = tagptr(ng, TAG_SYM);
		forward(v, nc);
		if(ng->binding != UNBOUND)
			ng->binding = relocate(ng->binding);
		return nc;
	}
	return v;
}

value_t
relocate_lispvalue(value_t v)
{
	return relocate(v);
}

static void
trace_globals(symbol_t *root)
{
	while(root != nil){
		if(root->binding != UNBOUND)
			root->binding = relocate(root->binding);
		trace_globals(root->left);
		root = root->right;
	}
}

void
gc(int mustgrow)
{
	void *temp;
	uint32_t i, f, top;
	fl_readstate_t *rs;

	fl->curheap = fl->tospace;
	if(fl->grew)
		fl->lim = fl->curheap+fl->heapsize*2-sizeof(cons_t);
	else
		fl->lim = fl->curheap+fl->heapsize-sizeof(cons_t);

	if(fl->throwing_frame > fl->curr_frame){
		top = fl->throwing_frame - 4;
		f = fl->Stack[fl->throwing_frame-4];
	}else{
		top = fl->SP;
		f = fl->curr_frame;
	}
	while(1){
		for(i = f; i < top; i++)
			fl->Stack[i] = relocate(fl->Stack[i]);
		if(f == 0)
			break;
		top = f - 4;
		f = fl->Stack[f-4];
	}
	for(i = 0; i < fl->N_GCHND; i++)
		*fl->GCHandleStack[i] = relocate(*fl->GCHandleStack[i]);
	trace_globals(fl->symtab);
	relocate_typetable();
	rs = fl->readstate;
	while(rs){
		value_t ent;
		for(i = 0; i < rs->backrefs.size; i++){
			ent = (value_t)rs->backrefs.table[i];
			if(ent != (value_t)HT_NOTFOUND)
				rs->backrefs.table[i] = (void*)relocate(ent);
		}
		for(i = 0; i < rs->gensyms.size; i++){
			ent = (value_t)rs->gensyms.table[i];
			if(ent != (value_t)HT_NOTFOUND)
				rs->gensyms.table[i] = (void*)relocate(ent);
		}
		rs->source = relocate(rs->source);
		rs = rs->prev;
	}
	fl->lasterror = relocate(fl->lasterror);
	fl->memory_exception_value = relocate(fl->memory_exception_value);
	fl->the_empty_vector = relocate(fl->the_empty_vector);

	sweep_finalizers();

#ifdef VERBOSEGC
	printf("GC: found %d/%d live conses\n",
		   (fl->curheap-fl->tospace)/sizeof(cons_t), fl->heapsize/sizeof(cons_t));
#endif
	temp = fl->tospace;
	fl->tospace = fl->fromspace;
	fl->fromspace = temp;

	// 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(fl->grew || ((fl->lim-fl->curheap) < (int)(fl->heapsize/5)) || mustgrow){
		temp = LLT_REALLOC(fl->tospace, fl->heapsize*2);
		if(temp == nil)
			fl_raise(fl->memory_exception_value);
		fl->tospace = temp;
		if(fl->grew){
			fl->heapsize *= 2;
			temp = bitvector_resize(fl->consflags, 0, fl->heapsize/sizeof(cons_t), 1);
			if(temp == nil)
				fl_raise(fl->memory_exception_value);
			fl->consflags = (uint32_t*)temp;
		}
		fl->grew = !fl->grew;
	}
	if(fl->curheap > fl->lim)  // all data was live
		gc(0);
}

static void
grow_stack(void)
{
	size_t newsz = fl->N_STACK * 2;
	value_t *ns = LLT_REALLOC(fl->Stack, newsz*sizeof(value_t));
	if(ns == nil)
		lerrorf(fl->MemoryError, "stack overflow");
	fl->Stack = ns;
	fl->N_STACK = newsz;
}

// utils ----------------------------------------------------------------------

// apply function with n args on the stack
static value_t
_applyn(uint32_t n)
{
	value_t f = fl->Stack[fl->SP-n-1];
	uint32_t saveSP = fl->SP;
	value_t v;
	if(iscbuiltin(f)){
		v = ((builtin_t*)ptr(f))[3](&fl->Stack[fl->SP-n], n);
	}else if(isfunction(f)){
		v = apply_cl(n);
	}else if(isbuiltin(f)){
		value_t tab = symbol_value(fl->builtins_table_sym);
		if(ptr(tab) == nil)
			unbound_error(tab);
		fl->Stack[fl->SP-n-1] = vector_elt(tab, uintval(f));
		v = apply_cl(n);
	}else{
		type_error("function", f);
	}
	fl->SP = saveSP;
	return v;
}

value_t
fl_apply(value_t f, value_t l)
{
	value_t v = l;
	uint32_t n = fl->SP;

	PUSH(f);
	while(iscons(v)){
		if(fl->SP >= fl->N_STACK)
			grow_stack();
		PUSH(car_(v));
		v = cdr_(v);
	}
	n = fl->SP - n - 1;
	v = _applyn(n);
	POPN(n+1);
	return v;
}

value_t
fl_applyn(uint32_t n, value_t f, ...)
{
	va_list ap;
	va_start(ap, f);
	size_t i;

	PUSH(f);
	while(fl->SP+n > fl->N_STACK)
		grow_stack();
	for(i = 0; i < n; i++){
		value_t a = va_arg(ap, value_t);
		PUSH(a);
	}
	value_t v = _applyn(n);
	POPN(n+1);
	va_end(ap);
	return v;
}

value_t
fl_listn(size_t n, ...)
{
	va_list ap;
	va_start(ap, n);
	uint32_t si = fl->SP;
	size_t i;

	while(fl->SP+n > fl->N_STACK)
		grow_stack();
	for(i = 0; i < n; i++){
		value_t a = va_arg(ap, value_t);
		PUSH(a);
	}
	cons_t *c = alloc_words(n*2);
	cons_t *l = c;
	for(i = 0; i < n; i++){
		c->car = fl->Stack[si++];
		c->cdr = tagptr(c+1, TAG_CONS);
		c++;
	}
	c[-1].cdr = fl->NIL;

	POPN(n);
	va_end(ap);
	return tagptr(l, TAG_CONS);
}

value_t
fl_list2(value_t a, value_t b)
{
	PUSH(a);
	PUSH(b);
	cons_t *c = alloc_words(4);
	b = POP();
	a = POP();
	c[0].car = a;
	c[0].cdr = tagptr(c+1, TAG_CONS);
	c[1].car = b;
	c[1].cdr = fl->NIL;
	return tagptr(c, TAG_CONS);
}

value_t
fl_cons(value_t a, value_t b)
{
	PUSH(a);
	PUSH(b);
	value_t c = mk_cons();
	cdr_(c) = POP();
	car_(c) = POP();
	return c;
}

int
fl_isnumber(value_t v)
{
	if(isfixnum(v))
		return 1;
	if(iscprim(v)){
		cprim_t *c = ptr(v);
		return c->type != fl->runetype;
	}
	if(iscvalue(v)){
		cvalue_t *c = ptr(v);
		return valid_numtype(cv_class(c)->numtype);
	}
	return 0;
}

// eval -----------------------------------------------------------------------

static value_t
list(value_t *args, uint32_t nargs, int star)
{
	cons_t *c;
	uint32_t i;
	value_t v;
	v = cons_reserve(nargs);
	c = ptr(v);
	for(i = 0; i < nargs; i++){
		c->car = args[i];
		c->cdr = tagptr(c+1, TAG_CONS);
		c++;
	}
	if(star)
		(c-2)->cdr = (c-1)->car;
	else
		(c-1)->cdr = fl->NIL;
	return v;
}

static value_t
copy_list(value_t L)
{
	if(!iscons(L))
		return fl->NIL;
	PUSH(fl->NIL);
	PUSH(L);
	value_t *plcons = &fl->Stack[fl->SP-2];
	value_t *pL = &fl->Stack[fl->SP-1];
	value_t c;
	c = mk_cons(); PUSH(c);  // save first cons
	car_(c) = car_(*pL);
	cdr_(c) = fl->NIL;
	*plcons = c;
	*pL = cdr_(*pL);
	while(iscons(*pL)){
		c = mk_cons();
		car_(c) = car_(*pL);
		cdr_(c) = fl->NIL;
		cdr_(*plcons) = c;
		*plcons = c;
		*pL = cdr_(*pL);
	}
	c = POP();  // first cons
	POPN(2);
	return c;
}

static value_t
do_trycatch(void)
{
	uint32_t saveSP = fl->SP;
	value_t v = fl->NIL;
	value_t thunk = fl->Stack[fl->SP-2];
	fl->Stack[fl->SP-2] = fl->Stack[fl->SP-1];
	fl->Stack[fl->SP-1] = thunk;

	FL_TRY{
		v = apply_cl(0);
	}
	FL_CATCH{
		v = fl->Stack[saveSP-2];
		PUSH(v);
		PUSH(fl->lasterror);
		v = apply_cl(1);
	}
	fl->SP = saveSP;
	return v;
}

/*
  argument layout on stack is
  |--required args--|--opt args--|--kw args--|--rest args...
*/
static uint32_t
process_keys(value_t kwtable, uint32_t nreq, uint32_t nkw, uint32_t nopt, uint32_t bp, uint32_t nargs, int va)
{
	uint32_t extr = nopt+nkw;
	uint32_t ntot = nreq+extr;
	value_t args[64], v = fl->NIL;
	uint32_t i, a = 0, nrestargs;
	value_t s1 = fl->Stack[fl->SP-1];
	value_t s2 = fl->Stack[fl->SP-2];
	value_t s4 = fl->Stack[fl->SP-4];
	value_t s5 = fl->Stack[fl->SP-5];
	if(nargs < nreq)
		lerrorf(fl->ArgError, "too few arguments");
	if(extr > nelem(args))
		lerrorf(fl->ArgError, "too many arguments");
	for(i = 0; i < extr; i++)
		args[i] = UNBOUND;
	for(i = nreq; i < nargs; i++){
		v = fl->Stack[bp+i];
		if(issymbol(v) && iskeyword((symbol_t*)ptr(v)))
			break;
		if(a >= nopt)
			goto no_kw;
		args[a++] = v;
	}
	if(i >= nargs)
		goto no_kw;
	// now process keywords
	uintptr_t n = vector_size(kwtable)/2;
	do{
		i++;
		if(i >= nargs)
			lerrorf(fl->ArgError, "keyword %s requires an argument", symbol_name(v));
		value_t hv = fixnum(((symbol_t*)ptr(v))->hash);
		lltint_t lx = numval(hv);
		uintptr_t x = 2*((lx < 0 ? -lx : lx) % n);
		if(vector_elt(kwtable, x) == v){
			uintptr_t idx = numval(vector_elt(kwtable, x+1));
			assert(idx < nkw);
			idx += nopt;
			if(args[idx] == UNBOUND){
				// if duplicate key, keep first value
				args[idx] = fl->Stack[bp+i];
			}
		}else{
			lerrorf(fl->ArgError, "unsupported keyword %s", symbol_name(v));
		}
		i++;
		if(i >= nargs)
			break;
		v = fl->Stack[bp+i];
	}while(issymbol(v) && iskeyword((symbol_t*)ptr(v)));
no_kw:
	nrestargs = nargs - i;
	if(!va && nrestargs > 0)
		lerrorf(fl->ArgError, "too many arguments");
	nargs = ntot + nrestargs;
	if(nrestargs)
		memmove(&fl->Stack[bp+ntot], &fl->Stack[bp+i], nrestargs*sizeof(value_t));
	memmove(&fl->Stack[bp+nreq], args, extr*sizeof(value_t));
	fl->SP = bp + nargs;
	assert(fl->SP < fl->N_STACK-5);
	PUSH(s5);
	PUSH(s4);
	PUSH(nargs);
	PUSH(s2);
	PUSH(s1);
	fl->curr_frame = fl->SP;
	return nargs;
}

#if BYTE_ORDER == BIG_ENDIAN
#define GET_INT32(a) \
	((int32_t) \
	((((int32_t)a[0])<<0)  | \
	 (((int32_t)a[1])<<8)  | \
	 (((int32_t)a[2])<<16) | \
	 (((int32_t)a[3])<<24)))
#define GET_INT16(a) \
	((int16_t) \
	((((int16_t)a[0])<<0)  | \
	 (((int16_t)a[1])<<8)))
#define PUT_INT32(a, i) (*(int32_t*)(a) = bswap_32((int32_t)(i)))
#else
#define GET_INT32(a) (*(int32_t*)a)
#define GET_INT16(a) (*(int16_t*)a)
#define PUT_INT32(a, i) (*(int32_t*)(a) = (int32_t)(i))
#endif

#define OP(x) case x:
#define NEXT_OP break

/*
  stack on entry: <func>  <nargs args...>
  caller's responsibility:
  - put the stack in this state
  - provide arg count
  - respect tail position
  - restore SP

  callee's responsibility:
  - check arg counts
  - allocate vararg array
  - push closed env, set up new environment
*/
static value_t
apply_cl(uint32_t nargs)
{
	uint32_t top_frame = fl->curr_frame;
	// frame variables
	uint32_t n, captured;
	uint32_t bp;
	const uint8_t *ip;
	fixnum_t s, hi;
	int tail, x;

	// temporary variables (not necessary to preserve across calls)
	uint32_t op, i;
	symbol_t *sym;
	cons_t *c;
	value_t *pv;
	int64_t accum;
	value_t func, v, e;

	n = 0;
	v = 0;
	USED(n);
	USED(v);
apply_cl_top:
	captured = 0;
	func = fl->Stack[fl->SP-nargs-1];
	ip = cv_data((cvalue_t*)ptr(fn_bcode(func)));
	assert(!ismanaged((uintptr_t)ip));
	while(fl->SP+GET_INT32(ip) > fl->N_STACK)
		grow_stack();
	ip += 4;

	bp = fl->SP-nargs;
	PUSH(fn_env(func));
	PUSH(fl->curr_frame);
	PUSH(nargs);
	fl->SP++;//PUSH(0); //ip
	PUSH(0); //captured?
	fl->curr_frame = fl->SP;

	op = *ip++;
	while(1){
		switch(op){
		OP(OP_LOADA0)
			PUSH(captured ? vector_elt(fl->Stack[bp], 0) : fl->Stack[bp]);
			NEXT_OP;

		OP(OP_LOADA1)
			PUSH(captured ? vector_elt(fl->Stack[bp], 1) : fl->Stack[bp+1]);
			NEXT_OP;

		OP(OP_LOADV)
			v = fn_vals(fl->Stack[bp-1]);
			assert(*ip < vector_size(v));
			PUSH(vector_elt(v, *ip++));
			NEXT_OP;

		OP(OP_BRF)
			ip += POP() == fl->FL_F ? GET_INT16(ip) : 2;
			NEXT_OP;

		OP(OP_POP)
			POPN(1);
			NEXT_OP;

		OP(OP_TCALLL)
			tail = 1;
			if(0){
		OP(OP_CALLL)
				tail = 0;
			}
			n = GET_INT32(ip);
			ip += 4;
			if(0){
		OP(OP_TCALL)
				tail = 1;
				if(0){
		OP(OP_CALL)
					tail = 0;
				}
				n = *ip++;  // nargs
			}
		do_call:
			func = fl->Stack[fl->SP-n-1];
			if(tag(func) == TAG_FUNCTION){
				if(func > (N_BUILTINS<<3)){
					if(tail){
						fl->curr_frame = fl->Stack[fl->curr_frame-4];
						for(s = -1; s < (fixnum_t)n; s++)
							fl->Stack[bp+s] = fl->Stack[fl->SP-n+s];
						fl->SP = bp+n;
					}else{
						fl->Stack[fl->curr_frame-2] = (uintptr_t)ip;
					}
					nargs = n;
					goto apply_cl_top;
				}else{
					i = uintval(func);
					if(isbuiltin(func)){
						s = builtins[i].nargs;
						if(s >= 0)
							argcount(n, s);
						else if(s != ANYARGS && (signed)n < -s)
							argcount(n, -s);
						// remove function arg
						for(s = fl->SP-n-1; s < (int)fl->SP-1; s++)
							fl->Stack[s] = fl->Stack[s+1];
						fl->SP--;
						switch(i){
						case OP_LIST:   goto apply_list;
						case OP_VECTOR: goto apply_vector;
						case OP_APPLY:  goto apply_apply;
						case OP_ADD:	goto apply_add;
						case OP_SUB:	goto apply_sub;
						case OP_MUL:	goto apply_mul;
						case OP_DIV:	goto apply_div;
						default:
							op = i;
							continue;
						}
					}
				}
			}else if(iscbuiltin(func)){
				s = fl->SP;
				v = (((builtin_t*)ptr(func))[3])(&fl->Stack[fl->SP-n], n);
				fl->SP = s-n;
				fl->Stack[fl->SP-1] = v;
				NEXT_OP;
			}
			type_error("function", func);

		OP(OP_LOADGL)
			v = fn_vals(fl->Stack[bp-1]);
			v = vector_elt(v, GET_INT32(ip));
			ip += 4;
			if(0){
		OP(OP_LOADG)
				v = fn_vals(fl->Stack[bp-1]);
				assert(*ip < vector_size(v));
				v = vector_elt(v, *ip);
				ip++;
			}
			assert(issymbol(v));
			sym = (symbol_t*)ptr(v);
			if(sym->binding == UNBOUND)
				unbound_error(v);
			PUSH(sym->binding);
			NEXT_OP;

		OP(OP_LOADA)
			assert(nargs > 0);
			i = *ip++;
			if(captured){
				e = fl->Stack[bp];
				assert(isvector(e));
				assert(i < vector_size(e));
				v = vector_elt(e, i);
			}else{
				v = fl->Stack[bp+i];
			}
			PUSH(v);
			NEXT_OP;

		OP(OP_LOADC)
			s = *ip++;
			i = *ip++;
			v = fl->Stack[bp+nargs];
			while(s--)
				v = vector_elt(v, vector_size(v)-1);
			assert(isvector(v));
			assert(i < vector_size(v));
			PUSH(vector_elt(v, i));
			NEXT_OP;

		OP(OP_RET)
			v = POP();
			fl->SP = fl->curr_frame;
			fl->curr_frame = fl->Stack[fl->SP-4];
			if(fl->curr_frame == top_frame)
				return v;
			fl->SP -= 5+nargs;
			captured = fl->Stack[fl->curr_frame-1];
			ip = (uint8_t*)fl->Stack[fl->curr_frame-2];
			nargs = fl->Stack[fl->curr_frame-3];
			bp = fl->curr_frame - 5 - nargs;
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_DUP)
			fl->SP++;
			fl->Stack[fl->SP-1] = fl->Stack[fl->SP-2];
			NEXT_OP;

		OP(OP_CAR)
			v = fl->Stack[fl->SP-1];
			if(!iscons(v))
				type_error("cons", v);
			fl->Stack[fl->SP-1] = car_(v);
			NEXT_OP;

		OP(OP_CDR)
			v = fl->Stack[fl->SP-1];
			if(!iscons(v))
				type_error("cons", v);
			fl->Stack[fl->SP-1] = cdr_(v);
			NEXT_OP;

		OP(OP_CLOSURE)
			// build a closure (lambda args body . env)
			if(nargs > 0 && !captured){
				// save temporary environment to the heap
				n = nargs;
				pv = alloc_words(n + 2);
				PUSH(tagptr(pv, TAG_VECTOR));
				pv[0] = fixnum(n+1);
				pv++;
				do{
					pv[n] = fl->Stack[bp+n];
				}while(n--);
				// environment representation changed; install
				// the new representation so everybody can see it
				captured = 1;
				fl->Stack[fl->curr_frame-1] = 1;
				fl->Stack[bp] = fl->Stack[fl->SP-1];
			}else{
				PUSH(fl->Stack[bp]); // env has already been captured; share
			}
			if(fl->curheap > fl->lim-2)
				gc(0);
			pv = (value_t*)fl->curheap;
			fl->curheap += (4*sizeof(value_t));
			e = fl->Stack[fl->SP-2];  // closure to copy
			assert(isfunction(e));
			pv[0] = ((value_t*)ptr(e))[0];
			pv[1] = ((value_t*)ptr(e))[1];
			pv[2] = fl->Stack[fl->SP-1];  // env
			pv[3] = ((value_t*)ptr(e))[3];
			POPN(1);
			fl->Stack[fl->SP-1] = tagptr(pv, TAG_FUNCTION);
			NEXT_OP;

		OP(OP_SETA)
			assert(nargs > 0);
			v = fl->Stack[fl->SP-1];
			i = *ip++;
			if(captured){
				e = fl->Stack[bp];
				assert(isvector(e));
				assert(i < vector_size(e));
				vector_elt(e, i) = v;
			}else{
				fl->Stack[bp+i] = v;
			}
			NEXT_OP;

		OP(OP_JMP)
			ip += GET_INT16(ip);
			NEXT_OP;

		OP(OP_LOADC00)
			PUSH(vector_elt(fl->Stack[bp+nargs], 0));
			NEXT_OP;

		OP(OP_PAIRP)
			fl->Stack[fl->SP-1] = iscons(fl->Stack[fl->SP-1]) ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_BRNE)
			ip += fl->Stack[fl->SP-2] != fl->Stack[fl->SP-1] ? GET_INT16(ip) : 2;
			POPN(2);
			NEXT_OP;

		OP(OP_LOADT)
			PUSH(fl->FL_T);
			NEXT_OP;

		OP(OP_LOAD0)
			PUSH(fixnum(0));
			NEXT_OP;

		OP(OP_LOADC01)
			PUSH(vector_elt(fl->Stack[bp+nargs], 1));
			NEXT_OP;

		OP(OP_AREF)
			v = fl->Stack[fl->SP-2];
			if(isvector(v)){
				e = fl->Stack[fl->SP-1];
				i = isfixnum(e) ? numval(e) : (uint32_t)toulong(e);
				if(i >= vector_size(v))
					bounds_error(v, e);
				v = vector_elt(v, i);
			}else if(isarray(v)){
				v = cvalue_array_aref(&fl->Stack[fl->SP-2]);
			}else{
				type_error("sequence", v);
			}
			POPN(1);
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_ATOMP)
			fl->Stack[fl->SP-1] = iscons(fl->Stack[fl->SP-1]) ? fl->FL_F : fl->FL_T;
			NEXT_OP;

		OP(OP_BRT)
			ip += POP() != fl->FL_F ? GET_INT16(ip) : 2;
			NEXT_OP;

		OP(OP_BRNN)
			ip += POP() != fl->NIL ? GET_INT16(ip) : 2;
			NEXT_OP;

		OP(OP_LOAD1)
			PUSH(fixnum(1));
			NEXT_OP;

		OP(OP_LT)
			x = numeric_compare(fl->Stack[fl->SP-2], fl->Stack[fl->SP-1], 0, 0, 0);
			if(x > 1)
				x = numval(fl_compare(fl->Stack[fl->SP-2], fl->Stack[fl->SP-1]));
			POPN(1);
			fl->Stack[fl->SP-1] = x < 0 ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_ADD2)
			if(bothfixnums(fl->Stack[fl->SP-1], fl->Stack[fl->SP-2])){
				s = numval(fl->Stack[fl->SP-1]) + numval(fl->Stack[fl->SP-2]);
				v = fits_fixnum(s) ? fixnum(s) : mk_xlong(s);
			}else{
				v = fl_add_any(&fl->Stack[fl->SP-2], 2, 0);
			}
			POPN(1);
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_SETCDR)
			cdr(fl->Stack[fl->SP-2]) = fl->Stack[fl->SP-1];
			POPN(1);
			NEXT_OP;

		OP(OP_LOADF)
			PUSH(fl->FL_F);
			NEXT_OP;

		OP(OP_CONS)
			if(fl->curheap > fl->lim)
				gc(0);
			c = (cons_t*)fl->curheap;
			fl->curheap += sizeof(cons_t);
			c->car = fl->Stack[fl->SP-2];
			c->cdr = fl->Stack[fl->SP-1];
			fl->Stack[fl->SP-2] = tagptr(c, TAG_CONS);
			POPN(1); NEXT_OP;

		OP(OP_EQ)
			fl->Stack[fl->SP-2] = fl->Stack[fl->SP-2] == fl->Stack[fl->SP-1] ? fl->FL_T : fl->FL_F;
			POPN(1);
			NEXT_OP;

		OP(OP_SYMBOLP)
			fl->Stack[fl->SP-1] = issymbol(fl->Stack[fl->SP-1]) ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_NOT)
			fl->Stack[fl->SP-1] = fl->Stack[fl->SP-1] == fl->FL_F ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_CADR)
			v = fl->Stack[fl->SP-1];
			if(!iscons(v))
				type_error("cons", v);
			v = cdr_(v);
			if(!iscons(v))
				type_error("cons", v);
			fl->Stack[fl->SP-1] = car_(v);
			NEXT_OP;

		OP(OP_NEG)
		do_neg:
			fl->Stack[fl->SP-1] = fl_neg(fl->Stack[fl->SP-1]);
			NEXT_OP;

		OP(OP_NULLP)
			fl->Stack[fl->SP-1] = fl->Stack[fl->SP-1] == fl->NIL ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_BOOLEANP)
			v = fl->Stack[fl->SP-1];
			fl->Stack[fl->SP-1] = (v == fl->FL_T || v == fl->FL_F) ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_NUMBERP)
			v = fl->Stack[fl->SP-1];
			fl->Stack[fl->SP-1] = fl_isnumber(v) ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_FIXNUMP)
			fl->Stack[fl->SP-1] = isfixnum(fl->Stack[fl->SP-1]) ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_BOUNDP)
			sym = tosymbol(fl->Stack[fl->SP-1]);
			fl->Stack[fl->SP-1] = sym->binding == UNBOUND ? fl->FL_F : fl->FL_T;
			NEXT_OP;

		OP(OP_BUILTINP)
			v = fl->Stack[fl->SP-1];
			fl->Stack[fl->SP-1] = (isbuiltin(v) || iscbuiltin(v)) ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_FUNCTIONP)
			v = fl->Stack[fl->SP-1];
			fl->Stack[fl->SP-1] = ((tag(v) == TAG_FUNCTION &&
							(isbuiltin(v) || v>(N_BUILTINS<<3))) ||
						   iscbuiltin(v)) ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_VECTORP)
			fl->Stack[fl->SP-1] = isvector(fl->Stack[fl->SP-1]) ? fl->FL_T : fl->FL_F;
			NEXT_OP;

		OP(OP_JMPL)
			ip += GET_INT32(ip);
			NEXT_OP;

		OP(OP_BRFL)
			ip += POP() == fl->FL_F ? GET_INT32(ip) : 4;
			NEXT_OP;

		OP(OP_BRTL)
			ip += POP() != fl->FL_F ? GET_INT32(ip) : 4;
			NEXT_OP;

		OP(OP_BRNEL)
			ip += fl->Stack[fl->SP-2] != fl->Stack[fl->SP-1] ? GET_INT32(ip) : 4;
			POPN(2);
			NEXT_OP;

		OP(OP_BRNNL)
			ip += POP() != fl->NIL ? GET_INT32(ip) : 4;
			NEXT_OP;

		OP(OP_BRN)
			ip += POP() == fl->NIL ? GET_INT16(ip) : 2;
			NEXT_OP;

		OP(OP_BRNL)
			ip += POP() == fl->NIL ? GET_INT32(ip) : 4;
			NEXT_OP;

		OP(OP_EQV)
			if(fl->Stack[fl->SP-2] == fl->Stack[fl->SP-1])
				v = fl->FL_T;
			else if(!leafp(fl->Stack[fl->SP-2]) || !leafp(fl->Stack[fl->SP-1]))
				v = fl->FL_F;
			else
				v = compare_(fl->Stack[fl->SP-2], fl->Stack[fl->SP-1], 1) == 0 ? fl->FL_T : fl->FL_F;
			fl->Stack[fl->SP-2] = v;
			POPN(1);
			NEXT_OP;

		OP(OP_EQUAL)
			if(fl->Stack[fl->SP-2] == fl->Stack[fl->SP-1])
				v = fl->FL_T;
			else
				v = compare_(fl->Stack[fl->SP-2], fl->Stack[fl->SP-1], 1) == 0 ? fl->FL_T : fl->FL_F;
			fl->Stack[fl->SP-2] = v;
			POPN(1);
			NEXT_OP;

		OP(OP_SETCAR)
			car(fl->Stack[fl->SP-2]) = fl->Stack[fl->SP-1];
			POPN(1);
			NEXT_OP;

		OP(OP_LIST)
			n = *ip++;
		apply_list:
			if(n > 0){
				v = list(&fl->Stack[fl->SP-n], n, 0);
				POPN(n);
				PUSH(v);
			}else{
				PUSH(fl->NIL);
			}
			NEXT_OP;

		OP(OP_TAPPLY)
			tail = 1;
			if(0){
		OP(OP_APPLY)
				tail = 0;
			}
			n = *ip++;
		apply_apply:
			v = POP();	 // arglist
			n = fl->SP-(n-2);  // n-2 == # leading arguments not in the list
			while(iscons(v)){
				if(fl->SP >= fl->N_STACK)
					grow_stack();
				PUSH(car_(v));
				v = cdr_(v);
			}
			n = fl->SP-n;
			goto do_call;

		OP(OP_ADD)
			n = *ip++;
		apply_add:
			s = 0;
			i = fl->SP-n;
			for(; i < fl->SP; i++){
				if(isfixnum(fl->Stack[i])){
					s += numval(fl->Stack[i]);
					if(!fits_fixnum(s)){
						i++;
						goto add_ovf;
					}
				}else{
				add_ovf:
					v = fl_add_any(&fl->Stack[i], fl->SP-i, s);
					break;
				}
			}
			if(i == fl->SP)
				v = fixnum(s);
			POPN(n);
			PUSH(v);
			NEXT_OP;

		OP(OP_SUB)
			n = *ip++;
		apply_sub:
			if(n == 2)
				goto do_sub2;
			if(n == 1)
				goto do_neg;
			i = fl->SP-n;
			// we need to pass the full arglist on to fl_add_any
			// so it can handle rest args properly
			PUSH(fl->Stack[i]);
			fl->Stack[i] = fixnum(0);
			fl->Stack[i+1] = fl_neg(fl_add_any(&fl->Stack[i], n, 0));
			fl->Stack[i] = POP();
			v = fl_add_any(&fl->Stack[i], 2, 0);
			POPN(n);
			PUSH(v);
			NEXT_OP;

		OP(OP_SUB2)
		do_sub2:
			if(bothfixnums(fl->Stack[fl->SP-2], fl->Stack[fl->SP-1])){
				s = numval(fl->Stack[fl->SP-2]) - numval(fl->Stack[fl->SP-1]);
				v = fits_fixnum(s) ? fixnum(s) : mk_xlong(s);
			}else{
				fl->Stack[fl->SP-1] = fl_neg(fl->Stack[fl->SP-1]);
				v = fl_add_any(&fl->Stack[fl->SP-2], 2, 0);
			}
			POPN(1);
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_MUL)
			n = *ip++;
		apply_mul:
			accum = 1;
			for(i = fl->SP-n; i < fl->SP; i++){
				if(isfixnum(fl->Stack[i])){
					accum *= numval(fl->Stack[i]);
				}else{
					v = fl_mul_any(&fl->Stack[i], fl->SP-i, accum);
					break;
				}
			}
			if(i == fl->SP)
				v = fits_fixnum(accum) ? fixnum(accum) : return_from_int64(accum);
			POPN(n);
			PUSH(v);
			NEXT_OP;

		OP(OP_DIV)
			n = *ip++;
		apply_div:
			i = fl->SP-n;
			if(n == 1){
				fl->Stack[fl->SP-1] = fl_div2(fixnum(1), fl->Stack[i]);
			}else{
				if(n > 2){
					PUSH(fl->Stack[i]);
					fl->Stack[i] = fixnum(1);
					fl->Stack[i+1] = fl_mul_any(&fl->Stack[i], n, 1);
					fl->Stack[i] = POP();
				}
				v = fl_div2(fl->Stack[i], fl->Stack[i+1]);
				POPN(n);
				PUSH(v);
			}
			NEXT_OP;

		OP(OP_IDIV)
			v = fl->Stack[fl->SP-2]; e = fl->Stack[fl->SP-1];
			if(bothfixnums(v, e)){
				if(e == 0)
					DivideByZeroError();
				v = fixnum(numval(v) / numval(e));
			}else
				v = fl_idiv2(v, e);
			POPN(1);
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_NUMEQ)
			v = fl->Stack[fl->SP-2]; e = fl->Stack[fl->SP-1];
			if(bothfixnums(v, e))
				v = v == e ? fl->FL_T : fl->FL_F;
			else
				v = numeric_compare(v, e, 1, 0, 1) == 0 ? fl->FL_T : fl->FL_F;
			POPN(1);
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_COMPARE)
			fl->Stack[fl->SP-2] = compare_(fl->Stack[fl->SP-2], fl->Stack[fl->SP-1], 0);
			POPN(1);
			NEXT_OP;

		OP(OP_ARGC)
			n = *ip++;
			if(0){
		OP(OP_LARGC)
				n = GET_INT32(ip);
				ip += 4;
			}
			argcount(nargs, n);
			NEXT_OP;

		OP(OP_VECTOR)
			n = *ip++;
		apply_vector:
			v = alloc_vector(n, 0);
			if(n){
				memmove(&vector_elt(v, 0), &fl->Stack[fl->SP-n], n*sizeof(value_t));
				POPN(n);
			}
			PUSH(v);
			NEXT_OP;

		OP(OP_ASET)
			e = fl->Stack[fl->SP-3];
			if(isvector(e)){
				i = tofixnum(fl->Stack[fl->SP-2]);
				if(i >= vector_size(e))
					bounds_error(v, fl->Stack[fl->SP-1]);
				vector_elt(e, i) = (v = fl->Stack[fl->SP-1]);
			}else if(isarray(e)){
				v = cvalue_array_aset(&fl->Stack[fl->SP-3]);
			}else{
				type_error("sequence", e);
			}
			POPN(2);
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_FOR)
			s  = tofixnum(fl->Stack[fl->SP-3]);
			hi = tofixnum(fl->Stack[fl->SP-2]);
			//f = fl->Stack[fl->SP-1];
			v = fl->FL_UNSPECIFIED;
			fl->SP += 2;
			n = fl->SP;
			for(; s <= hi; s++){
				fl->Stack[fl->SP-2] = fl->Stack[fl->SP-3];
				fl->Stack[fl->SP-1] = fixnum(s);
				v = apply_cl(1);
				fl->SP = n;
			}
			POPN(4);
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_LOADNIL)
			PUSH(fl->NIL);
			NEXT_OP;

		OP(OP_LOADI8)
			s = (int8_t)*ip++;
			PUSH(fixnum(s));
			NEXT_OP;

		OP(OP_LOADVL)
			v = fn_vals(fl->Stack[bp-1]);
			v = vector_elt(v, GET_INT32(ip));
			ip += 4;
			PUSH(v);
			NEXT_OP;

		OP(OP_SETGL)
			v = fn_vals(fl->Stack[bp-1]);
			v = vector_elt(v, GET_INT32(ip));
			ip += 4;
			if(0){
		OP(OP_SETG)
				v = fn_vals(fl->Stack[bp-1]);
				assert(*ip < vector_size(v));
				v = vector_elt(v, *ip);
				ip++;
			}
			assert(issymbol(v));
			sym = (symbol_t*)ptr(v);
			v = fl->Stack[fl->SP-1];
			if(!isconstant(sym))
				sym->binding = v;
			NEXT_OP;

		OP(OP_LOADAL)
			assert(nargs > 0);
			i = GET_INT32(ip);
			ip += 4;
			v = captured ? vector_elt(fl->Stack[bp], i) : fl->Stack[bp+i];
			PUSH(v);
			NEXT_OP;

		OP(OP_SETAL)
			assert(nargs > 0);
			v = fl->Stack[fl->SP-1];
			i = GET_INT32(ip);
			ip += 4;
			if(captured)
				vector_elt(fl->Stack[bp], i) = v;
			else
				fl->Stack[bp+i] = v;
			NEXT_OP;

		OP(OP_SETC)
			s = *ip++;
			i = *ip++;
			v = fl->Stack[bp+nargs];
			while(s--)
				v = vector_elt(v, vector_size(v)-1);
			assert(isvector(v));
			assert(i < vector_size(v));
			vector_elt(v, i) = fl->Stack[fl->SP-1];
			NEXT_OP;

		OP(OP_LOADCL)
			s = GET_INT32(ip);
			ip += 4;
			i = GET_INT32(ip);
			ip += 4;
			v = fl->Stack[bp+nargs];
			while(s--)
				v = vector_elt(v, vector_size(v)-1);
			PUSH(vector_elt(v, i));
			NEXT_OP;

		OP(OP_SETCL)
			s = GET_INT32(ip);
			ip += 4;
			i = GET_INT32(ip);
			ip += 4;
			v = fl->Stack[bp+nargs];
			while(s--)
				v = vector_elt(v, vector_size(v)-1);
			assert(i < vector_size(v));
			vector_elt(v, i) = fl->Stack[fl->SP-1];
			NEXT_OP;

		OP(OP_VARGC)
			i = *ip++;
			if(0){
		OP(OP_LVARGC)
				i = GET_INT32(ip);
				ip += 4;
			}
			s = (fixnum_t)nargs - (fixnum_t)i;
			if(s > 0){
				v = list(&fl->Stack[bp+i], s, 0);
				fl->Stack[bp+i] = v;
				if(s > 1){
					fl->Stack[bp+i+1] = fl->Stack[bp+nargs+0];
					fl->Stack[bp+i+2] = fl->Stack[bp+nargs+1];
					fl->Stack[bp+i+3] = i+1;
					//fl->Stack[bp+i+4] = 0;
					fl->Stack[bp+i+5] = 0;
					fl->SP =  bp+i+6;
					fl->curr_frame = fl->SP;
				}
			}else if(s < 0){
				lerrorf(fl->ArgError, "too few arguments");
			}else{
				PUSH(0);
				fl->Stack[fl->SP-3] = i+1;
				fl->Stack[fl->SP-4] = fl->Stack[fl->SP-5];
				fl->Stack[fl->SP-5] = fl->Stack[fl->SP-6];
				fl->Stack[fl->SP-6] = fl->NIL;
				fl->curr_frame = fl->SP;
			}
			nargs = i+1;
			NEXT_OP;

		OP(OP_TRYCATCH)
			v = do_trycatch();
			POPN(1);
			fl->Stack[fl->SP-1] = v;
			NEXT_OP;

		OP(OP_OPTARGS)
			i = GET_INT32(ip);
			ip += 4;
			n = GET_INT32(ip);
			ip += 4;
			if(nargs < i)
				lerrorf(fl->ArgError, "too few arguments");
			if((int32_t)n > 0){
				if(nargs > n)
					lerrorf(fl->ArgError, "too many arguments");
			}else
				n = -n;
			if(n > nargs){
				n -= nargs;
				fl->SP += n;
				fl->Stack[fl->SP-1] = fl->Stack[fl->SP-n-1];
				fl->Stack[fl->SP-2] = fl->Stack[fl->SP-n-2];
				fl->Stack[fl->SP-3] = nargs+n;
				fl->Stack[fl->SP-4] = fl->Stack[fl->SP-n-4];
				fl->Stack[fl->SP-5] = fl->Stack[fl->SP-n-5];
				fl->curr_frame = fl->SP;
				for(i = 0; i < n; i++)
					fl->Stack[bp+nargs+i] = UNBOUND;
				nargs += n;
			}
			NEXT_OP;

		OP(OP_BRBOUND)
			i = GET_INT32(ip);
			ip += 4;
			v = captured ? vector_elt(fl->Stack[bp], i) : fl->Stack[bp+i];
			PUSH(v != UNBOUND ? fl->FL_T : fl->FL_F);
			NEXT_OP;

		OP(OP_KEYARGS)
			v = fn_vals(fl->Stack[bp-1]);
			v = vector_elt(v, 0);
			i = GET_INT32(ip);
			ip += 4;
			n = GET_INT32(ip);
			ip += 4;
			s = GET_INT32(ip);
			ip += 4;
			nargs = process_keys(v, i, n, labs(s)-(i+n), bp, nargs, s<0);
			NEXT_OP;
		}
		op = *ip++;
	}
}

#define SWAP_INT32(a)
#define SWAP_INT16(a)
#include "maxstack.inc"

#if BYTE_ORDER == BIG_ENDIAN
#undef SWAP_INT32
#undef SWAP_INT16
#if defined(__sparc__)
#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) (*(int32_t*)(a) = bswap_32(*(int32_t*)(a)))
#define SWAP_INT16(a) (*(int16_t*)(a) = bswap_16(*(int16_t*)(a)))
#endif
#define compute_maxstack compute_maxstack_swap
#include "maxstack.inc"
#undef compute_maxstack
#else
#endif

// top = top frame pointer to start at
static value_t
_stacktrace(uint32_t top)
{
	value_t lst = fl->NIL;

	fl_gc_handle(&lst);
	while(top > 0){
		uint32_t sz = fl->Stack[top-3]+1;
		uint32_t bp = top-5-sz;
		value_t v = alloc_vector(sz, 0);
		if(fl->Stack[top-1] /*captured*/){
			vector_elt(v, 0) = fl->Stack[bp];
			memmove(&vector_elt(v, 1),
				   &vector_elt(fl->Stack[bp+1], 0), (sz-1)*sizeof(value_t));
		}else{
			uint32_t i;
			for(i = 0; i < sz; i++){
				value_t si = fl->Stack[bp+i];
				// if there's an error evaluating argument defaults some slots
				// might be left set to UNBOUND (issue #22)
				vector_elt(v, i) = si == UNBOUND ? fl->FL_UNSPECIFIED : si;
			}
		}
		lst = fl_cons(v, lst);
		top = fl->Stack[top-4];
	}
	fl_free_gc_handles(1);
	return lst;
}

// builtins -------------------------------------------------------------------

BUILTIN("gc", gc)
{
	USED(args);
	argcount(nargs, 0);
	gc(0);
	return fl->FL_T;
}

BUILTIN("function", function)
{
	if(nargs == 1 && issymbol(args[0]))
		return fn_builtin_builtin(args, nargs);
	if(nargs < 2 || nargs > 4)
		argcount(nargs, 2);
	if(!fl_isstring(args[0]))
		type_error("string", args[0]);
	if(!isvector(args[1]))
		type_error("vector", args[1]);
	cvalue_t *arr = (cvalue_t*)ptr(args[0]);
	cv_pin(arr);
	char *data = cv_data(arr);
	uint32_t ms;
	if((uint8_t)data[4] >= N_OPCODES){
		// read syntax, shifted 48 for compact text representation
		size_t i, sz = cv_len(arr);
		for(i = 0; i < sz; i++)
			data[i] -= 48;
#if BYTE_ORDER == BIG_ENDIAN
		ms = compute_maxstack((uint8_t*)data, cv_len(arr));
	}else{
		ms = compute_maxstack_swap((uint8_t*)data, cv_len(arr));
	}
#else
	}
	ms = compute_maxstack((uint8_t*)data, cv_len(arr));
#endif
	PUT_INT32(data, ms);
	function_t *fn = alloc_words(4);
	value_t fv = tagptr(fn, TAG_FUNCTION);
	fn->bcode = args[0];
	fn->vals = args[1];
	fn->env = fl->NIL;
	fn->name = fl->LAMBDA;
	if(nargs > 2){
		if(issymbol(args[2])){
			fn->name = args[2];
			if(nargs > 3)
				fn->env = args[3];
		}else{
			fn->env = args[2];
			if(nargs > 3){
				if(!issymbol(args[3]))
					type_error("symbol", args[3]);
				fn->name = args[3];
			}
		}
		if(isgensym(fn->name))
			lerrorf(fl->ArgError, "name should not be a gensym");
	}
	return fv;
}

BUILTIN("function:code", function_code)
{
	argcount(nargs, 1);
	value_t v = args[0];
	if(!isclosure(v))
		type_error("function", v);
	return fn_bcode(v);
}

BUILTIN("function:vals", function_vals)
{
	argcount(nargs, 1);
	value_t v = args[0];
	if(!isclosure(v))
		type_error("function", v);
	return fn_vals(v);
}

BUILTIN("function:env", function_env)
{
	argcount(nargs, 1);
	value_t v = args[0];
	if(!isclosure(v))
		type_error("function", v);
	return fn_env(v);
}

BUILTIN("function:name", function_name)
{
	argcount(nargs, 1);
	value_t v = args[0];
	if(!isclosure(v))
		type_error("function", v);
	return fn_name(v);
}

BUILTIN("copy-list", copy_list)
{
	argcount(nargs, 1);
	return copy_list(args[0]);
}

BUILTIN("append", append)
{
	value_t first = fl->NIL, lst, lastcons = fl->NIL;
	int i;
	if(nargs == 0)
		return fl->NIL;
	fl_gc_handle(&first);
	fl_gc_handle(&lastcons);
	for(i = 0; i < nargs; i++){
		lst = args[i];
		if(iscons(lst)){
			lst = copy_list(lst);
			if(first == fl->NIL)
				first = lst;
			else
				cdr_(lastcons) = lst;
			lastcons = tagptr((((cons_t*)fl->curheap)-1), TAG_CONS);
		}else if(lst != fl->NIL){
			type_error("cons", lst);
		}
	}
	fl_free_gc_handles(2);
	return first;
}

BUILTIN("list*", liststar)
{
	if(nargs == 1)
		return args[0];
	if(nargs == 0)
		argcount(nargs, 1);
	return list(args, nargs, 1);
}

BUILTIN("stacktrace", stacktrace)
{
	USED(args);
	argcount(nargs, 0);
	return _stacktrace(fl->throwing_frame ? fl->throwing_frame : fl->curr_frame);
}

BUILTIN("map", map)
{
	if(nargs < 2)
		lerrorf(fl->ArgError, "too few arguments");
	if(!iscons(args[1]))
		return fl->NIL;
	value_t first, last, v;
	int64_t argSP = args-fl->Stack;
	assert(argSP >= 0 && argSP < fl->N_STACK);
	if(nargs == 2){
		if(fl->SP+3 > fl->N_STACK)
			grow_stack();
		PUSH(fl->Stack[argSP]);
		PUSH(car_(fl->Stack[argSP+1]));
		v = _applyn(1);
		PUSH(v);
		v = mk_cons();
		car_(v) = POP(); cdr_(v) = fl->NIL;
		last = first = v;
		fl->Stack[argSP+1] = cdr_(fl->Stack[argSP+1]);
		fl_gc_handle(&first);
		fl_gc_handle(&last);
		while(iscons(fl->Stack[argSP+1])){
			fl->Stack[fl->SP-2] = fl->Stack[argSP];
			fl->Stack[fl->SP-1] = car_(fl->Stack[argSP+1]);
			v = _applyn(1);
			PUSH(v);
			v = mk_cons();
			car_(v) = POP(); cdr_(v) = fl->NIL;
			cdr_(last) = v;
			last = v;
			fl->Stack[argSP+1] = cdr_(fl->Stack[argSP+1]);
		}
		POPN(2);
		fl_free_gc_handles(2);
	}else{
		int i;
		while(fl->SP+nargs+1 > fl->N_STACK)
			grow_stack();
		PUSH(fl->Stack[argSP]);
		for(i = 1; i < nargs; i++){
			PUSH(car(fl->Stack[argSP+i]));
			fl->Stack[argSP+i] = cdr_(fl->Stack[argSP+i]);
		}
		v = _applyn(nargs-1);
		POPN(nargs);
		PUSH(v);
		v = mk_cons();
		car_(v) = POP(); cdr_(v) = fl->NIL;
		last = first = v;
		fl_gc_handle(&first);
		fl_gc_handle(&last);
		while(iscons(fl->Stack[argSP+1])){
			PUSH(fl->Stack[argSP]);
			for(i = 1; i < nargs; i++){
				PUSH(car(fl->Stack[argSP+i]));
				fl->Stack[argSP+i] = cdr_(fl->Stack[argSP+i]);
			}
			v = _applyn(nargs-1);
			POPN(nargs);
			PUSH(v);
			v = mk_cons();
			car_(v) = POP(); cdr_(v) = fl->NIL;
			cdr_(last) = v;
			last = v;
		}
		fl_free_gc_handles(2);
	}
	return first;
}

BUILTIN("sleep", fl_sleep)
{
	if(nargs > 1)
		argcount(nargs, 1);
	double s = nargs > 0 ? todouble(args[0]) : 0;
	sleep_ms(s * 1000.0);
	return fl->FL_T;
}

static const builtinspec_t builtin_fns[] = {
#define BUILTIN_FN(l, c){l, fn_builtin_##c},
#include "builtin_fns.h"
#undef BUILTIN_FN
};

// initialization -------------------------------------------------------------

void
fl_init(size_t initial_heapsize)
{
	int i;

	fl = LLT_ALLOC(sizeof(*fl));
	memset(fl, 0, sizeof(*fl));
	fl->SCR_WIDTH = 80;

	fl->heapsize = initial_heapsize;

	fl->fromspace = LLT_ALLOC(fl->heapsize);
	fl->tospace   = LLT_ALLOC(fl->heapsize);
	fl->curheap = fl->fromspace;
	fl->lim = fl->curheap+fl->heapsize-sizeof(cons_t);
	fl->consflags = bitvector_new(fl->heapsize/sizeof(cons_t), 1);
	htable_new(&fl->printconses, 32);
	comparehash_init();
	fl->N_STACK = 262144;
	fl->Stack = LLT_ALLOC(fl->N_STACK*sizeof(value_t));

	fl->FL_NIL = fl->NIL = builtin(OP_THE_EMPTY_LIST);
	fl->FL_T = builtin(OP_BOOL_CONST_T);
	fl->FL_F = builtin(OP_BOOL_CONST_F);
	fl->FL_EOF = builtin(OP_EOF_OBJECT);
	fl->LAMBDA = symbol("λ");
	fl->FUNCTION = symbol("function");
	fl->QUOTE = symbol("quote");
	fl->TRYCATCH = symbol("trycatch");
	fl->BACKQUOTE = symbol("quasiquote");
	fl->COMMA = symbol("unquote");
	fl->COMMAAT = symbol("unquote-splicing");
	fl->COMMADOT = symbol("unquote-nsplicing");
	fl->IOError = symbol("io-error");
	fl->ParseError = symbol("parse-error");
	fl->TypeError = symbol("type-error");
	fl->ArgError = symbol("arg-error");
	fl->UnboundError = symbol("unbound-error");
	fl->KeyError = symbol("key-error");
	fl->MemoryError = symbol("memory-error");
	fl->BoundsError = symbol("bounds-error");
	fl->DivideError = symbol("divide-error");
	fl->EnumerationError = symbol("enumeration-error");
	fl->Error = symbol("error");
	fl->pairsym = symbol("pair");
	fl->symbolsym = symbol("symbol");
	fl->fixnumsym = symbol("fixnum");
	fl->vectorsym = symbol("vector");
	fl->builtinsym = symbol("builtin");
	fl->booleansym = symbol("boolean");
	fl->nullsym = symbol("null");
	fl->definesym = symbol("define");
	fl->defmacrosym = symbol("define-macro");
	fl->forsym = symbol("for");
	fl->setqsym = symbol("set!");
	fl->evalsym = symbol("eval");
	fl->vu8sym = symbol("vu8");
	fl->fnsym = symbol("fn");
	fl->nulsym = symbol("nul");
	fl->alarmsym = symbol("alarm");
	fl->backspacesym = symbol("backspace");
	fl->tabsym = symbol("tab");
	fl->linefeedsym = symbol("linefeed");
	fl->vtabsym = symbol("vtab");
	fl->pagesym = symbol("page");
	fl->returnsym = symbol("return");
	fl->escsym = symbol("esc");
	fl->spacesym = symbol("space");
	fl->deletesym = symbol("delete");
	fl->newlinesym = symbol("newline");
	fl->tsym = symbol("t");
	fl->Tsym = symbol("T");
	fl->fsym = symbol("f");
	fl->Fsym = symbol("F");
	fl->builtins_table_sym = symbol("*builtins*");
	set(fl->printprettysym = symbol("*print-pretty*"), fl->FL_T);
	set(fl->printreadablysym = symbol("*print-readably*"), fl->FL_T);
	set(fl->printwidthsym = symbol("*print-width*"), fixnum(fl->SCR_WIDTH));
	set(fl->printlengthsym = symbol("*print-length*"), fl->FL_F);
	set(fl->printlevelsym = symbol("*print-level*"), fl->FL_F);
	fl->lasterror = fl->NIL;
	for(i = 0; i < nelem(builtins); i++){
		if(builtins[i].name)
			setc(symbol(builtins[i].name), builtin(i));
	}
	setc(symbol("eq"), builtin(OP_EQ));
	setc(symbol("procedure?"), builtin(OP_FUNCTIONP));
	setc(symbol("top-level-bound?"), builtin(OP_BOUNDP));

#if defined(__linux__)
	set(symbol("*os-name*"), symbol("linux"));
#elif defined(__OpenBSD__)
	set(symbol("*os-name*"), symbol("openbsd"));
#elif defined(__FreeBSD__)
	set(symbol("*os-name*"), symbol("freebsd"));
#elif defined(__NetBSD__)
	set(symbol("*os-name*"), symbol("netbsd"));
#elif defined(__DragonFly__)
	set(symbol("*os-name*"), symbol("dragonflybsd"));
#elif defined(__plan9__)
	set(symbol("*os-name*"), symbol("plan9"));
#else
	set(symbol("*os-name*"), symbol("unknown"));
#endif

	fl->the_empty_vector = tagptr(alloc_words(1), TAG_VECTOR);
	vector_setsize(fl->the_empty_vector, 0);

	cvalues_init();

	fl->memory_exception_value = fl_list2(fl->MemoryError, cvalue_static_cstring("out of memory"));
	const builtinspec_t *b;
	for(i = 0, b = builtin_fns; i < nelem(builtin_fns); i++, b++)
		setc(symbol(b->name), cbuiltin(b->name, b->fptr));

	table_init();
	iostream_init();
	fsixel_init();
}

// top level ------------------------------------------------------------------

value_t
fl_toplevel_eval(value_t expr)
{
	return fl_applyn(1, symbol_value(fl->evalsym), expr);
}

int
fl_load_system_image(value_t sys_image_iostream)
{
	value_t e;
	int saveSP;
	symbol_t *sym;

	PUSH(sys_image_iostream);
	saveSP = fl->SP;
	FL_TRY{
		while(1){
			e = fl_read_sexpr(fl->Stack[fl->SP-1]);
			if(ios_eof(value2c(ios_t*, fl->Stack[fl->SP-1])))
				break;
			if(isfunction(e)){
				// stage 0 format: series of thunks
				PUSH(e);
				(void)_applyn(0);
				fl->SP = saveSP;
			}else{
				// stage 1 format: list alternating symbol/value
				while(iscons(e)){
					sym = tosymbol(car_(e));
					e = cdr_(e);
					(void)tocons(e);
					sym->binding = car_(e);
					e = cdr_(e);
				}
				break;
			}
		}
	}
	FL_CATCH_NO_INC{
		ios_puts("fatal error during bootstrap:\n", ios_stderr);
		fl_print(ios_stderr, fl->lasterror);
		ios_putc('\n', ios_stderr);
		return 1;
	}
	ios_close(value2c(ios_t*, fl->Stack[fl->SP-1]));
	POPN(1);
	return 0;
}