#pragma once #ifdef _MSC_VER #pragma warning (disable:4244) #pragma warning (disable:4267) #pragma warning (disable:4102) #endif #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 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 { public: u8 type; union { u32 ret; i64 cnt; f64 num; var* addr; nas_val* gcobj; } val; private: var(u8 t, u32 pc) {type=t;val.ret=pc;} var(u8 t, i64 ct) {type=t;val.cnt=ct;} var(u8 t, f64 n) {type=t;val.num=n;} var(u8 t, var* p) {type=t;val.addr=p;} var(u8 t, nas_val* p) {type=t;val.gcobj=p;} public: 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(usize); // 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 { public: /* on stack, use these variables */ bool onstk; u32 size; var* stk; /* not on stack, use this */ std::vector elems; public: nas_upval(): onstk(true), size(0), stk(nullptr) {} var& operator[](usize n) { return onstk? stk[n]:elems[n]; } void clear() { onstk=true; elems.clear(); size=0; } }; void filehandle_destructor(void* ptr) { if ((FILE*)ptr==stdin) { return; } fclose((FILE*)ptr); } void dir_entry_destructor(void* ptr) { #ifndef _MSC_VER closedir((DIR*)ptr); #else FindClose(ptr); #endif } void dylib_destructor(void* ptr) { #ifdef _WIN32 FreeLibrary((HMODULE)ptr); #else dlclose(ptr); #endif } void func_addr_destructor(void* ptr) {} struct ghost_register_table { private: using dtor=void (*)(void*); private: std::unordered_map mapper; std::vector ghost_name; std::vector destructors; public: // reserved ghost type only for native functions usize ghost_file; usize ghost_dir; usize ghost_dylib; usize ghost_faddr; public: ghost_register_table() { ghost_file=register_ghost_type("file", filehandle_destructor); ghost_dir=register_ghost_type("dir", dir_entry_destructor); ghost_dylib=register_ghost_type("dylib", dylib_destructor); ghost_faddr=register_ghost_type("faddr", func_addr_destructor); } bool exists(const string& name) const { return mapper.count(name); } usize get_ghost_type_index(const string& name) const { return mapper.at(name); } const string& get_ghost_name(usize index) const { return ghost_name.at(index); } usize register_ghost_type(const std::string& name, dtor ptr) { if (mapper.count(name)) { std::cerr<<"nasal_gc.h: ghost_register_table::register_ghost_type: "; std::cerr<<"ghost type \""<get_ghost_name(ghost.type); out<<" at 0x"<"; return out; } }; 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<destructor(type)(ptr); ptr=nullptr; } void nas_co::clear() { for(u32 i=0;iclear(); 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; } return out; } bool var::objchk(usize obj_type) { return type==vm_obj && obj().type==obj_type && obj().ptr; } var var::none() { return {vm_none, (u32)0}; } var var::nil() { return {vm_nil, (u32)0}; } var var::ret(u32 pc) { return {vm_ret, pc}; } var var::cnt(i64 n) { return {vm_cnt, n}; } var var::num(f64 n) { return {vm_num, n}; } var var::gcobj(nas_val* p) { return {p->type, p}; } var var::addr(var* p) { return {vm_addr, p}; } 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 { ghost_register_table global_ghost_type_table; /* 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); void ctxchg(nas_co&); void ctxreserve(); public: var newstr(char c) { var s=alloc(vm_str); s.str()=c; return s; } var newstr(const char* buff) { var s=alloc(vm_str); s.str()=buff; return s; } var newstr(const string& buff) { var s=alloc(vm_str); s.str()=buff; return s; } }; 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) { const 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; } 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& error_function_name, const string& info) { std::cerr<<"[vm] "<