ref: 24a71723ffbe92ccbdc088a078d776fb869b52ca
dir: /src/sl.c/
#include "sl.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 "io.h" #include "compress.h" sl_v sl_builtinssym, sl_quote, sl_lambda, sl_comma, sl_commaat; sl_v sl_commadot, sl_trycatch, sl_backquote; sl_v sl_conssym, sl_symsym, sl_fixnumsym, sl_vecsym, sl_builtinsym, sl_vu8sym; sl_v sl_defsym, sl_defmacrosym, sl_forsym, sl_setqsym; sl_v sl_booleansym, sl_nullsym, sl_evalsym, sl_fnsym, sl_trimsym; sl_v sl_nulsym, sl_alarmsym, sl_backspacesym, sl_tabsym, sl_linefeedsym, sl_newlinesym; sl_v sl_vtabsym, sl_pagesym, sl_returnsym, sl_escsym, sl_spacesym, sl_deletesym; sl_v sl_errio, sl_errparse, sl_errtype, sl_errarg, sl_errmem, sl_errconst; sl_v sl_errdiv0, sl_errbounds, sl_err, sl_errkey, sl_errunbound, sl_erroom; sl_v sl_emptyvec, sl_emptystr; sl_v sl_printwidthsym, sl_printreadablysym, sl_printprettysym, sl_printlengthsym; sl_v sl_printlevelsym; sl_v sl_tablesym, sl_arrsym; sl_v sl_iosym, sl_rdsym, sl_wrsym, sl_apsym, sl_crsym, sl_truncsym; sl_v sl_s8sym, sl_u8sym, sl_s16sym, sl_u16sym, sl_s32sym, sl_u32sym; sl_v sl_s64sym, sl_u64sym, sl_bignumsym; sl_v sl_bytesym, sl_runesym, sl_floatsym, sl_doublesym; sl_v sl_strtypesym; sl_type *sl_mptype, *sl_builtintype; sl_type *sl_s8type, *sl_u8type; sl_type *sl_s16type, *sl_u16type; sl_type *sl_s32type, *sl_u32type; sl_type *sl_s64type, *sl_u64type; sl_type *sl_floattype, *sl_doubletype; sl_type *sl_bytetype, *sl_runetype; sl_type *sl_strtype; sl_thread(Sl *slp); Slg slg = {0}; typedef struct { const char *name; builtin_t fptr; }sl_builtinspec; bool isbuiltin(sl_v x) { int i; return tag(x) == TAG_FN && (i = uintval(x)) < nelem(builtins) && builtins[i].name != nil; } static sl_v apply_cl(int nargs) sl_hotfn; // error utilities ------------------------------------------------------------ void free_readstate(sl_readstate *rs) { htable_free(&rs->backrefs); htable_free(&rs->gensyms); } _Noreturn void sl_exit(const char *status) { if(!slg.exiting){ slg.exiting = true; sl_applyn( 1, sym_value(mk_sym("__finish", false)), status == nil ? sl_nil : cvalue_static_cstr(status) ); sl_gc(false); } exits(status); } #define SL_TRY \ sl_exctx _ctx; int l__tr, l__ca; \ _ctx.sp = sl.sp; _ctx.frame = sl.curr_frame; _ctx.rdst = sl.readstate; _ctx.prev = sl.exctx; \ _ctx.ngchnd = slg.ngchandles; sl.exctx = &_ctx; \ if(!sl_setjmp(_ctx.buf)) \ for(l__tr = 1; l__tr; l__tr = 0, (void)(sl.exctx = sl.exctx->prev)) #define SL_CATCH_INC \ l__ca = 0, sl.lasterror = sl_nil, sl.throwing_frame = 0, sl.sp = _ctx.sp, sl.curr_frame = _ctx.frame #define SL_CATCH \ else \ for(l__ca = 1; l__ca; SL_CATCH_INC) #define SL_CATCH_NO_INC \ else \ for(l__ca = 1; l__ca;) void sl_savestate(sl_exctx *_ctx) { _ctx->sp = sl.sp; _ctx->frame = sl.curr_frame; _ctx->rdst = sl.readstate; _ctx->prev = sl.exctx; _ctx->ngchnd = slg.ngchandles; } void sl_restorestate(sl_exctx *_ctx) { sl.lasterror = sl_nil; sl.throwing_frame = 0; sl.sp = _ctx->sp; sl.curr_frame = _ctx->frame; } _Noreturn void sl_raise(sl_v e) { ios_flush(ios_stdout); ios_flush(ios_stderr); sl.lasterror = e; // unwind read state while(sl.readstate != sl.exctx->rdst){ free_readstate(sl.readstate); sl.readstate = sl.readstate->prev; } if(sl.throwing_frame == 0) sl.throwing_frame = sl.curr_frame; slg.ngchandles = sl.exctx->ngchnd; sl_exctx *thisctx = sl.exctx; if(sl.exctx->prev) // don't throw past toplevel sl.exctx = sl.exctx->prev; sl_longjmp(thisctx->buf, 1); } _Noreturn void lerrorf(sl_v e, const char *format, ...) { char msgbuf[256]; va_list args; PUSH(e); va_start(args, format); vsnprintf(msgbuf, sizeof(msgbuf), format, args); sl_v msg = str_from_cstr(msgbuf); va_end(args); e = POP(); sl_raise(mk_list2(e, msg)); } _Noreturn void type_error(const char *expected, sl_v got) { sl_raise(mk_listn(3, sl_errtype, mk_sym(expected, false), got)); } _Noreturn void bounds_error(sl_v arr, sl_v ind) { sl_raise(mk_listn(3, sl_errbounds, arr, ind)); } _Noreturn void const_error(const sl_sym *sym) { lerrorf( sl_errconst, "modifying a %s is not permitted: %s", iskeyword(sym) ? "keyword" : "constant", sym->name ); } _Noreturn void unbound_error(sl_v sym) { sl_raise(mk_listn(2, sl_errunbound, sym)); } _Noreturn void arity_error(int nargs, int c) { lerrorf(sl_errarg, "arity mismatch: wanted %d, got %d", c, nargs); } // safe cast operators -------------------------------------------------------- #define isstr sl_isstr #define SAFECAST_OP(type, ctype, cnvt) \ ctype to##type(sl_v v) \ { \ if(sl_likely(is##type(v))) \ return (ctype)cnvt(v); \ type_error(#type, v); \ } SAFECAST_OP(cons, sl_cons*, ptr) SAFECAST_OP(sym, sl_sym*, ptr) SAFECAST_OP(fixnum, sl_fx, numval) //SAFECAST_OP(cvalue, sl_cv*, ptr) SAFECAST_OP(str, char*, cvalue_data) #undef isstr // symbol table --------------------------------------------------------------- static sl_sym * alloc_sym(const char *str, int len, bool copy) { sl_sym *sym = MEM_ALLOC(sizeof(*sym) + (copy ? len+1 : 0)); sym->numtype = NONNUMERIC; if(str[0] == ':' && str[1] != 0){ sl_v s = tagptr(sym, TAG_SYM); sym->flags = FLAG_KEYWORD; setc(s, s); }else{ sym->binding = UNBOUND; sym->flags = 0; } sym->type = nil; sym->hash = memhash(str, len)^0xAAAAAAAAAAAAAAAAULL; if(copy){ memcpy((char*)(sym+1), str, len+1); sym->name = (const char*)(sym+1); }else{ sym->name = str; } sym->size = 0; return sym; } sl_v mk_sym(const char *str, bool copy) { int len = strlen(str); sl_sym *v; const char *k; if(!Tgetkv(slg.symbols, str, len, &k, (void**)&v)){ v = alloc_sym(str, len, copy); slg.symbols = Tsetl(slg.symbols, v->name, len, v); } return tagptr(v, TAG_SYM); } sl_v mk_csym_(const char *str, int len) { sl_sym *v = alloc_sym(str, len, false); slg.symbols = Tsetl(slg.symbols, str, len, v); return tagptr(v, TAG_SYM); } BUILTIN("gensym", gensym) { argcount(nargs, 0); USED(args); sl_gensym *gs = alloc_words(sizeof(sl_gensym)/sizeof(sl_v)); gs->id = slg.gensym_ctr++; gs->binding = UNBOUND; gs->type = nil; return tagptr(gs, TAG_SYM); } sl_v mk_gensym(void) { return fn_builtin_gensym(nil, 0); } sl_purefn BUILTIN("gensym?", gensymp) { argcount(nargs, 1); return isgensym(args[0]) ? sl_t : sl_nil; } char * uint2str(char *dest, usize len, u64int num, int base) { int i = len-1; u64int b = (u64int)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 * sym_name(sl_v v) { if(ismanaged(v)){ sl_gensym *gs = ptr(v); slg.gsnameno = 1-slg.gsnameno; char *n = uint2str(slg.gsname[slg.gsnameno]+1, sizeof(slg.gsname[0])-1, gs->id, 10); *(--n) = 'g'; return n; } return ((sl_sym*)ptr(v))->name; } // conses --------------------------------------------------------------------- sl_v alloc_cons(void) { sl_cons *c; if(sl_unlikely(slg.curheap > slg.lim)) sl_gc(false); c = (sl_cons*)slg.curheap; slg.curheap += sizeof(sl_cons); return tagptr(c, TAG_CONS); } void * alloc_words(int n) { sl_v *first; #if !defined(BITS64) // force 8-byte alignment if(n & 1) n++; #endif if(sl_unlikely((sl_v*)slg.curheap > (sl_v*)slg.lim+2-n)){ sl_gc(false); while(sl_unlikely((sl_v*)slg.curheap > ((sl_v*)slg.lim)+2-n)) sl_gc(true); } first = (sl_v*)slg.curheap; slg.curheap += n*sizeof(sl_v); return first; } sl_v alloc_vec(usize n, bool init) { if(n == 0) return sl_emptyvec; sl_v *c = alloc_words(n+1); sl_v v = tagptr(c, TAG_VEC); vec_setsize(v, n); if(init){ for(usize i = 0; i < n; i++) vec_elt(v, i) = sl_void; } return v; } // collector ------------------------------------------------------------------ void sl_gc_handle(sl_v *pv) { if(sl_unlikely(slg.ngchandles >= N_GC_HANDLES)) lerrorf(sl_errmem, "out of gc handles"); slg.gchandles[slg.ngchandles++] = pv; } void sl_free_gc_handles(int n) { assert(slg.ngchandles >= n); slg.ngchandles -= n; } sl_v sl_relocate(sl_v v) { sl_v a, d, nc, first, *pcdr; if(isfixnum(v)) return v; uintptr t = tag(v); if(t == TAG_CONS){ // iterative implementation allows arbitrarily long cons chains pcdr = &first; do{ a = car_(v); if(isforwarded(v)){ *pcdr = forwardloc(v); return first; } d = cdr_(v); *pcdr = nc = tagptr((sl_cons*)slg.curheap, TAG_CONS); slg.curheap += sizeof(sl_cons); forward(v, nc); car_(nc) = ismanaged(a) ? sl_relocate(a) : a; pcdr = &cdr_(nc); v = d; }while(iscons(v)); *pcdr = d == sl_nil ? sl_nil : sl_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_VEC){ // N.B.: 0-length vectors secretly have space for a first element usize i, sz = vec_size(v); if(vec_elt(v, -1) & 0x1){ // grown vector nc = sl_relocate(vec_elt(v, 0)); forward(v, nc); }else{ nc = tagptr(alloc_words(sz+1), TAG_VEC); vec_setsize(nc, sz); a = vec_elt(v, 0); forward(v, nc); if(sz > 0){ vec_elt(nc, 0) = sl_relocate(a); for(i = 1; i < sz; i++) vec_elt(nc, i) = sl_relocate(vec_elt(v, i)); } } return nc; } if(t == TAG_FN){ sl_fn *fn = ptr(v); sl_fn *nfn = alloc_words(sizeof(sl_fn)/sizeof(sl_v)); nfn->vals = fn->vals; nfn->bcode = fn->bcode; nc = tagptr(nfn, TAG_FN); forward(v, nc); nfn->vals = sl_relocate(nfn->vals); nfn->bcode = sl_relocate(nfn->bcode); nfn->env = sl_relocate(fn->env); assert(!ismanaged(fn->name)); nfn->name = fn->name; return nc; } if(t == TAG_SYM){ sl_gensym *gs = ptr(v); sl_gensym *ng = alloc_words(sizeof(sl_gensym)/sizeof(sl_v)); ng->id = gs->id; ng->binding = gs->binding; ng->type = gs->type; nc = tagptr(ng, TAG_SYM); forward(v, nc); if(sl_likely(ng->binding != UNBOUND)) ng->binding = sl_relocate(ng->binding); return nc; } if(t == TAG_CPRIM){ sl_cprim *pcp = ptr(v); usize nw = CPRIM_NWORDS+NWORDS(cp_class(pcp)->size); sl_cprim *ncp = alloc_words(nw); while(nw--) ((sl_v*)ncp)[nw] = ((sl_v*)pcp)[nw]; nc = tagptr(ncp, TAG_CPRIM); forward(v, nc); return nc; } return v; } static void trace_globals(void) { const char *k = nil; sl_sym *v; while(Tnext(slg.symbols, &k, (void**)&v)){ if(v->binding != UNBOUND) v->binding = sl_relocate(v->binding); } } void sl_gc(bool mustgrow) { slg.gccalls++; slg.lim = slg.curheap = slg.tospace; slg.lim += slg.heapsize * (slg.grew ? 2 : 1) - sizeof(sl_cons); sl_v *top, *f; if(sl.throwing_frame > sl.curr_frame){ top = sl.throwing_frame - 3; f = (sl_v*)*top; }else{ top = sl.sp; f = sl.curr_frame; } for(;;){ for(sl_v *p = f; p < top; p++) *p = sl_relocate(*p); if(f == sl.stack) break; top = f - 3; f = (sl_v*)*top; } for(int i = 0; i < slg.ngchandles; i++) *slg.gchandles[i] = sl_relocate(*slg.gchandles[i]); trace_globals(); relocate_typetable(); sl_readstate *rs = sl.readstate; while(rs){ sl_v ent; for(int i = 0; i < rs->backrefs.size; i++){ ent = (sl_v)rs->backrefs.table[i]; if(ent != (sl_v)HT_NOTFOUND) rs->backrefs.table[i] = (void*)sl_relocate(ent); } for(int i = 0; i < rs->gensyms.size; i++){ ent = (sl_v)rs->gensyms.table[i]; if(ent != (sl_v)HT_NOTFOUND) rs->gensyms.table[i] = (void*)sl_relocate(ent); } rs->source = sl_relocate(rs->source); rs = rs->prev; } sl.lasterror = sl_relocate(sl.lasterror); sl_erroom = sl_relocate(sl_erroom); sl_emptyvec = sl_relocate(sl_emptyvec); sl_emptystr = sl_relocate(sl_emptystr); sweep_finalizers(); void *temp = slg.tospace; slg.tospace = slg.fromspace; slg.fromspace = temp; // FIXME(sigrid): add some extra to the "used"? sl_segused(slg.fromspace, slg.heapsize, slg.curheap-slg.fromspace); // if we're using > 80% of the space, resize tospace so we have // more space to fill next time. if we grew tospace last time, // grow the other half of the heap this time to catch up. if(slg.grew || ((intptr)(slg.lim-slg.curheap) < (intptr)slg.heapsize/5) || mustgrow){ sl_segfree(slg.tospace, slg.heapsize); slg.tospace = sl_segalloc(slg.heapsize*2); if(sl_unlikely(slg.tospace == nil)){ slg.tospace = sl_segalloc(slg.heapsize); if(slg.tospace == nil){ // FIXME(sigrid): lost it entirely. give up? // alternatively, wait and try indefinitely? sysfatal("lost tospace"); } sl_raise(sl_erroom); } if(slg.grew){ slg.heapsize *= 2; temp = bitvector_resize(sl.consflags, 0, slg.heapsize/sizeof(sl_cons), 1); if(sl_unlikely(temp == nil)) sl_raise(sl_erroom); sl.consflags = (u32int*)temp; } slg.grew = !slg.grew; } if(sl_unlikely((sl_v*)slg.curheap > (sl_v*)slg.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. sl_gc(false); } } // utils ---------------------------------------------------------------------- // apply function with n args on the stack sl_hotfn static sl_v _applyn(int n) { sl_v *saveSP = sl.sp; sl_v f = saveSP[-n-1]; sl_v v; if(iscbuiltin(f)) v = ((sl_cv*)ptr(f))->cbuiltin(saveSP-n, n); else if(isfn(f)) v = apply_cl(n); else if(sl_likely(isbuiltin(f))){ sl_v tab = sym_value(sl_builtinssym); if(sl_unlikely(ptr(tab) == nil)) unbound_error(tab); saveSP[-n-1] = vec_elt(tab, uintval(f)); v = apply_cl(n); }else{ type_error("fn", f); } sl.sp = saveSP; return v; } sl_v sl_apply(sl_v f, sl_v v) { sl_v *saveSP = sl.sp; PUSH(f); int n; for(n = 0; iscons(v); n++){ PUSH(car_(v)); v = cdr_(v); } if(v != sl_nil) lerrorf(sl_errarg, "apply: last argument: not a list"); v = _applyn(n); sl.sp = saveSP; return v; } sl_v sl_applyn(int n, sl_v f, ...) { va_list ap; va_start(ap, f); PUSH(f); for(int i = 0; i < n; i++){ sl_v a = va_arg(ap, sl_v); PUSH(a); } sl_v v = _applyn(n); POPN(n+1); va_end(ap); return v; } sl_v mk_listn(int n, ...) { va_list ap; va_start(ap, n); sl_v *si = sl.sp; for(int i = 0; i < n; i++){ sl_v a = va_arg(ap, sl_v); PUSH(a); } sl_cons *c = alloc_words(n*2); sl_cons *l = c; for(int i = 0; i < n; i++){ c->car = *si++; c->cdr = tagptr(c+1, TAG_CONS); c++; } c[-1].cdr = sl_nil; POPN(n); va_end(ap); return tagptr(l, TAG_CONS); } sl_v mk_list2(sl_v a, sl_v b) { PUSH(a); PUSH(b); sl_cons *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 = sl_nil; return tagptr(c, TAG_CONS); } sl_v mk_cons(sl_v a, sl_v b) { PUSH(a); PUSH(b); sl_v c = alloc_cons(); cdr_(c) = POP(); car_(c) = POP(); return c; } bool sl_isnum(sl_v v) { if(isfixnum(v) || ismp(v)) return true; if(iscprim(v)){ sl_cprim *c = ptr(v); return c->type != sl_runetype && valid_numtype(c->type->numtype); } return false; } // eval ----------------------------------------------------------------------- sl_hotfn static sl_v list(sl_v *args, int nargs, bool star) { if(sl_unlikely(nargs == 0)) return sl_nil; sl_v v = cons_reserve(nargs); sl_cons *c = ptr(v); for(int 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 = sl_nil; return v; } static sl_v copy_list(sl_v L) { if(!iscons(L)) return sl_nil; sl_v *plcons = sl.sp; sl_v *pL = plcons+1; PUSH(sl_nil); PUSH(L); sl_v c; c = alloc_cons(); PUSH(c); // save first cons car_(c) = car_(*pL); cdr_(c) = sl_nil; *plcons = c; *pL = cdr_(*pL); while(iscons(*pL)){ c = alloc_cons(); car_(c) = car_(*pL); cdr_(c) = sl_nil; cdr_(*plcons) = c; *plcons = c; *pL = cdr_(*pL); } c = POP(); // first cons POPN(2); return c; } static sl_v do_trycatch(void) { sl_v *saveSP = sl.sp; sl_v v = sl_nil; sl_v thunk = saveSP[-2]; sl.sp[-2] = saveSP[-1]; sl.sp[-1] = thunk; SL_TRY{ v = apply_cl(0); } SL_CATCH{ v = saveSP[-2]; PUSH(v); PUSH(sl.lasterror); v = apply_cl(1); } sl.sp = saveSP; return v; } /* argument layout on stack is |--required args--|--opt args--|--kw args--|--rest args... */ static int process_keys(sl_v kwtable, int nreq, int nkw, int nopt, sl_v *bp, int nargs, int va) { int extr = nopt+nkw; int ntot = nreq+extr; sl_v args[64], v = sl_nil; int i, a = 0, nrestargs; sl_v s1 = sl.sp[-1]; sl_v s3 = sl.sp[-3]; sl_v s4 = sl.sp[-4]; if(sl_unlikely(nargs < nreq)) lerrorf(sl_errarg, "too few arguments"); if(sl_unlikely(extr > nelem(args))) lerrorf(sl_errarg, "too many arguments"); for(i = 0; i < extr; i++) args[i] = UNBOUND; for(i = nreq; i < nargs; i++){ v = bp[i]; if(issym(v) && iskeyword((sl_sym*)ptr(v))) break; if(a >= nopt) goto no_kw; args[a++] = v; } if(i >= nargs) goto no_kw; // now process keywords uintptr n = vec_size(kwtable)/2; do{ i++; if(sl_unlikely(i >= nargs)) lerrorf(sl_errarg, "keyword %s requires an argument", sym_name(v)); sl_v hv = fixnum(((sl_sym*)ptr(v))->hash); sl_fx lx = numval(hv); uintptr x = 2*((lx < 0 ? -lx : lx) % n); if(sl_likely(vec_elt(kwtable, x) == v)){ intptr idx = numval(vec_elt(kwtable, x+1)); assert(idx < nkw); idx += nopt; if(args[idx] == UNBOUND){ // if duplicate key, keep first value args[idx] = bp[i]; } }else{ lerrorf(sl_errarg, "unsupported keyword %s", sym_name(v)); } i++; if(i >= nargs) break; v = bp[i]; }while(issym(v) && iskeyword((sl_sym*)ptr(v))); no_kw: nrestargs = nargs - i; if(sl_unlikely(!va && nrestargs > 0)) lerrorf(sl_errarg, "too many arguments"); nargs = ntot + nrestargs; if(nrestargs) memmove(bp+ntot, bp+i, nrestargs*sizeof(sl_v)); memmove(bp+nreq, args, extr*sizeof(sl_v)); sl.sp = bp + nargs; assert((intptr)(sl.sp-sl.stack) < (intptr)sl.nstack-4); PUSH(s4); PUSH(s3); PUSH(nargs); PUSH(s1); sl.curr_frame = sl.sp; return nargs; } #if BYTE_ORDER == LITTLE_ENDIAN && defined(MEM_UNALIGNED_ACCESS) #define GET_S32(a) *(const s32int*)(a) #define GET_S16(a) *(const s16int*)(a) #else #define GET_S32(a) (s32int)((a)[0]<<0 | (a)[1]<<8 | (a)[2]<<16 | (u32int)(a)[3]<<24) #define GET_S16(a) (s16int)((a)[0]<<0 | (a)[1]<<8) #endif /* stack on entry: <fn> <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 sl_v apply_cl(int nargs) { sl_v *top_frame = sl.curr_frame, *bp, *ipd; register sl_v *sp = sl.sp; const u8int *ip; bool tail; int n; goto apply_func; #if defined(COMPUTED_GOTO) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" static const void * const ops[] = { #define GOTO_OP_OFFSET(op) [op] = &&op_##op #include "vm_goto.h" #undef GOTO_OP_OFFSET }; #define NEXT_OP goto *ops[*ip++] #define LABEL(x) x #define OP(x) op_##x: #include "vm.h" #undef OP #undef LABEL #undef NEXT_OP #pragma GCC diagnostic pop #else /* just a usual (portable) switch/case */ u8int op; while(1){ switch(op){ #define NEXT_OP break #define LABEL(x) x #define OP(x) case x: #include "vm.h" #undef OP #undef LABEL #undef NEXT_OP } op = *ip++; } #endif } // top = top frame pointer to start at static sl_v _stacktrace(sl_v *top) { sl_v lst = sl_nil; sl_v *stack = sl.stack; sl_gc_handle(&lst); while(top > stack){ const u8int *ip1 = (void*)top[-1]; int sz = top[-2]+1; sl_v *bp = top-4-sz; sl_v fn = bp[0]; const u8int *ip0 = cvalue_data(fn_bcode(fn)); intptr ip = ip1 - ip0 - 1; /* -1: ip1 is *after* the one that was being executed */ sl_v v = alloc_vec(sz+1, 0); vec_elt(v, 0) = fixnum(ip); vec_elt(v, 1) = fn; for(int i = 1; i < sz; i++){ sl_v si = bp[i]; // if there's an error evaluating argument defaults some slots // might be left set to UNBOUND vec_elt(v, i+1) = si == UNBOUND ? sl_void : si; } lst = mk_cons(v, lst); top = (sl_v*)top[-3]; } sl_free_gc_handles(1); return lst; } // builtins ------------------------------------------------------------------- BUILTIN("gc", gc) { USED(args); argcount(nargs, 0); sl_gc(false); return sl_void; } BUILTIN("fn", fn) { if(nargs == 1 && issym(args[0])) return fn_builtin_builtin(args, nargs); if(nargs < 2 || nargs > 4) argcount(nargs, 2); if(sl_unlikely(!sl_isstr(args[0]))) type_error("str", args[0]); if(sl_unlikely(!isvec(args[1]))) type_error("vec", args[1]); sl_cv *arr = ptr(args[0]); cv_pin(arr); u8int *data = cv_data(arr); if(slg.loading){ // read syntax, shifted 48 for compact text representation usize i, sz = cv_len(arr); for(i = 0; i < sz; i++) data[i] -= 48; } sl_fn *fn = alloc_words(sizeof(sl_fn)/sizeof(sl_v)); sl_v fv = tagptr(fn, TAG_FN); fn->bcode = args[0]; fn->vals = args[1]; fn->env = sl_nil; fn->name = sl_lambda; if(nargs > 2){ if(issym(args[2])){ fn->name = args[2]; if(nargs > 3) fn->env = args[3]; }else{ fn->env = args[2]; if(nargs > 3){ if(sl_unlikely(!issym(args[3]))) type_error("sym", args[3]); fn->name = args[3]; } } if(sl_unlikely(isgensym(fn->name))) lerrorf(sl_errarg, "name should not be a gensym"); } return fv; } sl_purefn BUILTIN("fn-code", fn_code) { argcount(nargs, 1); sl_v v = args[0]; if(sl_unlikely(!isfn(v))) type_error("fn", v); return fn_bcode(v); } sl_purefn BUILTIN("fn-vals", fn_vals) { argcount(nargs, 1); sl_v v = args[0]; if(sl_unlikely(!isfn(v))) type_error("fn", v); return fn_vals(v); } sl_purefn BUILTIN("fn-env", fn_env) { argcount(nargs, 1); sl_v v = args[0]; if(sl_unlikely(!isfn(v))) type_error("fn", v); return fn_env(v); } BUILTIN("fn-name", fn_name) { argcount(nargs, 1); sl_v v = args[0]; if(isfn(v)) return fn_name(v); if(isbuiltin(v)) return mk_sym(builtins[uintval(v)].name, false); if(iscbuiltin(v)){ v = (sl_v)ptrhash_get(&slg.reverse_dlsym_lookup, ptr(v)); if(v == (sl_v)HT_NOTFOUND) return sl_nil; return v; } type_error("fn", v); } BUILTIN("copy-list", copy_list) { argcount(nargs, 1); return copy_list(args[0]); } BUILTIN("append", append) { sl_v first = sl_nil, lst, lastcons = sl_nil; int i; if(nargs == 0) return sl_nil; sl_gc_handle(&first); sl_gc_handle(&lastcons); for(i = 0; i < nargs; i++){ lst = args[i]; if(iscons(lst)){ lst = copy_list(lst); if(first == sl_nil) first = lst; else cdr_(lastcons) = lst; lastcons = tagptr((((sl_cons*)slg.curheap)-1), TAG_CONS); }else if(lst != sl_nil){ type_error("cons", lst); } } sl_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, true); } BUILTIN("stacktrace", stacktrace) { USED(args); argcount(nargs, 0); return _stacktrace(sl.throwing_frame ? sl.throwing_frame : sl.curr_frame); } BUILTIN("map", map) { if(sl_unlikely(nargs < 2)) lerrorf(sl_errarg, "too few arguments"); sl_v *k = sl.sp; PUSH(sl_nil); PUSH(sl_nil); for(bool first = true;;){ PUSH(args[0]); for(int i = 1; i < nargs; i++){ if(!iscons(args[i])){ POPN(2+i); return k[1]; } PUSH(car(args[i])); args[i] = cdr_(args[i]); } sl_v v = _applyn(nargs-1); POPN(nargs); PUSH(v); sl_v c = alloc_cons(); car_(c) = POP(); cdr_(c) = sl_nil; if(first) k[1] = c; else cdr_(k[0]) = c; k[0] = c; first = false; } } BUILTIN("for-each", for_each) { if(sl_unlikely(nargs < 2)) lerrorf(sl_errarg, "too few arguments"); for(usize n = 0;; n++){ PUSH(args[0]); int pargs = 0; for(int i = 1; i < nargs; i++, pargs++){ sl_v v = args[i]; if(iscons(v)){ PUSH(car_(v)); args[i] = cdr_(v); continue; } if(isvec(v)){ usize sz = vec_size(v); if(n < sz){ PUSH(vec_elt(v, n)); continue; } } if(isarr(v)){ usize sz = cvalue_arrlen(v); if(n < sz){ sl_v a[2]; a[0] = v; a[1] = fixnum(n); PUSH(cvalue_arr_aref(a)); continue; } } if(ishashtable(v)){ sl_htable *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((sl_v)table[h->i]); pargs++; PUSH((sl_v)table[h->i+1]); h->i += 2; continue; } h->i = 0; } POPN(pargs+1); return sl_void; } _applyn(pargs); POPN(pargs+1); } } BUILTIN("sleep", sl_sleep) { if(nargs > 1) argcount(nargs, 1); double s = nargs > 0 ? todouble(args[0]) : 0; sleep_ms(s * 1000.0); return sl_void; } BUILTIN("vm-stats", vm_stats) { USED(args); argcount(nargs, 0); sl_ios *io = toio(sym_value(sl_iooutsym)); ios_printf(io, "heap total %10"PRIuPTR" bytes\n", slg.heapsize); ios_printf(io, "heap free %10"PRIuPTR" bytes\n", (uintptr)(slg.lim-slg.curheap)); ios_printf(io, "heap used %10"PRIuPTR" bytes\n", (uintptr)(slg.curheap-slg.fromspace)); ios_printf(io, "stack %10"PRIu64" bytes\n", (u64int)sl.nstack*sizeof(sl_v)); ios_printf(io, "finalizers %10"PRIu32"\n", (u32int)slg.nfinalizers); ios_printf(io, "max finalizers %10"PRIu32"\n", (u32int)slg.maxfinalizers); ios_printf(io, "gc handles %10"PRIu32"\n", (u32int)slg.ngchandles); ios_printf(io, "gc calls %10"PRIu64"\n", (u64int)slg.gccalls); ios_printf(io, "opcodes %10d\n", N_OPCODES); return sl_void; } static const sl_builtinspec builtin_fns[] = { #define BUILTIN_FN(l, c, attr){l, (builtin_t)fn_builtin_##c}, #include "builtin_fns.h" #undef BUILTIN_FN }; // initialization ------------------------------------------------------------- int sl_init(usize heapsize, usize stacksize) { int i; if((slp = MEM_CALLOC(1, sizeof(*slp))) == nil) return -1; sl.scr_width = 100; slg.heapsize = heapsize*sizeof(sl_v); if((slg.fromspace = sl_segalloc(slg.heapsize)) == nil){ failed: MEM_FREE(sl.consflags); MEM_FREE(slg.finalizers); sl_segfree(slg.fromspace, slg.heapsize); sl_segfree(slg.tospace, slg.heapsize); sl_segfree(sl.stack, stacksize*sizeof(sl_v)); htable_free(&sl.printconses); MEM_FREE(slp); return -1; } if((slg.tospace = sl_segalloc(slg.heapsize)) == nil) goto failed; slg.curheap = slg.fromspace; slg.lim = slg.curheap+slg.heapsize-sizeof(sl_cons); if((sl.stack = sl_segalloc(stacksize*sizeof(sl_v))) == nil) goto failed; sl.curr_frame = sl.sp = sl.stack; sl.nstack = stacksize; slg.maxfinalizers = 512; if((slg.finalizers = MEM_ALLOC(slg.maxfinalizers * sizeof(*slg.finalizers))) == nil) goto failed; if((sl.consflags = bitvector_new(slg.heapsize/sizeof(sl_cons), 1)) == nil) goto failed; if((htable_new(&sl.printconses, 32)) == nil) goto failed; comparehash_init(); sl_lambda = mk_csym("λ"); sl_quote = mk_csym("quote"); sl_trycatch = mk_csym("trycatch"); sl_backquote = mk_csym("quasiquote"); sl_comma = mk_csym("unquote"); sl_commaat = mk_csym("unquote-splicing"); sl_commadot = mk_csym("unquote-nsplicing"); sl_errio = mk_csym("io-error"); sl_errparse = mk_csym("parse-error"); sl_errtype = mk_csym("type-error"); sl_errarg = mk_csym("arg-error"); sl_errunbound = mk_csym("unbound-error"); sl_errkey = mk_csym("key-error"); sl_errmem = mk_csym("memory-error"); sl_errconst = mk_csym("const-error"); sl_errbounds = mk_csym("bounds-error"); sl_errdiv0 = mk_csym("divide-error"); sl_err = mk_csym("error"); sl_conssym = mk_csym("cons"); sl_symsym = mk_csym("symbol"); sl_fixnumsym = mk_csym("fixnum"); sl_vecsym = mk_csym("vec"); sl_builtinsym = mk_csym("builtin"); sl_booleansym = mk_csym("boolean"); sl_nullsym = mk_csym("null"); sl_defsym = mk_csym("def"); sl_defmacrosym = mk_csym("defmacro"); sl_forsym = mk_csym("for"); sl_setqsym = mk_csym("set!"); sl_evalsym = mk_csym("eval"); sl_vu8sym = mk_csym("vu8"); sl_fnsym = mk_csym("fn"); sl_trimsym = mk_csym(":trim"); sl_nulsym = mk_csym("nul"); sl_alarmsym = mk_csym("alarm"); sl_backspacesym = mk_csym("backspace"); sl_tabsym = mk_csym("tab"); sl_linefeedsym = mk_csym("linefeed"); sl_vtabsym = mk_csym("vtab"); sl_pagesym = mk_csym("page"); sl_returnsym = mk_csym("return"); sl_escsym = mk_csym("esc"); sl_spacesym = mk_csym("space"); sl_deletesym = mk_csym("delete"); sl_newlinesym = mk_csym("newline"); sl_builtinssym = mk_csym("*builtins*"); set(sl_printprettysym = mk_csym("*print-pretty*"), sl_t); set(sl_printreadablysym = mk_csym("*print-readably*"), sl_t); set(sl_printwidthsym = mk_csym("*print-width*"), fixnum(sl.scr_width)); set(sl_printlengthsym = mk_csym("*print-length*"), sl_nil); set(sl_printlevelsym = mk_csym("*print-level*"), sl_nil); sl.lasterror = sl_nil; for(i = 0; i < nelem(builtins); i++){ if(builtins[i].name) set(mk_sym(builtins[i].name, false), builtin(i)); } sl_emptyvec = tagptr(alloc_words(1), TAG_VEC); vec_setsize(sl_emptyvec, 0); cvalues_init(); set(mk_csym("*os-name*"), cvalue_static_cstr(__os_name__)); #if defined(__os_version__) set(mk_csym("*os-version*"), cvalue_static_cstr(__os_version__)); #endif sl_erroom = mk_list2(sl_errmem, cvalue_static_cstr("out of memory")); const sl_builtinspec *b; for(i = 0, b = builtin_fns; i < nelem(builtin_fns); i++, b++) cbuiltin(b->name, b->fptr); table_init(); io_init(); compress_init(); sys_init(); return 0; } // top level ------------------------------------------------------------------ sl_v sl_toplevel_eval(sl_v expr) { return sl_applyn(1, sym_value(sl_evalsym), expr); } int sl_load_system_image(sl_v sys_image_io) { slg.loading = true; PUSH(sys_image_io); sl_v *saveSP = sl.sp; SL_TRY{ while(1){ sl.sp = saveSP; sl_v e = sl_read_sexpr(sl.sp[-1]); if(ios_eof(value2c(sl_ios*, sl.sp[-1]))) break; if(isfn(e)){ // stage 0 format: series of thunks PUSH(e); (void)_applyn(0); }else{ // stage 1 format: list alternating symbol/value while(iscons(e)){ sl_sym *sym = tosym(car_(e)); e = cdr_(e); if(sym->binding != UNBOUND) ios_printf(ios_stderr, "%s redefined on boot\n", sym->name); sym->binding = car_(e); e = cdr_(e); } break; } } } SL_CATCH_NO_INC{ ios_puts(ios_stderr, "fatal error during bootstrap: "); sl_print(ios_stderr, sl.lasterror); ios_putc(ios_stderr, '\n'); return -1; } sl.sp = saveSP-1; slg.loading = false; return 0; }