#pragma once #ifndef _MSC_VER #include #include #else #include #include #endif #ifdef _WIN32 #include #else #include #endif #include #include #include #include #include #include "nasal.h" #include "nasal_err.h" enum vm_type:u8 { /* none-gc object */ vm_none=0, vm_cnt, vm_addr, vm_ret, vm_nil, vm_num, /* gc object */ vm_str, vm_vec, vm_hash, vm_func, vm_upval, vm_obj, vm_co }; const u32 gc_type_size=vm_co-vm_str+1; enum class obj_type:u32 { null=0, file=1, dir, dylib, faddr }; enum class coroutine_status:u32 { suspended, running, dead }; enum class gc_status:u8 { uncollected=0, collected, found }; struct nas_vec; // vector struct nas_hash; // hashmap(dict) struct nas_func; // function(lambda) struct nas_upval;// upvalue struct nas_obj; // special objects struct nas_co; // coroutine struct nas_val; // nas_val includes gc-managed types struct var { u8 type; union { u32 ret; i64 cnt; f64 num; var* addr; nas_val* gcobj; } val; var() = default; var(const var&) = default; bool operator==(const var& nr) const {return type==nr.type && val.gcobj==nr.val.gcobj;} bool operator!=(const var& nr) const {return type!=nr.type || val.gcobj!=nr.val.gcobj;} friend std::ostream& operator<<(std::ostream&,var&); // number and string can be translated to each other f64 tonum(); string tostr(); bool objchk(obj_type); // create new var object static var none(); static var nil(); static var ret(u32); static var cnt(i64); static var num(f64); static var gcobj(nas_val*); static var addr(var*); // get content var* addr(); u32 ret (); i64& cnt (); f64 num (); string& str (); nas_vec& vec (); nas_hash& hash(); nas_func& func(); nas_upval& upval(); nas_obj& obj (); nas_co& co (); }; struct nas_vec { std::vector elems; // mark if this is printed, avoid stackoverflow bool printed; nas_vec():printed(false) {} usize size() const {return elems.size();} var get_val(const i32); var* get_mem(const i32); }; struct nas_hash { std::unordered_map elems; // mark if this is printed, avoid stackoverflow bool printed; nas_hash():printed(false) {} usize size() const {return elems.size();} var get_val(const string&); var* get_mem(const string&); }; struct nas_func { i32 dpara; // dynamic parameter name index in hash. u32 entry; // pc will set to entry-1 to call this function u32 psize; // used to load default parameters to a new function u32 lsize; // used to expand memory space for local values on stack std::vector local; // local scope with default value(var) std::vector upval; // closure std::unordered_map keys; // parameter table, u32 begins from 1 nas_func():dpara(-1),entry(0),psize(0),lsize(0) {} void clear(); }; struct nas_upval { /* on stack, use these variables */ bool onstk; u32 size; var* stk; /* not on stack, use this */ std::vector elems; nas_upval() {onstk=true;stk=nullptr;size=0;} var& operator[](usize n) {return onstk? stk[n]:elems[n];} void clear() {onstk=true;elems.clear();size=0;} }; struct nas_obj { obj_type type; void* ptr; private: /* RAII constructor, new object is initialized when creating */ void file_dtor() { fclose((FILE*)ptr); } void dir_dtor() { #ifndef _MSC_VER closedir((DIR*)ptr); #else FindClose(ptr); #endif } void dylib_dtor() { #ifdef _WIN32 FreeLibrary((HMODULE)ptr); #else dlclose(ptr); #endif } public: nas_obj():type(obj_type::null),ptr(nullptr) {} ~nas_obj() {clear();} void set(obj_type,void*); void clear(); }; struct context { u32 pc; var* localr; var* memr; var funcr; var upvalr; var* canary; var* stack; var* top; }; struct nas_co { var stack[STACK_DEPTH]; context ctx; coroutine_status status; nas_co() {clear();} void clear(); }; struct nas_val { gc_status mark; u8 type; // value type u8 unmut; // used to mark if a string is unmutable union { string* str; nas_vec* vec; nas_hash* hash; nas_func* func; nas_upval* upval; nas_obj* obj; nas_co* co; } ptr; nas_val(u8); ~nas_val(); void clear(); }; var nas_vec::get_val(const i32 n) { i32 size=elems.size(); if (n<-size || n>=size) { return var::none(); } return elems[n>=0?n:n+size]; } var* nas_vec::get_mem(const i32 n) { i32 size=elems.size(); if (n<-size || n>=size) { return nullptr; } return &elems[n>=0?n:n+size]; } std::ostream& operator<<(std::ostream& out,nas_vec& vec) { if (!vec.elems.size() || vec.printed) { out<<(vec.elems.size()?"[..]":"[]"); return out; } vec.printed=true; usize iter=0,size=vec.elems.size(); out<<'['; for(auto& i:vec.elems) { out<clear(); break; case vm_vec: ptr.vec->elems.clear(); break; case vm_hash: ptr.hash->elems.clear();break; case vm_func: ptr.func->clear(); break; case vm_upval:ptr.upval->clear(); break; case vm_obj: ptr.obj->clear(); break; case vm_co: ptr.co->clear(); break; } } f64 var::tonum() { return type!=vm_str? val.num:str2num(str().c_str()); } string var::tostr() { if (type==vm_str) { return str(); } else if (type==vm_num) { string tmp=std::to_string(num()); tmp.erase(tmp.find_last_not_of('0')+1,string::npos); tmp.erase(tmp.find_last_not_of('.')+1,string::npos); return tmp; } return ""; } std::ostream& operator<<(std::ostream& out,var& ref) { switch(ref.type) { case vm_none: out<<"undefined"; break; case vm_nil: out<<"nil"; break; case vm_num: out<"; break; case vm_co: out<<""; break; } return out; } bool var::objchk(obj_type objtype) { return type==vm_obj && obj().type==objtype && obj().ptr; } var var::none() { return {vm_none,{0}}; } var var::nil() { return {vm_nil,{0}}; } var var::ret(u32 pc) { return {vm_ret,{.ret=pc}}; } var var::cnt(i64 n) { return {vm_cnt,{.cnt=n}}; } var var::num(f64 n) { return {vm_num,{.num=n}}; } var var::gcobj(nas_val* p) { var tmp={p->type,{0}}; tmp.val.gcobj=p; return tmp; } var var::addr(var* p) { var tmp={vm_addr,{0}}; tmp.val.addr=p; return tmp; } var* var::addr () {return val.addr; } u32 var::ret () {return val.ret; } i64& var::cnt () {return val.cnt; } f64 var::num () {return val.num; } string& var::str () {return *val.gcobj->ptr.str; } nas_vec& var::vec () {return *val.gcobj->ptr.vec; } nas_hash& var::hash () {return *val.gcobj->ptr.hash; } nas_func& var::func () {return *val.gcobj->ptr.func; } nas_upval& var::upval() {return *val.gcobj->ptr.upval;} nas_obj& var::obj () {return *val.gcobj->ptr.obj; } nas_co& var::co () {return *val.gcobj->ptr.co; } const var zero=var::num(0); const var one =var::num(1); const var nil =var::nil(); struct gc { /* main context temporary storage */ context mctx; /* runtime context */ context* rctx; nas_co* cort=nullptr; // running coroutine /* temporary space used in builtin/module functions */ var temp=nil; /* constants and memory pool */ std::vector strs; // reserved address for const vm_str std::vector env_argv; // command line arguments std::vector memory; // gc memory std::vector unused[gc_type_size]; // gc free list /* heap increase size */ u32 incr[gc_type_size]={ 128, // vm_str 128, // vm_vec 64, // vm_hash 128, // vm_func 256, // vm_upval 16, // vm_obj 16 // vm_co }; /* values for analysis */ u64 size[gc_type_size]; u64 gcnt[gc_type_size]; u64 acnt[gc_type_size]; i64 worktime=0; gc(context* _ctx): rctx(_ctx) {} private: /* gc functions */ void mark(); void mark_context(std::vector&); void mark_var(std::vector&,var&); inline void mark_vec(std::vector&,nas_vec&); inline void mark_hash(std::vector&,nas_hash&); inline void mark_func(std::vector&,nas_func&); inline void mark_upval(std::vector&,nas_upval&); inline void mark_co(std::vector&,nas_co&); void sweep(); public: void extend(u8); void init(const std::vector&,const std::vector&); void clear(); void info(); var alloc(const u8); var newstr(char); var newstr(const char*); var newstr(const string&); void ctxchg(nas_co&); void ctxreserve(); }; void gc::mark() { std::vector bfs; mark_context(bfs); while(!bfs.empty()) { var value=bfs.back(); bfs.pop_back(); if (value.type<=vm_num || value.val.gcobj->mark!=gc_status::uncollected) { continue; } mark_var(bfs,value); } } void gc::mark_context(std::vector& bfs_queue) { // scan now running context, this context maybe related to coroutine or main for(var* i=rctx->stack;i<=rctx->top;++i) { bfs_queue.push_back(*i); } bfs_queue.push_back(rctx->funcr); bfs_queue.push_back(rctx->upvalr); bfs_queue.push_back(temp); if (!cort) { return; } // coroutine is running, so scan main process stack from mctx for(var* i=mctx.stack;i<=mctx.top;++i) { bfs_queue.push_back(*i); } bfs_queue.push_back(mctx.funcr); bfs_queue.push_back(mctx.upvalr); } void gc::mark_var(std::vector& bfs_queue,var& value) { value.val.gcobj->mark=gc_status::found; switch(value.type) { case vm_vec: mark_vec(bfs_queue,value.vec()); break; case vm_hash: mark_hash(bfs_queue,value.hash()); break; case vm_func: mark_func(bfs_queue,value.func()); break; case vm_upval: mark_upval(bfs_queue,value.upval()); break; case vm_co: mark_co(bfs_queue,value.co()); break; default: break; } } void gc::mark_vec(std::vector& bfs_queue,nas_vec& vec) { for(auto& i:vec.elems) { bfs_queue.push_back(i); } } void gc::mark_hash(std::vector& bfs_queue,nas_hash& hash) { for(auto& i:hash.elems) { bfs_queue.push_back(i.second); } } void gc::mark_func(std::vector& bfs_queue,nas_func& function) { for(auto& i:function.local) { bfs_queue.push_back(i); } for(auto& i:function.upval) { bfs_queue.push_back(i); } } void gc::mark_upval(std::vector& bfs_queue,nas_upval& upval) { for(auto& i:upval.elems) { bfs_queue.push_back(i); } } void gc::mark_co(std::vector& bfs_queue,nas_co& co) { bfs_queue.push_back(co.ctx.funcr); bfs_queue.push_back(co.ctx.upvalr); for(var* i=co.stack;i<=co.ctx.top;++i) { bfs_queue.push_back(*i); } } void gc::sweep() { for(auto i:memory) { if (i->mark==gc_status::uncollected) { i->clear(); unused[i->type-vm_str].push_back(i); i->mark=gc_status::collected; } else if (i->mark==gc_status::found) { i->mark=gc_status::uncollected; } } } void gc::extend(u8 type) { u8 index=type-vm_str; size[index]+=incr[index]; for(u32 i=0;i& s,const std::vector& argv) { // initialize function register rctx->funcr=nil; worktime=0; // initialize counters for(u8 i=0;iunmut=1; strs[i].str()=s[i]; } // record arguments env_argv.resize(argv.size()); for(usize i=0;iunmut=1; env_argv[i].str()=argv[i]; } } void gc::clear() { for(auto i:memory) { delete i; } memory.clear(); for(u8 i=0;imark=gc_status::uncollected; unused[index].pop_back(); return ret; } var gc::newstr(char c) { var s=alloc(vm_str); s.str()=c; return s; } var gc::newstr(const char* buff) { var s=alloc(vm_str); s.str()=buff; return s; } var gc::newstr(const string& buff) { var s=alloc(vm_str); s.str()=buff; return s; } void gc::ctxchg(nas_co& co) { // store running state to main context mctx=*rctx; // restore coroutine context state *rctx=co.ctx; // set coroutine pointer cort=&co; // set coroutine state to running cort->status=coroutine_status::running; } void gc::ctxreserve() { // pc=0 means this coroutine is finished cort->status=rctx->pc? coroutine_status::suspended: coroutine_status::dead; // store running state to coroutine cort->ctx=*rctx; // restore main context state *rctx=mctx; // set coroutine pointer to nullptr cort=nullptr; } // use to print error log and return error value var nas_err(const string& err_f,const string& info) { std::cerr<<"[vm] "<