ref: d9f6cb0d7f88d15f8a110c63e84ab7b5514755c2
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" #include "compress.h" value_t FL_builtins_table_sym, FL_quote, FL_lambda, FL_function, FL_comma, FL_commaat; value_t FL_commadot, FL_trycatch, FL_backquote; value_t FL_conssym, FL_symbolsym, FL_fixnumsym, FL_vectorsym, FL_builtinsym, FL_vu8sym; value_t FL_definesym, FL_defmacrosym, FL_forsym, FL_setqsym; value_t FL_tsym, FL_Tsym, FL_fsym, FL_Fsym, FL_booleansym, FL_nullsym, FL_evalsym, FL_fnsym; value_t FL_nulsym, FL_alarmsym, FL_backspacesym, FL_tabsym, FL_linefeedsym, FL_newlinesym; value_t FL_vtabsym, FL_pagesym, FL_returnsym, FL_escsym, FL_spacesym, FL_deletesym; value_t FL_IOError, FL_ParseError, FL_TypeError, FL_ArgError, FL_MemoryError; value_t FL_DivideError, FL_BoundsError, FL_Error, FL_KeyError, FL_EnumerationError; value_t FL_UnboundError; value_t FL_sizesym, FL_tosym; value_t FL_printwidthsym, FL_printreadablysym, FL_printprettysym, FL_printlengthsym; value_t FL_printlevelsym; value_t FL_tablesym, FL_arraysym; value_t FL_iostreamsym, FL_rdsym, FL_wrsym, FL_apsym, FL_crsym, FL_truncsym; value_t FL_instrsym, FL_outstrsym; value_t FL_int8sym, FL_uint8sym, FL_int16sym, FL_uint16sym, FL_int32sym, FL_uint32sym; value_t FL_int64sym, FL_uint64sym, FL_bignumsym; value_t FL_bytesym, FL_runesym, FL_floatsym, FL_doublesym; value_t FL_stringtypesym, FL_runestringtypesym; 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" static const void *ops[] = { #define GOTO_OP_OFFSET(op) [op] = &&op_##op #include "vm_goto.inc" #undef GOTO_OP_OFFSET }; #define NEXT_OP goto *ops[*ip++] #define LABEL(x) x #define OP(x) op_##x: NEXT_OP; #include "vm.inc" #undef OP #undef LABEL #undef NEXT_OP #pragma GCC diagnostic pop #else /* just a usual (portable) switch/case */ uint8_t op = *ip++; while(1){ switch(op){ #define NEXT_OP break #define LABEL(x) x #define OP(x) case x: #include "vm.inc" #undef OP #undef LABEL #undef NEXT_OP } 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 = MEM_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(); compress_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; }