ref: 6a51a03b801b21f42e2eb3dfa02c99e96c86b10c
dir: /flisp.c/
/*
femtoLisp
by Jeff Bezanson (C) 2009
Distributed under the BSD License
*/
#include "flisp.h"
#include "operators.h"
#include "cvalues.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;
#ifdef NDEBUG
__thread
#endif
Fl *fl;
bool
isbuiltin(value_t x)
{
uint32_t i;
return tag(x) == TAG_FUNCTION && (i = uintval(x)) < 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(ngchandles); 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_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(ngchandles);
}
void
fl_restorestate(fl_exception_context_t *_ctx)
{
FL(lasterror) = FL_nil;
FL(throwing_frame) = 0;
FL(sp) = _ctx->sp;
FL(curr_frame) = _ctx->frame;
}
_Noreturn void
fl_raise(value_t e)
{
ios_flush(ios_stdout);
ios_flush(ios_stderr);
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(ngchandles) = 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, const 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(const char *expected, value_t got)
{
fl_raise(fl_listn(3, FL(TypeError), symbol(expected, false), 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(__likely(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 ---------------------------------------------------------------
bool
fl_is_keyword_name(const char *str, size_t len)
{
return (str[0] == ':' || str[len-1] == ':') && str[1] != '\0';
}
static symbol_t *
mk_symbol(const char *str, int len, bool copy)
{
symbol_t *sym;
sym = MEM_ALLOC(sizeof(*sym) + (copy ? len+1 : 0));
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->flags = 0;
}
sym->type = nil;
sym->hash = memhash32(str, len)^0xAAAAAAAA;
if(copy){
sym->name = (const char*)(sym+1);
memcpy((char*)sym->name, str, len+1);
}else{
sym->name = str;
}
sym->size = 0;
return sym;
}
value_t
symbol(const char *str, bool copy)
{
int len = strlen(str);
symbol_t *v;
const char *k;
if(!Tgetkv(FL(symtab), str, len, &k, (void**)&v)){
v = mk_symbol(str, len, copy);
FL(symtab) = Tsetl(FL(symtab), v->name, len, v);
}
return tagptr(v, TAG_SYM);
}
BUILTIN("gensym", gensym)
{
argcount(nargs, 0);
USED(args);
gensym_t *gs = alloc_words(sizeof(gensym_t)/sizeof(value_t));
gs->id = FL(gensym_ctr)++;
gs->binding = UNBOUND;
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_t : 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];
}
const 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(uint32_t n)
{
value_t *first;
assert(n > 0);
n = ALIGNED(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_void;
}
return v;
}
// collector ------------------------------------------------------------------
void
fl_gc_handle(value_t *pv)
{
if(__unlikely(FL(ngchandles) >= N_GC_HANDLES))
lerrorf(FL(MemoryError), "out of gc handles");
FL(gchandles)[FL(ngchandles)++] = pv;
}
void
fl_free_gc_handles(uint32_t n)
{
assert(FL(ngchandles) >= n);
FL(ngchandles) -= n;
}
value_t
relocate(value_t v)
{
value_t a, d, nc, first, *pcdr;
if(isfixnum(v))
return v;
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;
}
car_(v) = TAG_FWD;
d = cdr_(v);
*pcdr = nc = tagptr((cons_t*)FL(curheap), TAG_CONS);
FL(curheap) += sizeof(cons_t);
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(!ismanaged(v))
return v;
if(isforwarded(v))
return forwardloc(v);
if(t == TAG_CVALUE)
return cvalue_relocate(v);
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_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_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_SYM){
gensym_t *gs = ptr(v);
gensym_t *ng = alloc_words(sizeof(gensym_t)/sizeof(value_t));
ng->id = gs->id;
ng->binding = gs->binding;
nc = tagptr(ng, TAG_SYM);
forward(v, nc);
if(__likely(ng->binding != UNBOUND))
ng->binding = relocate(ng->binding);
return nc;
}
return v;
}
static void
trace_globals(void)
{
const char *k = nil;
symbol_t *v;
while(Tnext(FL(symtab), &k, (void**)&v)){
if(v->binding != UNBOUND)
v->binding = relocate(v->binding);
}
}
void
gc(int mustgrow)
{
void *temp;
uint32_t i, f, top;
fl_readstate_t *rs;
FL(gccalls)++;
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) - 3;
f = FL(stack)[FL(throwing_frame)-3];
}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 - 3;
f = FL(stack)[f-3];
}
for(i = 0; i < FL(ngchandles); i++)
*FL(gchandles)[i] = relocate(*FL(gchandles)[i]);
trace_globals();
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));
FL(the_empty_string) = relocate(FL(the_empty_string));
sweep_finalizers();
#if defined(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 = MEM_REALLOC(FL(tospace), FL(heapsize)*2);
if(__unlikely(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(__unlikely(temp == nil))
fl_raise(FL(memory_exception_value));
FL(consflags) = (uint32_t*)temp;
}
FL(grew) = !FL(grew);
}
if(__unlikely((value_t*)FL(curheap) > (value_t*)FL(lim)-2)){
// all data was live; gc again and grow heap.
// but also always leave at least 4 words available, so a closure
// can be allocated without an extra check.
gc(0);
}
}
static void
grow_stack(void)
{
size_t newsz = FL(nstack) * 2;
value_t *ns = MEM_REALLOC(FL(stack), newsz*sizeof(value_t));
if(__unlikely(ns == nil))
lerrorf(FL(MemoryError), "stack overflow");
FL(stack) = ns;
FL(nstack) = 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(__likely(isbuiltin(f))){
value_t tab = symbol_value(FL(builtins_table_sym));
if(__unlikely(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(nstack))
grow_stack();
PUSH(car_(v));
v = cdr_(v);
}
if(v != FL_nil)
lerrorf(FL(ArgError), "apply: last argument: not a list");
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(nstack))
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(nstack))
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;
}
bool
fl_isnumber(value_t v)
{
if(isfixnum(v))
return true;
if(iscprim(v)){
cprim_t *c = ptr(v);
return c->type != FL(runetype) && valid_numtype(c->type->numtype);
}
if(iscvalue(v)){
cvalue_t *c = ptr(v);
return valid_numtype(cp_numtype(c));
}
return false;
}
// 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 s3 = FL(stack)[FL(sp)-3];
value_t s4 = FL(stack)[FL(sp)-4];
if(__unlikely(nargs < nreq))
lerrorf(FL(ArgError), "too few arguments");
if(__unlikely(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(__unlikely(i >= nargs))
lerrorf(FL(ArgError), "keyword %s requires an argument", symbol_name(v));
value_t hv = fixnum(((symbol_t*)ptr(v))->hash);
fixnum_t lx = numval(hv);
uintptr_t x = 2*((lx < 0 ? -lx : lx) % n);
if(__likely(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(__unlikely(!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(nstack)-4);
PUSH(s4);
PUSH(s3);
PUSH(nargs);
PUSH(s1);
FL(curr_frame) = FL(sp);
return nargs;
}
#define GET_INT32(a) \
((int32_t) \
((((uint32_t)a[0])<<0) | \
(((uint32_t)a[1])<<8) | \
(((uint32_t)a[2])<<16) | \
(((uint32_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) \
do{ \
((uint8_t*)(a))[0] = ((uint32_t)(i)>>0)&0xff; \
((uint8_t*)(a))[1] = ((uint32_t)(i)>>8)&0xff; \
((uint8_t*)(a))[2] = ((uint32_t)(i)>>16)&0xff; \
((uint8_t*)(a))[3] = ((uint32_t)(i)>>24)&0xff; \
}while(0)
/*
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);
uint32_t n, bp;
const uint8_t *ip;
fixnum_t s, hi;
bool tail;
// temporary variables (not necessary to preserve across calls)
size_t isz;
uint32_t i, ipd;
symbol_t *sym;
cons_t *c;
value_t *pv;
value_t func, v, e;
int x;
n = 0;
v = 0;
USED(n);
USED(v);
apply_cl_top:
bp = FL(sp)-nargs;
func = FL(stack)[bp-1];
ip = cvalue_data(fn_bcode(func));
assert(!ismanaged((uintptr_t)ip));
i = FL(sp)+GET_INT32(ip);
while(i >= FL(nstack))
grow_stack();
ip += 4;
PUSH(fn_env(func));
PUSH(FL(curr_frame));
PUSH(nargs);
ipd = FL(sp);
FL(sp)++; // ip
FL(curr_frame) = FL(sp);
#if defined(COMPUTED_GOTO)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#define OP(x) op_##x:
#define NEXT_OP goto *ops[*ip++]
#define GOTO_OP_OFFSET(op) [op] = &&op_##op
static const void *ops[] = {
GOTO_OP_OFFSET(OP_LOADA0),
GOTO_OP_OFFSET(OP_LOADA1),
GOTO_OP_OFFSET(OP_LOADV),
GOTO_OP_OFFSET(OP_BRF),
GOTO_OP_OFFSET(OP_POP),
GOTO_OP_OFFSET(OP_CALL),
GOTO_OP_OFFSET(OP_TCALL),
GOTO_OP_OFFSET(OP_LOADG),
GOTO_OP_OFFSET(OP_LOADA),
GOTO_OP_OFFSET(OP_LOADC),
GOTO_OP_OFFSET(OP_RET),
GOTO_OP_OFFSET(OP_DUP),
GOTO_OP_OFFSET(OP_CAR),
GOTO_OP_OFFSET(OP_CDR),
GOTO_OP_OFFSET(OP_CLOSURE),
GOTO_OP_OFFSET(OP_SETA),
GOTO_OP_OFFSET(OP_JMP),
GOTO_OP_OFFSET(OP_LOADC0),
GOTO_OP_OFFSET(OP_CONSP),
GOTO_OP_OFFSET(OP_BRNE),
GOTO_OP_OFFSET(OP_LOADT),
GOTO_OP_OFFSET(OP_LOAD0),
GOTO_OP_OFFSET(OP_LOADC1),
GOTO_OP_OFFSET(OP_AREF2),
GOTO_OP_OFFSET(OP_AREF),
GOTO_OP_OFFSET(OP_ATOMP),
GOTO_OP_OFFSET(OP_BRT),
GOTO_OP_OFFSET(OP_BRNN),
GOTO_OP_OFFSET(OP_LOAD1),
GOTO_OP_OFFSET(OP_LT),
GOTO_OP_OFFSET(OP_ADD2),
GOTO_OP_OFFSET(OP_SETCDR),
GOTO_OP_OFFSET(OP_LOADF),
GOTO_OP_OFFSET(OP_CONS),
GOTO_OP_OFFSET(OP_EQ),
GOTO_OP_OFFSET(OP_SYMBOLP),
GOTO_OP_OFFSET(OP_NOT),
GOTO_OP_OFFSET(OP_CADR),
GOTO_OP_OFFSET(OP_NEG),
GOTO_OP_OFFSET(OP_NULLP),
GOTO_OP_OFFSET(OP_BOOLEANP),
GOTO_OP_OFFSET(OP_NUMBERP),
GOTO_OP_OFFSET(OP_FIXNUMP),
GOTO_OP_OFFSET(OP_BOUNDP),
GOTO_OP_OFFSET(OP_BUILTINP),
GOTO_OP_OFFSET(OP_FUNCTIONP),
GOTO_OP_OFFSET(OP_VECTORP),
GOTO_OP_OFFSET(OP_SETCAR),
GOTO_OP_OFFSET(OP_JMPL),
GOTO_OP_OFFSET(OP_BRFL),
GOTO_OP_OFFSET(OP_BRTL),
GOTO_OP_OFFSET(OP_EQV),
GOTO_OP_OFFSET(OP_EQUAL),
GOTO_OP_OFFSET(OP_LIST),
GOTO_OP_OFFSET(OP_APPLY),
GOTO_OP_OFFSET(OP_ADD),
GOTO_OP_OFFSET(OP_SUB),
GOTO_OP_OFFSET(OP_MUL),
GOTO_OP_OFFSET(OP_DIV),
GOTO_OP_OFFSET(OP_IDIV),
GOTO_OP_OFFSET(OP_NUMEQ),
GOTO_OP_OFFSET(OP_COMPARE),
GOTO_OP_OFFSET(OP_ARGC),
GOTO_OP_OFFSET(OP_VECTOR),
GOTO_OP_OFFSET(OP_ASET),
GOTO_OP_OFFSET(OP_LOADNIL),
GOTO_OP_OFFSET(OP_LOADI8),
GOTO_OP_OFFSET(OP_LOADVL),
GOTO_OP_OFFSET(OP_LOADGL),
GOTO_OP_OFFSET(OP_LOADAL),
GOTO_OP_OFFSET(OP_LOADCL),
GOTO_OP_OFFSET(OP_SETG),
GOTO_OP_OFFSET(OP_SETGL),
GOTO_OP_OFFSET(OP_SETAL),
GOTO_OP_OFFSET(OP_VARGC),
GOTO_OP_OFFSET(OP_TRYCATCH),
GOTO_OP_OFFSET(OP_FOR),
GOTO_OP_OFFSET(OP_TAPPLY),
GOTO_OP_OFFSET(OP_SUB2),
GOTO_OP_OFFSET(OP_LARGC),
GOTO_OP_OFFSET(OP_LVARGC),
GOTO_OP_OFFSET(OP_CALLL),
GOTO_OP_OFFSET(OP_TCALLL),
GOTO_OP_OFFSET(OP_BRNEL),
GOTO_OP_OFFSET(OP_BRNNL),
GOTO_OP_OFFSET(OP_BRN),
GOTO_OP_OFFSET(OP_BRNL),
GOTO_OP_OFFSET(OP_OPTARGS),
GOTO_OP_OFFSET(OP_BRBOUND),
GOTO_OP_OFFSET(OP_KEYARGS),
GOTO_OP_OFFSET(OP_BOX),
GOTO_OP_OFFSET(OP_BOXL),
GOTO_OP_OFFSET(OP_SHIFT),
GOTO_OP_OFFSET(OP_LOADVOID),
};
NEXT_OP;
#else
#define OP(x) case x:
#define NEXT_OP break
uint8_t op = *ip++;
while(1){
switch(op){
#endif
OP(OP_LOADA0)
PUSH(FL(stack)[bp]);
NEXT_OP;
OP(OP_LOADA1)
PUSH(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_f ? 2 : GET_INT16(ip);
NEXT_OP;
OP(OP_POP)
POPN(1);
NEXT_OP;
OP(OP_TCALLL)
tail = true;
if(0){
OP(OP_CALLL)
tail = false;
}
n = GET_INT32(ip);
ip += 4;
if(0){
OP(OP_TCALL)
tail = true;
if(0){
OP(OP_CALL)
tail = false;
}
n = *ip++; // nargs
}
do_call:
FL(stack)[ipd] = (uintptr_t)ip;
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)-3];
for(s = -1; s < (fixnum_t)n; s++)
FL(stack)[bp+s] = FL(stack)[FL(sp)-n+s];
FL(sp) = bp+n;
}
nargs = n;
goto apply_cl_top;
}else{
i = uintval(func);
if(isbuiltin(func)){
s = builtins[i].nargs;
if(s >= 0)
argcount(n, (unsigned)s);
else if(s != ANYARGS && (signed)n < -s)
argcount(n, (unsigned)-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;
case OP_AREF: goto apply_aref;
case OP_ASET: goto apply_aset;
default:
#if defined(COMPUTED_GOTO)
goto *ops[i];
#else
op = i;
continue;
#endif
}
}
}
}else if(__likely(iscbuiltin(func))){
s = FL(sp) - n;
v = (((builtin_t*)ptr(func))[3])(&FL(stack)[s], n);
FL(sp) = s;
FL(stack)[s-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(__unlikely(sym->binding == UNBOUND)){
FL(stack)[ipd] = (uintptr_t)ip;
unbound_error(v);
}
PUSH(sym->binding);
NEXT_OP;
OP(OP_LOADA)
i = *ip++;
v = FL(stack)[bp+i];
PUSH(v);
NEXT_OP;
OP(OP_LOADC)
i = *ip++;
v = FL(stack)[bp+nargs];
assert(isvector(v));
assert(i < vector_size(v));
PUSH(vector_elt(v, i));
NEXT_OP;
OP(OP_BOX)
i = *ip++;
v = mk_cons();
car_(v) = FL(stack)[bp+i];
cdr_(v) = FL_nil;
FL(stack)[bp+i] = v;
NEXT_OP;
OP(OP_BOXL)
i = GET_INT32(ip); ip += 4;
v = mk_cons();
car_(v) = FL(stack)[bp+i];
cdr_(v) = FL_nil;
FL(stack)[bp+i] = v;
NEXT_OP;
OP(OP_SHIFT)
i = *ip++;
FL(stack)[FL(sp)-1-i] = FL(stack)[FL(sp)-1];
FL(sp) -= i;
NEXT_OP;
OP(OP_RET)
v = POP();
FL(sp) = FL(curr_frame);
FL(curr_frame) = FL(stack)[FL(sp)-3];
if(FL(curr_frame) == top_frame)
return v;
FL(sp) -= 4+nargs;
ipd = FL(curr_frame)-1;
ip = (uint8_t*)FL(stack)[ipd];
nargs = FL(stack)[FL(curr_frame)-2];
bp = FL(curr_frame) - 4 - nargs;
FL(stack)[FL(sp)-1] = v;
NEXT_OP;
OP(OP_DUP)
FL(stack)[FL(sp)] = FL(stack)[FL(sp)-1];
FL(sp)++;
NEXT_OP;
OP(OP_CAR)
v = FL(stack)[FL(sp)-1];
if(__likely(iscons(v)))
v = car_(v);
else if(__unlikely(v != FL_nil)){
FL(stack)[ipd] = (uintptr_t)ip;
type_error("cons", v);
}
FL(stack)[FL(sp)-1] = v;
NEXT_OP;
OP(OP_CDR)
v = FL(stack)[FL(sp)-1];
if(__likely(iscons(v)))
v = cdr_(v);
else if(__unlikely(v != FL_nil)){
FL(stack)[ipd] = (uintptr_t)ip;
type_error("cons", v);
}
FL(stack)[FL(sp)-1] = v;
NEXT_OP;
OP(OP_CLOSURE)
n = *ip++;
assert(n > 0);
pv = alloc_words(n + 1);
v = tagptr(pv, TAG_VECTOR);
i = 0;
pv[i++] = fixnum(n);
do{
pv[i] = FL(stack)[FL(sp)-n + i-1];
i++;
}while(i <= n);
POPN(n);
PUSH(v);
if(__unlikely((value_t*)FL(curheap) > (value_t*)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)
v = FL(stack)[FL(sp)-1];
i = *ip++;
FL(stack)[bp+i] = v;
NEXT_OP;
OP(OP_JMP)
ip += GET_INT16(ip);
NEXT_OP;
OP(OP_LOADC0)
PUSH(vector_elt(FL(stack)[bp+nargs], 0));
NEXT_OP;
OP(OP_CONSP)
FL(stack)[FL(sp)-1] = iscons(FL(stack)[FL(sp)-1]) ? FL_t : 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_t);
NEXT_OP;
OP(OP_LOADVOID)
PUSH(FL_void);
NEXT_OP;
OP(OP_LOAD0)
PUSH(fixnum(0));
NEXT_OP;
OP(OP_LOADC1)
PUSH(vector_elt(FL(stack)[bp+nargs], 1));
NEXT_OP;
OP(OP_AREF2)
n = 2;
if(0){
OP(OP_AREF)
FL(stack)[ipd] = (uintptr_t)ip;
n = 3 + *ip++;
}
apply_aref:
v = FL(stack)[FL(sp)-n];
for(i = n-1; i > 0; i--){
if(isarray(v)){
FL(stack)[FL(sp)-i-1] = v;
v = cvalue_array_aref(&FL(stack)[FL(sp)-i-1]);
continue;
}
e = FL(stack)[FL(sp)-i];
isz = tosize(e);
if(isvector(v)){
if(__unlikely(isz >= vector_size(v)))
bounds_error(v, e);
v = vector_elt(v, isz);
continue;
}
if(!iscons(v) && v != FL_nil)
type_error("sequence", v);
for(value_t v0 = v;; isz--){
if(isz == 0){
v = car_(v);
break;
}
v = cdr_(v);
if(__unlikely(!iscons(v)))
bounds_error(v0, e);
}
}
POPN(n);
PUSH(v);
NEXT_OP;
OP(OP_ATOMP)
FL(stack)[FL(sp)-1] = iscons(FL(stack)[FL(sp)-1]) ? FL_f : FL_t;
NEXT_OP;
OP(OP_BRT)
ip += POP() != 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)
{
value_t a = FL(stack)[FL(sp)-2], b = FL(stack)[FL(sp)-1];
POPN(1);
if(bothfixnums(a, b)){
FL(stack)[FL(sp)-1] = (fixnum_t)a < (fixnum_t)b ? FL_t : FL_f;
}else{
x = numeric_compare(a, b, false, false, false);
if(x > 1)
x = numval(fl_compare(a, b));
FL(stack)[FL(sp)-1] = x < 0 ? FL_t : FL_f;
}
}
NEXT_OP;
OP(OP_ADD2)
do_add2:
FL(stack)[ipd] = (uintptr_t)ip;
if(0){
OP(OP_SUB2)
do_sub2:
FL(stack)[ipd] = (uintptr_t)ip;
FL(stack)[FL(sp)-1] = fl_neg(FL(stack)[FL(sp)-1]);
}
{
fixnum_t a, b, c;
a = FL(stack)[FL(sp)-2];
b = FL(stack)[FL(sp)-1];
if(bothfixnums(a, b) && !sadd_overflow(numval(a), numval(b), &c) && fits_fixnum(c)){
v = fixnum(c);
}else{
v = fl_add_any(&FL(stack)[FL(sp)-2], 2);
}
}
POPN(1);
FL(stack)[FL(sp)-1] = v;
NEXT_OP;
OP(OP_SETCDR)
v = FL(stack)[FL(sp)-2];
if(__unlikely(!iscons(v))){
FL(stack)[ipd] = (uintptr_t)ip;
type_error("cons", v);
}
cdr_(v) = FL(stack)[FL(sp)-1];
POPN(1);
NEXT_OP;
OP(OP_LOADF)
PUSH(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_t : FL_f;
POPN(1);
NEXT_OP;
OP(OP_SYMBOLP)
FL(stack)[FL(sp)-1] = issymbol(FL(stack)[FL(sp)-1]) ? FL_t : FL_f;
NEXT_OP;
OP(OP_NOT)
FL(stack)[FL(sp)-1] = FL(stack)[FL(sp)-1] == FL_f ? FL_t : FL_f;
NEXT_OP;
OP(OP_CADR)
v = FL(stack)[FL(sp)-1];
if(__likely(iscons(v))){
v = cdr_(v);
if(__likely(iscons(v)))
v = car_(v);
else
goto cadr_nil;
}else{
cadr_nil:
if(__unlikely(v != FL_nil)){
FL(stack)[ipd] = (uintptr_t)ip;
type_error("cons", v);
}
}
FL(stack)[FL(sp)-1] = v;
NEXT_OP;
OP(OP_NEG)
do_neg:
FL(stack)[ipd] = (uintptr_t)ip;
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_t : FL_f;
NEXT_OP;
OP(OP_BOOLEANP)
v = FL(stack)[FL(sp)-1];
FL(stack)[FL(sp)-1] = (v == FL_t || v == FL_f) ? FL_t : FL_f;
NEXT_OP;
OP(OP_NUMBERP)
v = FL(stack)[FL(sp)-1];
FL(stack)[FL(sp)-1] = fl_isnumber(v) ? FL_t : FL_f;
NEXT_OP;
OP(OP_FIXNUMP)
FL(stack)[FL(sp)-1] = isfixnum(FL(stack)[FL(sp)-1]) ? FL_t : FL_f;
NEXT_OP;
OP(OP_BOUNDP)
FL(stack)[ipd] = (uintptr_t)ip;
sym = tosymbol(FL(stack)[FL(sp)-1]);
FL(stack)[FL(sp)-1] = sym->binding == UNBOUND ? FL_f : FL_t;
NEXT_OP;
OP(OP_BUILTINP)
v = FL(stack)[FL(sp)-1];
FL(stack)[FL(sp)-1] = (isbuiltin(v) || iscbuiltin(v)) ? FL_t : 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_t : FL_f;
NEXT_OP;
OP(OP_VECTORP)
FL(stack)[FL(sp)-1] = isvector(FL(stack)[FL(sp)-1]) ? FL_t : FL_f;
NEXT_OP;
OP(OP_JMPL)
ip += GET_INT32(ip);
NEXT_OP;
OP(OP_BRFL)
ip += POP() == FL_f ? GET_INT32(ip) : 4;
NEXT_OP;
OP(OP_BRTL)
ip += POP() != 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_t;
else if(!leafp(FL(stack)[FL(sp)-2]) || !leafp(FL(stack)[FL(sp)-1]))
v = FL_f;
else
v = compare_(FL(stack)[FL(sp)-2], FL(stack)[FL(sp)-1], 1) == 0 ? FL_t : 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_t;
else
v = compare_(FL(stack)[FL(sp)-2], FL(stack)[FL(sp)-1], 1) == 0 ? FL_t : FL_f;
FL(stack)[FL(sp)-2] = v;
POPN(1);
NEXT_OP;
OP(OP_SETCAR)
v = FL(stack)[FL(sp)-2];
if(__unlikely(!iscons(v))){
FL(stack)[ipd] = (uintptr_t)ip;
type_error("cons", v);
}
car_(v) = 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 = true;
if(0){
OP(OP_APPLY)
tail = false;
}
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(nstack))
grow_stack();
PUSH(car_(v));
v = cdr_(v);
}
if(v != FL_nil){
FL(stack)[ipd] = (uintptr_t)ip;
lerrorf(FL(ArgError), "apply: last argument: not a list");
}
n = FL(sp)-n;
goto do_call;
OP(OP_ADD)
n = *ip++;
if(n == 2)
goto do_add2;
apply_add:
FL(stack)[ipd] = (uintptr_t)ip;
v = fl_add_any(&FL(stack)[FL(sp)-n], n);
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;
FL(stack)[ipd] = (uintptr_t)ip;
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));
FL(stack)[i] = POP();
v = fl_add_any(&FL(stack)[i], 2);
POPN(n);
PUSH(v);
NEXT_OP;
OP(OP_MUL)
n = *ip++;
apply_mul:
FL(stack)[ipd] = (uintptr_t)ip;
v = fl_mul_any(&FL(stack)[FL(sp)-n], n);
POPN(n);
PUSH(v);
NEXT_OP;
OP(OP_DIV)
n = *ip++;
apply_div:
FL(stack)[ipd] = (uintptr_t)ip;
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);
FL(stack)[i] = POP();
}
v = fl_div2(FL(stack)[i], FL(stack)[i+1]);
POPN(n);
PUSH(v);
}
NEXT_OP;
OP(OP_IDIV)
FL(stack)[ipd] = (uintptr_t)ip;
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_t : FL_f;
else{
FL(stack)[ipd] = (uintptr_t)ip;
v = numeric_compare(v, e, true, false, true) == 0 ? FL_t : 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;
}
FL(stack)[ipd] = (uintptr_t)ip;
argcount(nargs, n);
NEXT_OP;
OP(OP_VECTOR)
n = *ip++;
apply_vector:
v = alloc_vector(n, 0);
if(n){
memcpy(&vector_elt(v, 0), &FL(stack)[FL(sp)-n], n*sizeof(value_t));
POPN(n);
}
PUSH(v);
NEXT_OP;
OP(OP_ASET)
FL(stack)[ipd] = (uintptr_t)ip;
v = FL(stack)[FL(sp)-3];
n = 3;
if(0){
apply_aset:
v = FL(stack)[FL(sp)-n];
for(i = n-1; i >= 3; i--){
if(isarray(v)){
FL(stack)[FL(sp)-i-1] = v;
v = cvalue_array_aref(&FL(stack)[FL(sp)-i-1]);
continue;
}
e = FL(stack)[FL(sp)-i];
isz = tosize(e);
if(isvector(v)){
if(__unlikely(isz >= vector_size(v)))
bounds_error(v, e);
v = vector_elt(v, isz);
continue;
}
if(!iscons(v) && v != FL_nil)
type_error("sequence", v);
for(value_t v0 = v;; isz--){
if(isz == 0){
v = car_(v);
break;
}
v = cdr_(v);
if(__unlikely(!iscons(v)))
bounds_error(v0, e);
}
}
FL(stack)[FL(sp)-3] = v;
}
e = FL(stack)[FL(sp)-2];
isz = tosize(e);
if(isvector(v)){
if(__unlikely(isz >= vector_size(v)))
bounds_error(v, e);
vector_elt(v, isz) = (e = FL(stack)[FL(sp)-1]);
}else if(iscons(v) || v == FL_nil){
for(value_t v0 = v;; isz--){
if(isz == 0){
car_(v) = (e = FL(stack)[FL(sp)-1]);
break;
}
v = cdr_(v);
if(__unlikely(!iscons(v)))
bounds_error(v0, e);
}
}else if(isarray(v)){
e = cvalue_array_aset(&FL(stack)[FL(sp)-3]);
}else{
type_error("sequence", v);
}
POPN(n);
PUSH(e);
NEXT_OP;
OP(OP_FOR)
FL(stack)[ipd] = (uintptr_t)ip;
s = tofixnum(FL(stack)[FL(sp)-3]);
hi = tofixnum(FL(stack)[FL(sp)-2]);
v = FL_void;
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 = _applyn(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 = FL(stack)[bp+i];
PUSH(v);
NEXT_OP;
OP(OP_SETAL)
v = FL(stack)[FL(sp)-1];
i = GET_INT32(ip);
ip += 4;
FL(stack)[bp+i] = v;
NEXT_OP;
OP(OP_LOADCL)
i = GET_INT32(ip);
ip += 4;
v = FL(stack)[bp+nargs];
PUSH(vector_elt(v, i));
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(sp) = bp+i+5;
FL(curr_frame) = FL(sp);
}
}else if(__unlikely(s < 0)){
FL(stack)[ipd] = (uintptr_t)ip;
lerrorf(FL(ArgError), "too few arguments");
}else{
FL(sp)++;
FL(stack)[FL(sp)-2] = i+1;
FL(stack)[FL(sp)-3] = FL(stack)[FL(sp)-4];
FL(stack)[FL(sp)-4] = FL(stack)[FL(sp)-5];
FL(stack)[FL(sp)-5] = FL_nil;
FL(curr_frame) = FL(sp);
}
ipd = FL(sp)-1;
nargs = i+1;
NEXT_OP;
OP(OP_TRYCATCH)
FL(stack)[ipd] = (uintptr_t)ip;
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(__unlikely(nargs < i)){
FL(stack)[ipd] = (uintptr_t)ip;
lerrorf(FL(ArgError), "too few arguments");
}
if((int32_t)n > 0){
if(__unlikely(nargs > n)){
FL(stack)[ipd] = (uintptr_t)ip;
lerrorf(FL(ArgError), "too many arguments");
}
}else
n = -n;
if(__likely(n > nargs)){
n -= nargs;
FL(sp) += n;
FL(stack)[FL(sp)-1] = FL(stack)[FL(sp)-n-1];
FL(stack)[FL(sp)-2] = nargs+n;
FL(stack)[FL(sp)-3] = FL(stack)[FL(sp)-n-3];
FL(stack)[FL(sp)-4] = FL(stack)[FL(sp)-n-4];
FL(curr_frame) = FL(sp);
ipd = FL(sp)-1;
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 = FL(stack)[bp+i];
PUSH(v != UNBOUND ? FL_t : 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;
FL(stack)[ipd] = (uintptr_t)ip;
nargs = process_keys(v, i, n, labs(s)-(i+n), bp, nargs, s<0);
ipd = FL(sp)-1;
NEXT_OP;
#if defined(COMPUTED_GOTO)
#pragma GCC diagnostic pop
#else
}
op = *ip++;
}
#endif
}
#define SWAP_INT32(a)
#define SWAP_INT16(a)
#include "maxstack.inc"
#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)
#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){
const uint8_t *ip1 = (void*)FL(stack)[top-1];
uint32_t sz = FL(stack)[top-2]+1;
uint32_t bp = top-4-sz;
value_t func = FL(stack)[bp];
const uint8_t *ip0 = cvalue_data(fn_bcode(func));
intptr_t ip = ip1 - ip0 - 1; /* -1: ip1 is *after* the one that was being executed */
value_t v = alloc_vector(sz+1, 0);
vector_elt(v, 0) = fixnum(ip);
vector_elt(v, 1) = func;
for(uint32_t i = 1; 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
vector_elt(v, i+1) = si == UNBOUND ? FL_void : si;
}
lst = fl_cons(v, lst);
top = FL(stack)[top-3];
}
fl_free_gc_handles(1);
return lst;
}
// builtins -------------------------------------------------------------------
BUILTIN("gc", gc)
{
USED(args);
argcount(nargs, 0);
gc(0);
return FL_void;
}
BUILTIN("function", function)
{
if(nargs == 1 && issymbol(args[0]))
return fn_builtin_builtin(args, nargs);
if(nargs < 2 || nargs > 4)
argcount(nargs, 2);
if(__unlikely(!fl_isstring(args[0])))
type_error("string", args[0]);
if(__unlikely(!isvector(args[1])))
type_error("vector", args[1]);
cvalue_t *arr = (cvalue_t*)ptr(args[0]);
cv_pin(arr);
char *data = cv_data(arr);
int 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
if(ms < 0)
lerrorf(FL(ArgError), "invalid bytecode");
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(__unlikely(!issymbol(args[3])))
type_error("symbol", args[3]);
fn->name = args[3];
}
}
if(__unlikely(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(__unlikely(!isclosure(v)))
type_error("function", v);
return fn_bcode(v);
}
BUILTIN("function:vals", function_vals)
{
argcount(nargs, 1);
value_t v = args[0];
if(__unlikely(!isclosure(v)))
type_error("function", v);
return fn_vals(v);
}
BUILTIN("function:env", function_env)
{
argcount(nargs, 1);
value_t v = args[0];
if(__unlikely(!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))
return fn_name(v);
if(isbuiltin(v))
return symbol(builtins[uintval(v)].name, false);
if(iscbuiltin(v)){
v = (value_t)ptrhash_get(&FL(reverse_dlsym_lookup_table), (cvalue_t*)ptr(v));
if(v == (value_t)HT_NOTFOUND)
return FL_f;
return v;
}
type_error("function", 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;
uint32_t 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(__unlikely(nargs < 2))
lerrorf(FL(ArgError), "too few arguments");
intptr_t argSP = args-FL(stack);
assert(argSP >= 0 && argSP < FL(nstack));
while(FL(sp)+2+1+nargs >= FL(nstack))
grow_stack();
uint32_t k = FL(sp);
PUSH(FL_nil);
PUSH(FL_nil);
for(bool first = true;;){
PUSH(FL(stack)[argSP]);
for(uint32_t i = 1; i < nargs; i++){
if(!iscons(FL(stack)[argSP+i])){
POPN(2+i);
return FL(stack)[k+1];
}
PUSH(car(FL(stack)[argSP+i]));
FL(stack)[argSP+i] = cdr_(FL(stack)[argSP+i]);
}
value_t v = _applyn(nargs-1);
POPN(nargs);
PUSH(v);
value_t c = mk_cons();
car_(c) = POP(); cdr_(c) = FL_nil;
if(first)
FL(stack)[k+1] = c;
else
cdr_(FL(stack)[k]) = c;
FL(stack)[k] = c;
first = false;
}
}
BUILTIN("for-each", for_each)
{
if(__unlikely(nargs < 2))
lerrorf(FL(ArgError), "too few arguments");
intptr_t argSP = args-FL(stack);
assert(argSP >= 0 && argSP < FL(nstack));
if(FL(sp)+1+2*nargs >= FL(nstack))
grow_stack();
for(size_t n = 0;; n++){
PUSH(FL(stack)[argSP]);
uint32_t pargs = 0;
for(uint32_t i = 1; i < nargs; i++, pargs++){
value_t v = FL(stack)[argSP+i];
if(iscons(v)){
PUSH(car_(v));
FL(stack)[argSP+i] = cdr_(v);
continue;
}
if(isvector(v)){
size_t sz = vector_size(v);
if(n < sz){
PUSH(vector_elt(v, n));
continue;
}
}
if(isarray(v)){
size_t sz = cvalue_arraylen(v);
if(n < sz){
value_t a[2];
a[0] = v;
a[1] = fixnum(n);
PUSH(cvalue_array_aref(a));
continue;
}
}
if(ishashtable(v)){
htable_t *h = totable(v);
assert(n != 0 || h->i == 0);
void **table = h->table;
for(; h->i < h->size; h->i += 2){
if(table[h->i+1] != HT_NOTFOUND)
break;
}
if(h->i < h->size){
PUSH((value_t)table[h->i]);
pargs++;
PUSH((value_t)table[h->i+1]);
h->i += 2;
continue;
}
h->i = 0;
}
POPN(pargs+1);
return FL_void;
}
_applyn(pargs);
POPN(pargs+1);
}
}
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_void;
}
BUILTIN("vm-stats", vm_stats)
{
USED(args);
argcount(nargs, 0);
ios_printf(ios_stderr, "heap total %10"PRIu32"\n", FL(heapsize));
ios_printf(ios_stderr, "heap free %10"PRIu32"\n", (uint32_t)(FL(lim)-FL(curheap)));
ios_printf(ios_stderr, "heap used %10"PRIu32"\n", (uint32_t)(FL(curheap)-FL(fromspace)));
ios_printf(ios_stderr, "stack %10"PRIu32"\n", FL(nstack)*sizeof(value_t));
ios_printf(ios_stderr, "gc calls %10"PRIu64"\n", (uint64_t)FL(gccalls));
ios_printf(ios_stderr, "max finalizers %10"PRIu32"\n", (uint32_t)FL(maxfinalizers));
ios_printf(ios_stderr, "opcodes %10d\n", N_OPCODES);
return FL_void;
}
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 = calloc(1, sizeof(*fl));
FL(scr_width) = 100;
FL(heapsize) = initial_heapsize;
FL(fromspace) = MEM_ALLOC(FL(heapsize));
FL(tospace) = MEM_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(nstack) = 4096;
FL(stack) = MEM_ALLOC(FL(nstack)*sizeof(value_t));
FL(lambda) = symbol("λ", false);
FL(function) = symbol("function", false);
FL(quote) = symbol("quote", false);
FL(trycatch) = symbol("trycatch", false);
FL(backquote) = symbol("quasiquote", false);
FL(comma) = symbol("unquote", false);
FL(commaat) = symbol("unquote-splicing", false);
FL(commadot) = symbol("unquote-nsplicing", false);
FL(IOError) = symbol("io-error", false);
FL(ParseError) = symbol("parse-error", false);
FL(TypeError) = symbol("type-error", false);
FL(ArgError) = symbol("arg-error", false);
FL(UnboundError) = symbol("unbound-error", false);
FL(KeyError) = symbol("key-error", false);
FL(MemoryError) = symbol("memory-error", false);
FL(BoundsError) = symbol("bounds-error", false);
FL(DivideError) = symbol("divide-error", false);
FL(EnumerationError) = symbol("enumeration-error", false);
FL(Error) = symbol("error", false);
FL(conssym) = symbol("cons", false);
FL(symbolsym) = symbol("symbol", false);
FL(fixnumsym) = symbol("fixnum", false);
FL(vectorsym) = symbol("vector", false);
FL(builtinsym) = symbol("builtin", false);
FL(booleansym) = symbol("boolean", false);
FL(nullsym) = symbol("null", false);
FL(definesym) = symbol("define", false);
FL(defmacrosym) = symbol("define-macro", false);
FL(forsym) = symbol("for", false);
FL(setqsym) = symbol("set!", false);
FL(evalsym) = symbol("eval", false);
FL(vu8sym) = symbol("vu8", false);
FL(fnsym) = symbol("fn", false);
FL(nulsym) = symbol("nul", false);
FL(alarmsym) = symbol("alarm", false);
FL(backspacesym) = symbol("backspace", false);
FL(tabsym) = symbol("tab", false);
FL(linefeedsym) = symbol("linefeed", false);
FL(vtabsym) = symbol("vtab", false);
FL(pagesym) = symbol("page", false);
FL(returnsym) = symbol("return", false);
FL(escsym) = symbol("esc", false);
FL(spacesym) = symbol("space", false);
FL(deletesym) = symbol("delete", false);
FL(newlinesym) = symbol("newline", false);
FL(tsym) = symbol("t", false);
FL(Tsym) = symbol("T", false);
FL(fsym) = symbol("f", false);
FL(Fsym) = symbol("F", false);
FL(builtins_table_sym) = symbol("*builtins*", false);
set(FL(printprettysym) = symbol("*print-pretty*", false), FL_t);
set(FL(printreadablysym) = symbol("*print-readably*", false), FL_t);
set(FL(printwidthsym) = symbol("*print-width*", false), fixnum(FL(scr_width)));
set(FL(printlengthsym) = symbol("*print-length*", false), FL_f);
set(FL(printlevelsym) = symbol("*print-level*", false), FL_f);
FL(lasterror) = FL_nil;
for(i = 0; i < nelem(builtins); i++){
if(builtins[i].name)
set(symbol(builtins[i].name, false), builtin(i));
}
set(symbol("procedure?", false), builtin(OP_FUNCTIONP));
set(symbol("top-level-bound?", false), builtin(OP_BOUNDP));
FL(the_empty_vector) = tagptr(alloc_words(1), TAG_VECTOR);
vector_setsize(FL(the_empty_vector), 0);
cvalues_init();
set(symbol("*os-name*", false), cvalue_static_cstring(__os_name__));
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++)
set(symbol(b->name, false), 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;
uint32_t 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(ios_stderr, "fatal error during bootstrap: ");
fl_print(ios_stderr, FL(lasterror));
ios_putc(ios_stderr, '\n');
return 1;
}
ios_close(value2c(ios_t*, FL(stack)[FL(sp)-1]));
POPN(1);
return 0;
}