#ifndef __NASAL_GC_H__ #define __NASAL_GC_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, vm_tsize }; const u32 gc_tsize=vm_tsize-vm_str; // change parameters here to make your own efficient gc // better set bigger number on vm_vec const u32 ini[gc_tsize]= { 128, // vm_str 128, // vm_vec 32, // vm_hash 512, // vm_func 512, // vm_upval 0, // vm_obj 0 // vm_co }; const u32 incr[gc_tsize]= { 256, // vm_str 512, // vm_vec 512, // vm_hash 512, // vm_func 128, // vm_upval 128, // vm_obj 16 // vm_co }; struct nasal_vec; // vector struct nasal_hash; // hashmap(dict) struct nasal_func; // function(lambda) struct nasal_upval;// upvalue struct nasal_obj; // special objects struct nasal_co; // coroutine struct nasal_val; // nasal_val includes gc-managed types struct nasal_ref { u8 type; union { u32 ret; i64 cnt; f64 num; nasal_ref* addr; nasal_val* gcobj; } val; // vm_none/vm_nil nasal_ref(const u8 t=vm_none):type(t){} // vm_ret nasal_ref(const u8 t,const u32 n):type(t){val.ret=n;} // vm_cnt nasal_ref(const u8 t,const i64 n):type(t){val.cnt=n;} // vm_num nasal_ref(const u8 t,const f64 n):type(t){val.num=n;} // vm_str/vm_func/vm_vec/vm_hash/vm_upval/vm_obj nasal_ref(const u8 t,nasal_val* n):type(t){val.gcobj=n;} // vm_addr nasal_ref(const u8 t,nasal_ref* n):type(t){val.addr=n;} nasal_ref(const nasal_ref& nr):type(nr.type),val(nr.val){} bool operator==(const nasal_ref& nr){return type==nr.type && val.gcobj==nr.val.gcobj;} bool operator!=(const nasal_ref& nr){return type!=nr.type || val.gcobj!=nr.val.gcobj;} // number and string can be translated to each other f64 tonum(); std::string tostr(); void print(); bool objchk(u32); inline nasal_ref* addr(); inline u32 ret (); inline i64& cnt (); inline f64 num (); inline std::string& str (); inline nasal_vec& vec (); inline nasal_hash& hash(); inline nasal_func& func(); inline nasal_upval& upval(); inline nasal_obj& obj (); inline nasal_co& co (); }; struct nasal_vec { bool printed; std::vector elems; nasal_vec():printed(false){} void print(); usize size(){return elems.size();} nasal_ref get_val(const int); nasal_ref* get_mem(const int); }; struct nasal_hash { bool printed; std::unordered_map elems; nasal_hash():printed(false){} void print(); usize size(){return elems.size();} nasal_ref get_val(const std::string&); nasal_ref* get_mem(const std::string&); }; struct nasal_func { i32 dynpara; // 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(nasal_ref) std::vector upvalue; // closure std::unordered_map keys; // parameter name table, u32 begins from 1 nasal_func():dynpara(-1),entry(0),psize(0),lsize(0){} void clear(); }; struct nasal_upval { bool onstk; u32 size; nasal_ref* stk; std::vector elems; nasal_upval(){onstk=true;stk=nullptr;size=0;} nasal_ref& operator[](const int i){return onstk?stk[i]:elems[i];} void clear(){onstk=true;elems.clear();size=0;} }; struct nasal_obj { enum obj_t:u32 { null, file, dir, dylib, faddr }; /* RAII constructor */ /* new object is initialized when creating */ u32 type; void* ptr; /* RAII destroyer */ /* default destroyer does nothing */ typedef void (*dest)(void*); dest dtor; nasal_obj():type(obj_t::null),ptr(nullptr),dtor(nullptr){} ~nasal_obj(){clear();} void set(u32 t=obj_t::null,void* p=nullptr,dest d=nullptr) { type=t; ptr=p; dtor=d; } void clear() { if(dtor && ptr) dtor(ptr); ptr=nullptr; dtor=nullptr; } }; struct nasal_co { enum coroutine_stat { suspended, running, dead }; nasal_ref stack[STACK_DEPTH]; u32 pc; nasal_ref* top; nasal_ref* canary; nasal_ref* localr; nasal_ref* memr; nasal_ref funcr; nasal_ref upvalr; u32 status; nasal_co(): pc(0), top(stack), canary(stack+STACK_DEPTH-1), localr(nullptr), memr(nullptr), funcr({vm_nil,(f64)0}), upvalr({vm_nil,(f64)0}), status(nasal_co::suspended) { for(u32 i=0;i=size) return {vm_none}; return elems[index>=0?index:index+size]; } nasal_ref* nasal_vec::get_mem(const int index) { int size=elems.size(); if(index<-size || index>=size) return nullptr; return &elems[index>=0?index:index+size]; } void nasal_vec::print() { if(!elems.size() || printed) { std::cout<<(elems.size()?"[..]":"[]"); return; } printed=true; usize iter=0; usize size=elems.size(); std::cout<<'['; for(auto& i:elems) { i.print(); std::cout<<",]"[(++iter)==size]; } printed=false; } nasal_ref nasal_hash::get_val(const std::string& key) { if(elems.count(key)) return elems[key]; else if(elems.count("parents")) { nasal_ref ret(vm_none); nasal_ref val=elems["parents"]; if(val.type==vm_vec) for(auto& i:val.vec().elems) { if(i.type==vm_hash) ret=i.hash().get_val(key); if(ret.type!=vm_none) return ret; } } return {vm_none}; } nasal_ref* nasal_hash::get_mem(const std::string& key) { if(elems.count(key)) return &elems[key]; else if(elems.count("parents")) { nasal_ref* addr=nullptr; nasal_ref val=elems["parents"]; if(val.type==vm_vec) for(auto& i:val.vec().elems) { if(i.type==vm_hash) addr=i.hash().get_mem(key); if(addr) return addr; } } return nullptr; } void nasal_hash::print() { if(!elems.size() || printed) { std::cout<<(elems.size()?"{..}":"{}"); return; } printed=true; usize iter=0; usize size=elems.size(); std::cout<<'{'; for(auto& i:elems) { std::cout<"; break; case vm_co: std::cout<<""; break; } } bool nasal_ref::objchk(u32 objtype) { return type==vm_obj && obj().type==objtype && obj().ptr; } inline nasal_ref* nasal_ref::addr (){return val.addr; } inline u32 nasal_ref::ret (){return val.ret; } inline i64& nasal_ref::cnt (){return val.cnt; } inline f64 nasal_ref::num (){return val.num; } inline std::string& nasal_ref::str (){return *val.gcobj->ptr.str; } inline nasal_vec& nasal_ref::vec (){return *val.gcobj->ptr.vec; } inline nasal_hash& nasal_ref::hash (){return *val.gcobj->ptr.hash; } inline nasal_func& nasal_ref::func (){return *val.gcobj->ptr.func; } inline nasal_upval& nasal_ref::upval(){return *val.gcobj->ptr.upval;} inline nasal_obj& nasal_ref::obj (){return *val.gcobj->ptr.obj; } inline nasal_co& nasal_ref::co (){return *val.gcobj->ptr.co; } const nasal_ref zero={vm_num,(f64)0}; const nasal_ref one ={vm_num,(f64)1}; const nasal_ref nil ={vm_nil,(f64)0}; struct nasal_gc { struct { u32 pc; nasal_ref* top; nasal_ref* localr; nasal_ref* memr; nasal_ref funcr; nasal_ref upvalr; nasal_ref* canary; nasal_ref* stack; } main_ctx; /* runtime context */ u32& pc; // program counter nasal_ref*& localr; // local scope register nasal_ref*& memr; // used for mem_call nasal_ref& funcr; // function register nasal_ref& upvalr; // upvalue register nasal_ref*& canary; // avoid stackoverflow nasal_ref*& top; // stack top nasal_ref* stack; // stack pointer nasal_co* coroutine; // running coroutine nasal_ref temp; // temporary place used in builtin/module functions /* 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::queue unused[gc_tsize]; // gc free list /* values for analysis */ u64 size[gc_tsize]; u64 count[gc_tsize]; u64 allocc[gc_tsize]; nasal_gc( u32& _pc, nasal_ref*& _localr, nasal_ref*& _memr, nasal_ref& _funcr, nasal_ref& _upvalr, nasal_ref*& _canary, nasal_ref*& _top, nasal_ref* _stk): pc(_pc),localr(_localr),memr(_memr),funcr(_funcr),upvalr(_upvalr), canary(_canary),top(_top),stack(_stk),coroutine(nullptr),temp(nil){} void mark(); void sweep(); void init(const std::vector&,const std::vector&); void clear(); void info(); nasal_ref alloc(const u8); nasal_ref newstr(char); nasal_ref newstr(const char*); nasal_ref newstr(const std::string&); void ctxchg(nasal_co&); void ctxreserve(); }; /* gc functions */ void nasal_gc::mark() { std::queue bfs; // scan coroutine process stack when coroutine ptr is not null // scan main process stack when coroutine ptr is null // this scan process must execute because when running coroutine, // the nasal_co related to it will not update it's context(like `top`) until the coroutine suspends or exits. for(nasal_ref* i=stack;i<=top;++i) bfs.push(*i); bfs.push(funcr); bfs.push(upvalr); bfs.push(temp); if(coroutine) // scan main process stack { for(nasal_ref* i=main_ctx.stack;i<=main_ctx.top;++i) bfs.push(*i); bfs.push(main_ctx.funcr); bfs.push(main_ctx.upvalr); } while(!bfs.empty()) { nasal_ref tmp=bfs.front(); bfs.pop(); if(tmp.type<=vm_num || tmp.val.gcobj->mark) continue; tmp.val.gcobj->mark=GC_FOUND; switch(tmp.type) { case vm_vec: for(auto& i:tmp.vec().elems) bfs.push(i); break; case vm_hash: for(auto& i:tmp.hash().elems) bfs.push(i.second); break; case vm_func: for(auto& i:tmp.func().local) bfs.push(i); for(auto& i:tmp.func().upvalue) bfs.push(i); break; case vm_upval: for(auto& i:tmp.upval().elems) bfs.push(i); break; case vm_co: bfs.push(tmp.co().funcr); bfs.push(tmp.co().upvalr); for(nasal_ref* i=tmp.co().stack;i<=tmp.co().top;++i) bfs.push(*i); break; } } } void nasal_gc::sweep() { for(auto i:memory) { if(i->mark==GC_UNCOLLECTED) { switch(i->type) { case vm_str: i->ptr.str->clear(); break; case vm_vec: i->ptr.vec->elems.clear(); break; case vm_hash: i->ptr.hash->elems.clear();break; case vm_func: i->ptr.func->clear(); break; case vm_upval:i->ptr.upval->clear(); break; case vm_obj: i->ptr.obj->clear(); break; case vm_co: i->ptr.co->clear(); break; } unused[i->type-vm_str].push(i); i->mark=GC_COLLECTED; } else if(i->mark==GC_FOUND) i->mark=GC_UNCOLLECTED; } } void nasal_gc::init(const std::vector& s,const std::vector& argv) { // initiaize function register funcr=nil; 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 nasal_gc::clear() { for(auto i:memory) delete i; memory.clear(); for(u8 i=0;imark=GC_UNCOLLECTED; unused[index].pop(); return ret; } nasal_ref nasal_gc::newstr(char c) { nasal_ref s=alloc(vm_str); s.str()=c; return s; } nasal_ref nasal_gc::newstr(const char* buff) { nasal_ref s=alloc(vm_str); s.str()=buff; return s; } nasal_ref nasal_gc::newstr(const std::string& buff) { nasal_ref s=alloc(vm_str); s.str()=buff; return s; } void nasal_gc::ctxchg(nasal_co& context) { main_ctx.pc=pc; main_ctx.top=top; main_ctx.localr=localr; main_ctx.memr=memr; main_ctx.funcr=funcr; main_ctx.upvalr=upvalr; main_ctx.canary=canary; main_ctx.stack=stack; pc=context.pc; top=context.top; localr=context.localr; memr=context.memr; funcr=context.funcr; upvalr=context.upvalr; canary=context.canary; stack=context.stack; coroutine=&context; coroutine->status=nasal_co::running; } void nasal_gc::ctxreserve() { // pc=0 means this coroutine is finished coroutine->status=pc?nasal_co::suspended:nasal_co::dead; coroutine->pc=pc; coroutine->localr=localr; coroutine->memr=memr; coroutine->funcr=funcr; coroutine->upvalr=upvalr; coroutine->canary=canary; coroutine->top=top; pc=main_ctx.pc; localr=main_ctx.localr; memr=main_ctx.memr; funcr=main_ctx.funcr; upvalr=main_ctx.upvalr; canary=main_ctx.canary; top=main_ctx.top; stack=main_ctx.stack; coroutine=nullptr; } #endif