diff --git a/README.md b/README.md index 38ac06d..5328558 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ * [v6.5](#version-65-vm-last-update-2021624) * [v7.0](#version-70-vm-last-update-2021108) * [v8.0](#version-80-vm-last-update-2022212) - * [v9.0](#version-90-vm-latest) + * [v9.0](#version-90-vm-last-update-2022518) + * [v10.0](#version-100-vm-latest) * [__Benchmark__](#benchmark) * [v6.5 (i5-8250U windows 10)](#version-65-i5-8250u-windows10-2021619) * [v6.5 (i5-8250U ubuntu-WSL)](#version-70-i5-8250u-ubuntu-wsl-on-windows10-2021629) @@ -1246,7 +1247,7 @@ The format of output information of bytecodes changes to this: Delete `op_pone` and `op_pzero`. Both of them are meaningless and will be replaced by `op_pnum`. -### version 9.0 vm (latest) +### version 9.0 vm (last update 2022/5/18) 2022/2/12 update: @@ -1343,6 +1344,131 @@ func <0x2c4>: 0x000002ca: 03 00 00 00 23 loadg 0x23 ``` +### version 10.0 vm (latest) + +2022/5/19 update: + +Now we add coroutine in this runtime: + +```javascript +var coroutine={ + create: func(function){return __builtin_cocreate;}, + resume: func(co) {return __builtin_coresume;}, + yield: func(args...) {return __builtin_coyield; }, + status: func(co) {return __builtin_costatus;}, + running:func() {return __builtin_corun; } +}; +``` + +`coroutine.create` is used to create a new coroutine object using a function. +But this coroutine will not run immediately. + +`coroutine.resume` is used to continue running a coroutine. + +`coroutine.yield` is used to interrupt the running of a coroutine and throw some values. +These values will be accepted and returned by `coroutine.resume`. +And `coroutine.yield` it self returns `vm_nil` in the coroutine function. + +`coroutine.status` is used to see the status of a coroutine. +There are 3 types of status:`suspended` means waiting for running,`running` means is running,`dead` means finished running. + +`coroutine.running` is used to judge if there is a coroutine running now. + +__CAUTION:__ coroutine should not be created or running inside another coroutine. + +__We will explain how resume and yield work here:__ + +When `op_callb` is called, the stack frame is like this: + +```C++ ++----------------------------+(main stack) +| old pc(vm_ret) | <- top[0] ++----------------------------+ +| old localr(vm_addr) | <- top[-1] ++----------------------------+ +| local scope(nasal_ref) | +| ... | ++----------------------------+ <- local pointer stored in localr +| old funcr(vm_func) | <- old function stored in funcr ++----------------------------+ +``` + +In `op_callb`'s progress, next step the stack frame is: + +```C++ ++----------------------------+(main stack) +| nil(vm_nil) | <- push nil ++----------------------------+ +| old pc(vm_ret) | ++----------------------------+ +| old localr(vm_addr) | ++----------------------------+ +| local scope(nasal_ref) | +| ... | ++----------------------------+ <- local pointer stored in localr +| old funcr(vm_func) | <- old function stored in funcr ++----------------------------+ +``` + +Then we call `resume`, this function will change stack. +As we can see, coroutine stack already has some values on it, +but if we first enter it, the stack top will be `vm_ret`, and the return `pc` is `0`. + +So for safe running, `resume` will return `gc.top[0]`. +`op_callb` will do `top[0]=resume()`, so the value does not change. + +```C++ ++----------------------------+(coroutine stack) +| pc:0(vm_ret) | <- now gc.top[0] ++----------------------------+ +``` + +When we call `yield`, the function will do like this. +And we find that `op_callb` has put the `nil` at the top. +but where is the returned `local[1]` sent? + +```C++ ++----------------------------+(coroutine stack) +| nil(vm_nil) | <- push nil ++----------------------------+ +| old pc(vm_ret) | ++----------------------------+ +| old localr(vm_addr) | ++----------------------------+ +| local scope(nasal_ref) | +| ... | ++----------------------------+ <- local pointer stored in localr +| old funcr(vm_func) | <- old function stored in funcr ++----------------------------+ +``` + +When `builtin_coyield` is finished, the stack is set to main stack, +and the returned `local[1]` in fact is set to the top of the main stack by `op_callb`: + +```C++ ++----------------------------+(main stack) +| return_value(nasal_ref) | ++----------------------------+ +| old pc(vm_ret) | ++----------------------------+ +| old localr(vm_addr) | ++----------------------------+ +| local scope(nasal_ref) | +| ... | ++----------------------------+ <- local pointer stored in localr +| old funcr(vm_func) | <- old function stored in funcr ++----------------------------+ +``` + +so the main progress feels the value on the top is the returned value of `resume`. +but in fact the `resume`'s returned value is set on coroutine stack. +so we conclude this: + +```C++ +resume (main->coroutine) return coroutine.top[0]. coroutine.top[0] = coroutine.top[0]; +yield (coroutine->main) return a vector. main.top[0] = vector; +``` + ## Benchmark ![benchmark](./pic/benchmark.png) diff --git a/lib.nas b/lib.nas index d367b58..f47c3f6 100644 --- a/lib.nas +++ b/lib.nas @@ -267,6 +267,27 @@ var assert=func(condition,message="assertion failed!"){ die(message); } +# settimer alows infinite loop running a function with a time interval +var settimer=func(f,interval,realtime=1){ + while(1){ + unix.sleep(interval); + f(); + } +} + +# get time stamp, this will return a timestamp object +var maketimestamp=func(){ + var t=0; + var millisec=func(){ + return __builtin_millisec; + } + return { + stamp:func(){t=millisec();}, + elapsedMSec:func(){return millisec()-t;}, + elapsedUSec:func(){return (millisec()-t)*1000;} + }; +} + # md5 var md5=func(str){ return __builtin_md5(str); @@ -466,4 +487,12 @@ var closure=func(function,level=1){ var compile=func(code,filename=""){ die("this runtime uses static code generator"); -} \ No newline at end of file +} + +var coroutine={ + create: func(function){return __builtin_cocreate;}, + resume: func(co) {return __builtin_coresume;}, + yield: func(args...) {return __builtin_coyield; }, + status: func(co) {return __builtin_costatus;}, + running:func() {return __builtin_corun; } +}; \ No newline at end of file diff --git a/nasal.h b/nasal.h index a564e5f..a84a197 100644 --- a/nasal.h +++ b/nasal.h @@ -1,7 +1,7 @@ #ifndef __NASAL_H__ #define __NASAL_H__ -#define __nasver "9.0" +#define __nasver "10.0" #include @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef _WIN32 #include diff --git a/nasal_builtin.h b/nasal_builtin.h index 7b9f3da..d5489fc 100644 --- a/nasal_builtin.h +++ b/nasal_builtin.h @@ -87,6 +87,12 @@ nas_native(builtin_dlcall); nas_native(builtin_platform); nas_native(builtin_gc); nas_native(builtin_md5); +nas_native(builtin_cocreate); +nas_native(builtin_coresume); +nas_native(builtin_coyield); +nas_native(builtin_costatus); +nas_native(builtin_corun); +nas_native(builtin_millisec); nasal_ref builtin_err(const char* func_name,const std::string& info) { @@ -180,6 +186,12 @@ struct {"__builtin_platform",builtin_platform}, {"__builtin_gc", builtin_gc }, {"__builtin_md5", builtin_md5 }, + {"__builtin_cocreate",builtin_cocreate}, + {"__builtin_coresume",builtin_coresume}, + {"__builtin_coyield", builtin_coyield }, + {"__builtin_costatus",builtin_costatus}, + {"__builtin_corun" ,builtin_corun }, + {"__builtin_millisec",builtin_millisec}, {nullptr, nullptr } }; @@ -200,6 +212,7 @@ nasal_ref builtin_print(nasal_ref* local,nasal_gc& gc) case vm_hash: i.hash().print(); break; case vm_func: std::cout<<"func(...){...}"; break; case vm_obj: std::cout<<""; break; + case vm_co: std::cout<<""; break; } std::cout<&,nasal_gc&); extern_func func=(extern_func)funcptr.obj().ptr; @@ -1321,4 +1335,86 @@ nasal_ref builtin_md5(nasal_ref* local,nasal_gc& gc) res.str()=md5(str.str()); return res; } + +nasal_ref builtin_cocreate(nasal_ref* local,nasal_gc& gc) +{ + // +-----------------+ + // | old pc | <- top[0] + // +-----------------+ + // | old localr | <- top[-1] + // +-----------------+ + // | local scope | + // | ... | + // +-----------------+ <- local pointer stored in localr + // | old funcr | <- old function stored in funcr + // +-----------------+ + nasal_ref func=local[1]; + if(func.type!=vm_func) + return builtin_err("coroutine::create","must use a function to create coroutine"); + if(gc.coroutine) + return builtin_err("coroutine::create","cannot create another coroutine in a coroutine"); + nasal_ref co=gc.alloc(vm_co); + nasal_co& coroutine=co.co(); + coroutine.pc=func.func().entry-1; + + coroutine.top[0]=nil; + coroutine.localr=coroutine.top+1; + coroutine.top=coroutine.localr+func.func().lsize; + coroutine.localr[0]=func.func().local[0]; + coroutine.top[0]={vm_addr,(nasal_ref*)nullptr}; + coroutine.top++; + coroutine.top[0]={vm_ret,(uint32_t)0}; + + coroutine.funcr=func; + coroutine.status=nasal_co::suspended; + + return co; +} +nasal_ref builtin_coresume(nasal_ref* local,nasal_gc& gc) +{ + if(gc.coroutine) + return builtin_err("coroutine::resume","cannot start another coroutine when one is running"); + nasal_ref co=local[1]; + if(co.type!=vm_co) + return builtin_err("coroutine::resume","must use a coroutine object"); + if(co.co().status==nasal_co::dead) + return nil; + gc.ctxchg(co.co()); + return gc.top[0]; +} +nasal_ref builtin_coyield(nasal_ref* local,nasal_gc& gc) +{ + if(!gc.coroutine) + return builtin_err("coroutine::yield","cannot yield, no coroutine is running"); + gc.ctxreserve(); + // this will set to main stack top + // then builtin_coresume will return it + return local[1]; +} +nasal_ref builtin_costatus(nasal_ref* local,nasal_gc& gc) +{ + nasal_ref co=local[1]; + if(co.type!=vm_co) + return builtin_err("coroutine::status","must use a coroutine object"); + nasal_ref res=gc.alloc(vm_str); + switch(co.co().status) + { + case nasal_co::suspended: res.str()="suspended";break; + case nasal_co::running: res.str()="running"; break; + case nasal_co::dead: res.str()="dead"; break; + } + return res; +} +nasal_ref builtin_corun(nasal_ref* local,nasal_gc& gc) +{ + if(gc.coroutine) + return one; + return zero; +} +nasal_ref builtin_millisec(nasal_ref* local,nasal_gc& gc) +{ + timeb now; + ftime(&now); + return {vm_num,(double)(now.time*1000+now.millitm)}; +} #endif \ No newline at end of file diff --git a/nasal_dbg.h b/nasal_dbg.h index c74e58e..a0e0703 100644 --- a/nasal_dbg.h +++ b/nasal_dbg.h @@ -94,7 +94,7 @@ void nasal_dbg::stepinfo() end=(1+(pc>>3))<<3; for(uint32_t i=begin;i\t":" \t",i); - stackinfo(5); + stackinfo(10); } void nasal_dbg::interact() @@ -135,8 +135,9 @@ void nasal_dbg::interact() printf("[%zu] %s\n",i,files[i].c_str()); else if(res[0]=="g" || res[0]=="global") global_state(); - else if(res[0]=="l" || res[0]=="local") + else if(res[0]=="l" || res[0]=="local"){ local_state(); + } else if(res[0]=="u" || res[0]=="upval") upval_state(); else if(res[0]=="a" || res[0]=="all") @@ -208,13 +209,13 @@ void nasal_dbg::run( goto *code[pc]; vmexit: - if(gc.top>=canary) + if(top>=canary) die("stack overflow"); gc.clear(); imm.clear(); printf("[debug] debugger exited\n"); return; -#define dbg(op) {interact();op();if(gc.top"; break; + case vm_co: std::cout<<""; break; } } bool nasal_ref::objchk(uint32_t objtype) @@ -387,6 +441,7 @@ inline nasal_hash& nasal_ref::hash (){return *value.gcobj->ptr.hash; } inline nasal_func& nasal_ref::func (){return *value.gcobj->ptr.func; } inline nasal_upval& nasal_ref::upval(){return *value.gcobj->ptr.upval;} inline nasal_obj& nasal_ref::obj (){return *value.gcobj->ptr.obj; } +inline nasal_co& nasal_ref::co (){return *value.gcobj->ptr.co; } const nasal_ref zero={vm_num,(double)0}; const nasal_ref one ={vm_num,(double)1}; @@ -395,9 +450,29 @@ const nasal_ref nil ={vm_nil,(double)0}; struct nasal_gc { static const uint32_t stack_depth=8192; // depth of value stack - nasal_ref funcr; // function register - nasal_ref stack[stack_depth]; // the last one is reserved to avoid stack overflow + struct + { + nasal_ref stack[stack_depth]; + uint32_t pc; + nasal_ref* localr; + nasal_ref* memr; + nasal_ref* canary; + nasal_ref funcr; + nasal_ref* top; + } main_ctx; + nasal_ref* global; // global values pointer(this should not be changed) + + /* runtime context */ + uint32_t pc; // program counter nasal_ref* top; // stack top + nasal_ref* localr; // local scope register + nasal_ref* memr; // used for mem_call + nasal_ref funcr; // function register + nasal_ref* canary; // avoid stackoverflow + nasal_ref* stack; // stack pointer + nasal_co* coroutine; // running coroutin + + /* constants and memory pool */ std::vector strs; // reserved address for const vm_str std::vector memory; // gc memory std::queue free_list[vm_type_size]; // gc free list @@ -407,6 +482,8 @@ struct nasal_gc /* if new functions generated in local scope */ /* they will share the same upvalue stored here */ std::vector upvalue; + + /* values for analysis */ uint64_t size[vm_type_size]; uint64_t count[vm_type_size]; void mark(); @@ -416,17 +493,30 @@ struct nasal_gc void info(); nasal_ref alloc(const uint8_t); nasal_ref builtin_alloc(const uint8_t); + void ctxchg(nasal_co&); + void ctxreserve(); }; /* gc functions */ void nasal_gc::mark() { std::queue bfs; - bfs.push(funcr); + for(auto& i:upvalue) bfs.push(i); - for(nasal_ref* i=stack;i<=top;++i) - bfs.push(*i); + if(!coroutine) + { + for(nasal_ref* i=stack;i<=top;++i) + bfs.push(*i); + bfs.push(funcr); + } + else + { + for(nasal_ref* i=main_ctx.stack;i<=main_ctx.top;++i) + bfs.push(*i); + bfs.push(main_ctx.funcr); + } + while(!bfs.empty()) { nasal_ref tmp=bfs.front(); @@ -452,6 +542,12 @@ void nasal_gc::mark() case vm_upval: for(auto& i:tmp.upval().elems) bfs.push(i); + break; + case vm_co: + bfs.push(tmp.co().funcr); + for(nasal_ref* i=tmp.co().stack;i<=tmp.co().top;++i) + bfs.push(*i); + break; } } } @@ -469,6 +565,7 @@ void nasal_gc::sweep() 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; } free_list[i->type].push(i); i->mark=GC_COLLECTED; @@ -491,7 +588,10 @@ void nasal_gc::init(const std::vector& s) memory.push_back(tmp); free_list[i].push(tmp); } - top=stack; + global=main_ctx.stack; + stack=main_ctx.stack; + top=main_ctx.stack; + coroutine=nullptr; // init constant strings strs.resize(s.size()); for(uint32_t i=0;istatus=nasal_co::running; +} +void nasal_gc::ctxreserve() +{ + if(coroutine->status!=nasal_co::dead) + coroutine->status=nasal_co::suspended; + // pc=0 means this coroutine is finished, so we use entry to reset it + coroutine->pc=pc==0?coroutine->funcr.func().entry:pc; + coroutine->top=top; + coroutine->localr=localr; + coroutine->memr=memr; + coroutine->funcr=funcr; + coroutine->canary=canary; + + pc=main_ctx.pc; + top=main_ctx.top; + localr=main_ctx.localr; + memr=main_ctx.memr; + funcr=main_ctx.funcr; + canary=main_ctx.canary; + stack=main_ctx.stack; + coroutine=nullptr; + + upvalue.pop_back(); +} #endif \ No newline at end of file diff --git a/nasal_vm.h b/nasal_vm.h index d5cd0d1..34d52aa 100644 --- a/nasal_vm.h +++ b/nasal_vm.h @@ -5,20 +5,23 @@ class nasal_vm { protected: /* values of nasal_vm */ - uint32_t pc; // program counter - nasal_ref* localr; // local scope register + uint32_t& pc; // program counter + nasal_ref*& global; // global scope register + nasal_ref*& localr; // local scope register + nasal_ref*& memr; // used for mem_call + nasal_ref& funcr; // function register + nasal_ref*& canary; // avoid stackoverflow + nasal_ref*& top; // stack top + /* constant */ const double* num_table;// const numbers, ref from nasal_codegen const std::string* str_table;// const symbols, ref from nasal_codegen std::vector imm; // immediate number - nasal_ref* mem_addr; // used for mem_call /* garbage collector */ nasal_gc gc; /* values used for debug */ size_t files_size; const std::string* files; // ref from nasal_import const opcode* bytecode; // ref from nasal_codegen - /* canary to avoid stackoverflow */ - nasal_ref* canary; void init( const std::vector&, @@ -115,6 +118,14 @@ protected: void opr_mcallh(); void opr_ret(); public: + nasal_vm(): + pc(gc.pc), + global(gc.global), + localr(gc.localr), + memr(gc.memr), + funcr(gc.funcr), + canary(gc.canary), + top(gc.top){} void run( const nasal_codegen&, const nasal_import&, @@ -136,9 +147,9 @@ void nasal_vm::init( files_size=filenames.size(); /* set canary and program counter */ canary=gc.stack+nasal_gc::stack_depth-1; // gc.stack[nasal_gc::stack_depth-1] - mem_addr=nullptr; - pc=0; + memr=nullptr; localr=nullptr; + pc=0; } void nasal_vm::valinfo(nasal_ref& val) { @@ -161,6 +172,7 @@ void nasal_vm::valinfo(nasal_ref& val) case vm_vec: printf("| vec | <0x" PRTHEX64 "> [%zu val]\n",(uint64_t)p,val.vec().size());break; case vm_hash: printf("| hash | <0x" PRTHEX64 "> {%zu val}\n",(uint64_t)p,val.hash().size());break; case vm_obj: printf("| obj | <0x" PRTHEX64 "> obj:0x" PRTHEX64 "\n",(uint64_t)p,(uint64_t)val.obj().ptr);break; + case vm_co: printf("| co | <0x" PRTHEX64 "> coroutine\n",(uint64_t)p);break; default: printf("| err | <0x" PRTHEX64 "> unknown object\n",(uint64_t)p);break; } } @@ -220,10 +232,10 @@ void nasal_vm::bytecodeinfo(const char* header,const uint32_t p) void nasal_vm::traceback() { uint32_t global_size=bytecode[0].num; // bytecode[0] is op_intg - nasal_ref* top=gc.top; + nasal_ref* t=top; nasal_ref* bottom=gc.stack+global_size; std::stack ret; - for(nasal_ref* i=bottom;i<=top;++i) + for(nasal_ref* i=bottom;i<=t;++i) if(i->type==vm_ret) ret.push(i->ret()); // push pc to ret stack to store the position program crashed @@ -248,38 +260,38 @@ void nasal_vm::traceback() void nasal_vm::stackinfo(const uint32_t limit=10) { /* bytecode[0] is op_intg, the .num is the global size */ - uint32_t gsize=bytecode[0].num; - nasal_ref* top=gc.top; + uint32_t gsize=gc.stack==gc.main_ctx.stack?bytecode[0].num:0; + nasal_ref* t=top; nasal_ref* bottom=gc.stack+gsize; printf("vm stack(0x" PRTHEX64 ", limit %u, total ",(uint64_t)bottom,gsize,limit); - if(top=bottom;++i,--top) + printf("" PRTINT64 "):\n",(int64_t)(t-bottom+1)); + for(uint32_t i=0;i=bottom;++i,--t) { - printf(" 0x" PRTHEX64_8 "",(uint64_t)(top-gc.stack)); - valinfo(top[0]); + printf(" 0x" PRTHEX64_8 "",(uint64_t)(t-gc.stack)); + valinfo(t[0]); } } void nasal_vm::global_state() { if(!bytecode[0].num || gc.stack[0].type==vm_none) // bytecode[0].op is op_intg return; - printf("global(0x" PRTHEX64 "):\n",(uint64_t)gc.stack); + printf("global(0x" PRTHEX64 "):\n",(uint64_t)global); for(uint32_t i=0;i):\n",(uint64_t)localr,(int64_t)(localr-gc.stack)); for(uint32_t i=0;i upval[%u]:\n",i); @@ -306,14 +318,14 @@ void nasal_vm::upval_state() } void nasal_vm::detail() { - printf("maddr:\n (0x" PRTHEX64 ")\n",(uint64_t)mem_addr); + printf("maddr:\n (0x" PRTHEX64 ")\n",(uint64_t)memr); printf("localr:\n (0x" PRTHEX64 ")\n",(uint64_t)localr); - if(gc.funcr.type==vm_nil) + if(funcr.type==vm_nil) printf("funcr:\n (nil)\n"); else printf("funcr:\n (<0x" PRTHEX64 "> entry:0x%x)\n", - (uint64_t)gc.funcr.value.gcobj, - gc.funcr.func().entry); + (uint64_t)funcr.value.gcobj, + funcr.func().entry); global_state(); local_state(); upval_state(); @@ -366,37 +378,37 @@ inline void nasal_vm::opr_intg() { // global values store on stack for(uint32_t i=0;i>16)&0xffff].upval()[imm[pc]&0xffff]=(gc.top--)[0]; + funcr.func().upvalue[(imm[pc]>>16)&0xffff].upval()[imm[pc]&0xffff]=(top--)[0]; } inline void nasal_vm::opr_pnum() { - (++gc.top)[0]={vm_num,num_table[imm[pc]]}; + (++top)[0]={vm_num,num_table[imm[pc]]}; } inline void nasal_vm::opr_pnil() { - (++gc.top)[0]=nil; + (++top)[0]=nil; } inline void nasal_vm::opr_pstr() { - (++gc.top)[0]=gc.strs[imm[pc]]; + (++top)[0]=gc.strs[imm[pc]]; } inline void nasal_vm::opr_newv() { @@ -404,28 +416,28 @@ inline void nasal_vm::opr_newv() auto& vec=newv.vec().elems; vec.resize(imm[pc]); // use top-=imm[pc]-1 here will cause error if imm[pc] is 0 - gc.top=gc.top-imm[pc]+1; + top=top-imm[pc]+1; for(uint32_t i=0;isize has 1 place reserved for "me" func.local[func.psize++]={vm_none}; } inline void nasal_vm::opr_defpara() { - nasal_ref val=gc.top[0]; - nasal_func& func=(--gc.top)[0].func(); + nasal_ref val=top[0]; + nasal_func& func=(--top)[0].func(); func.keys[str_table[imm[pc]]]=func.psize;// func->size has 1 place reserved for "me" func.local[func.psize++]=val; } inline void nasal_vm::opr_dynpara() { - gc.top[0].func().dynpara=imm[pc]; + top[0].func().dynpara=imm[pc]; } inline void nasal_vm::opr_unot() { - nasal_ref val=gc.top[0]; + nasal_ref val=top[0]; switch(val.type) { - case vm_nil:gc.top[0]=one;break; - case vm_num:gc.top[0]=val.num()?zero:one;break; + case vm_nil:top[0]=one;break; + case vm_num:top[0]=val.num()?zero:one;break; case vm_str: { double num=str2num(val.str().c_str()); if(std::isnan(num)) - gc.top[0]={vm_num,(double)val.str().empty()}; + top[0]={vm_num,(double)val.str().empty()}; else - gc.top[0]=num?zero:one; + top[0]=num?zero:one; } break; default:die("unot: incorrect value type");break; @@ -474,12 +486,12 @@ inline void nasal_vm::opr_unot() } inline void nasal_vm::opr_usub() { - gc.top[0]={vm_num,-gc.top[0].to_number()}; + top[0]={vm_num,-top[0].to_number()}; } #define op_calc(type)\ - nasal_ref val(vm_num,gc.top[-1].to_number() type gc.top[0].to_number());\ - (--gc.top)[0]=val; + nasal_ref val(vm_num,top[-1].to_number() type top[0].to_number());\ + (--top)[0]=val; inline void nasal_vm::opr_add(){op_calc(+);} inline void nasal_vm::opr_sub(){op_calc(-);} @@ -488,13 +500,13 @@ inline void nasal_vm::opr_div(){op_calc(/);} inline void nasal_vm::opr_lnk() { nasal_ref val=gc.alloc(vm_str); - val.str()=gc.top[-1].to_string()+gc.top[0].to_string(); - (--gc.top)[0]=val; + val.str()=top[-1].to_string()+top[0].to_string(); + (--top)[0]=val; } #define op_calc_const(type)\ - nasal_ref val(vm_num,gc.top[0].to_number() type num_table[imm[pc]]);\ - gc.top[0]=val; + nasal_ref val(vm_num,top[0].to_number() type num_table[imm[pc]]);\ + top[0]=val; inline void nasal_vm::opr_addc(){op_calc_const(+);} inline void nasal_vm::opr_subc(){op_calc_const(-);} @@ -503,15 +515,15 @@ inline void nasal_vm::opr_divc(){op_calc_const(/);} inline void nasal_vm::opr_lnkc() { nasal_ref val=gc.alloc(vm_str); - val.str()=gc.top[0].to_string()+str_table[imm[pc]]; - gc.top[0]=val; + val.str()=top[0].to_string()+str_table[imm[pc]]; + top[0]=val; } #define op_calc_eq(type)\ - nasal_ref val(vm_num,mem_addr[0].to_number() type gc.top[-1].to_number());\ - (--gc.top)[0]=mem_addr[0]=val;\ - mem_addr=nullptr;\ - gc.top-=imm[pc]; + nasal_ref val(vm_num,memr[0].to_number() type top[-1].to_number());\ + (--top)[0]=memr[0]=val;\ + memr=nullptr;\ + top-=imm[pc]; inline void nasal_vm::opr_addeq(){op_calc_eq(+);} inline void nasal_vm::opr_subeq(){op_calc_eq(-);} @@ -520,17 +532,17 @@ inline void nasal_vm::opr_diveq(){op_calc_eq(/);} inline void nasal_vm::opr_lnkeq() { nasal_ref val=gc.alloc(vm_str); - val.str()=mem_addr[0].to_string()+gc.top[-1].to_string(); - (--gc.top)[0]=mem_addr[0]=val; - mem_addr=nullptr; - gc.top-=imm[pc]; + val.str()=memr[0].to_string()+top[-1].to_string(); + (--top)[0]=memr[0]=val; + memr=nullptr; + top-=imm[pc]; } #define op_calc_eq_const(type)\ - nasal_ref val(vm_num,mem_addr[0].to_number() type num_table[imm[pc]&0x7fffffff]);\ - gc.top[0]=mem_addr[0]=val;\ - mem_addr=nullptr;\ - gc.top-=(imm[pc]>>31); + nasal_ref val(vm_num,memr[0].to_number() type num_table[imm[pc]&0x7fffffff]);\ + top[0]=memr[0]=val;\ + memr=nullptr;\ + top-=(imm[pc]>>31); inline void nasal_vm::opr_addeqc(){op_calc_eq_const(+);} inline void nasal_vm::opr_subeqc(){op_calc_eq_const(-);} @@ -539,55 +551,55 @@ inline void nasal_vm::opr_diveqc(){op_calc_eq_const(/);} inline void nasal_vm::opr_lnkeqc() { nasal_ref val=gc.alloc(vm_str); - val.str()=mem_addr[0].to_string()+str_table[imm[pc]&0x7fffffff]; - gc.top[0]=mem_addr[0]=val; - mem_addr=nullptr; - gc.top-=(imm[pc]>>31); + val.str()=memr[0].to_string()+str_table[imm[pc]&0x7fffffff]; + top[0]=memr[0]=val; + memr=nullptr; + top-=(imm[pc]>>31); } inline void nasal_vm::opr_meq() { - // pop old mem_addr[0] and replace it - // the reason why we should get mem_addr and push the old value on stack + // pop old memr[0] and replace it + // the reason why we should get memr and push the old value on stack // is that when lnkeq/lnkeqc is called, there will be // a new gc object vm_str which is returned by gc::alloc // this may cause gc, so we should temporarily put it on stack - mem_addr[0]=(--gc.top)[0]; - mem_addr=nullptr; - gc.top-=imm[pc]; + memr[0]=(--top)[0]; + memr=nullptr; + top-=imm[pc]; } inline void nasal_vm::opr_eq() { - nasal_ref val2=gc.top[0]; - nasal_ref val1=(--gc.top)[0]; + nasal_ref val2=top[0]; + nasal_ref val1=(--top)[0]; if(val1.type==vm_nil && val2.type==vm_nil) - gc.top[0]=one; + top[0]=one; else if(val1.type==vm_str && val2.type==vm_str) - gc.top[0]=(val1.str()==val2.str())?one:zero; + top[0]=(val1.str()==val2.str())?one:zero; else if((val1.type==vm_num || val2.type==vm_num) && val1.type!=vm_nil && val2.type!=vm_nil) - gc.top[0]=(val1.to_number()==val2.to_number())?one:zero; + top[0]=(val1.to_number()==val2.to_number())?one:zero; else - gc.top[0]=(val1==val2)?one:zero; + top[0]=(val1==val2)?one:zero; } inline void nasal_vm::opr_neq() { - nasal_ref val2=gc.top[0]; - nasal_ref val1=(--gc.top)[0]; + nasal_ref val2=top[0]; + nasal_ref val1=(--top)[0]; if(val1.type==vm_nil && val2.type==vm_nil) - gc.top[0]=zero; + top[0]=zero; else if(val1.type==vm_str && val2.type==vm_str) - gc.top[0]=(val1.str()!=val2.str())?one:zero; + top[0]=(val1.str()!=val2.str())?one:zero; else if((val1.type==vm_num || val2.type==vm_num) && val1.type!=vm_nil && val2.type!=vm_nil) - gc.top[0]=(val1.to_number()!=val2.to_number())?one:zero; + top[0]=(val1.to_number()!=val2.to_number())?one:zero; else - gc.top[0]=(val1!=val2)?one:zero; + top[0]=(val1!=val2)?one:zero; } #define op_cmp(type)\ - --gc.top;\ - gc.top[0]=(gc.top[0].to_number() type gc.top[1].to_number())?one:zero; + --top;\ + top[0]=(top[0].to_number() type top[1].to_number())?one:zero; inline void nasal_vm::opr_less(){op_cmp(<);} inline void nasal_vm::opr_leq(){op_cmp(<=);} @@ -595,7 +607,7 @@ inline void nasal_vm::opr_grt(){op_cmp(>);} inline void nasal_vm::opr_geq(){op_cmp(>=);} #define op_cmp_const(type)\ - gc.top[0]=(gc.top[0].to_number() type num_table[imm[pc]])?one:zero; + top[0]=(top[0].to_number() type num_table[imm[pc]])?one:zero; inline void nasal_vm::opr_lessc(){op_cmp_const(<);} inline void nasal_vm::opr_leqc(){op_cmp_const(<=);} @@ -604,7 +616,7 @@ inline void nasal_vm::opr_geqc(){op_cmp_const(>=);} inline void nasal_vm::opr_pop() { - --gc.top; + --top; } inline void nasal_vm::opr_jmp() { @@ -612,73 +624,73 @@ inline void nasal_vm::opr_jmp() } inline void nasal_vm::opr_jt() { - if(condition(gc.top[0])) + if(condition(top[0])) pc=imm[pc]-1; } inline void nasal_vm::opr_jf() { - if(!condition(gc.top[0])) + if(!condition(top[0])) pc=imm[pc]-1; - --gc.top; + --top; } inline void nasal_vm::opr_counter() { - (++gc.top)[0]={vm_cnt,(int64_t)-1}; - if(gc.top[-1].type!=vm_vec) + (++top)[0]={vm_cnt,(int64_t)-1}; + if(top[-1].type!=vm_vec) die("cnt: must use vector in forindex/foreach"); } inline void nasal_vm::opr_findex() { - if((size_t)(++gc.top[0].cnt())>=gc.top[-1].vec().size()) + if((size_t)(++top[0].cnt())>=top[-1].vec().size()) { pc=imm[pc]-1; return; } - gc.top[1]={vm_num,(double)gc.top[0].cnt()}; - ++gc.top; + top[1]={vm_num,(double)top[0].cnt()}; + ++top; } inline void nasal_vm::opr_feach() { - std::vector& ref=gc.top[-1].vec().elems; - if((size_t)(++gc.top[0].cnt())>=ref.size()) + std::vector& ref=top[-1].vec().elems; + if((size_t)(++top[0].cnt())>=ref.size()) { pc=imm[pc]-1; return; } - gc.top[1]=ref[gc.top[0].cnt()]; - ++gc.top; + top[1]=ref[top[0].cnt()]; + ++top; } inline void nasal_vm::opr_callg() { - (++gc.top)[0]=gc.stack[imm[pc]]; + (++top)[0]=global[imm[pc]]; } inline void nasal_vm::opr_calll() { - (++gc.top)[0]=localr[imm[pc]]; + (++top)[0]=localr[imm[pc]]; } inline void nasal_vm::opr_upval() { - (++gc.top)[0]=gc.funcr.func().upvalue[(imm[pc]>>16)&0xffff].upval()[imm[pc]&0xffff]; + (++top)[0]=funcr.func().upvalue[(imm[pc]>>16)&0xffff].upval()[imm[pc]&0xffff]; } inline void nasal_vm::opr_callv() { - nasal_ref val=gc.top[0]; - nasal_ref vec=(--gc.top)[0]; + nasal_ref val=top[0]; + nasal_ref vec=(--top)[0]; if(vec.type==vm_vec) { - gc.top[0]=vec.vec().get_val(val.to_number()); - if(gc.top[0].type==vm_none) + top[0]=vec.vec().get_val(val.to_number()); + if(top[0].type==vm_none) die("callv: index out of range:"+std::to_string(val.to_number())); } else if(vec.type==vm_hash) { if(val.type!=vm_str) die("callv: must use string as the key"); - gc.top[0]=vec.hash().get_val(val.str()); - if(gc.top[0].type==vm_none) + top[0]=vec.hash().get_val(val.str()); + if(top[0].type==vm_none) die("callv: cannot find member \""+val.str()+"\" of this hash"); - if(gc.top[0].type==vm_func) - gc.top[0].func().local[0]=val;// 'me' + if(top[0].type==vm_func) + top[0].func().local[0]=val;// 'me' } else if(vec.type==vm_str) { @@ -687,47 +699,47 @@ inline void nasal_vm::opr_callv() int str_size=str.length(); if(num<-str_size || num>=str_size) die("callv: index out of range:"+std::to_string(val.to_number())); - gc.top[0]={vm_num,double((uint8_t)str[num>=0? num:num+str_size])}; + top[0]={vm_num,double((uint8_t)str[num>=0? num:num+str_size])}; } else die("callv: must call a vector/hash/string"); } inline void nasal_vm::opr_callvi() { - nasal_ref val=gc.top[0]; + nasal_ref val=top[0]; if(val.type!=vm_vec) die("callvi: must use a vector"); // cannot use operator[],because this may cause overflow - (++gc.top)[0]=val.vec().get_val(imm[pc]); - if(gc.top[0].type==vm_none) + (++top)[0]=val.vec().get_val(imm[pc]); + if(top[0].type==vm_none) die("callvi: index out of range:"+std::to_string(imm[pc])); } inline void nasal_vm::opr_callh() { - nasal_ref val=gc.top[0]; + nasal_ref val=top[0]; if(val.type!=vm_hash) die("callh: must call a hash"); - gc.top[0]=val.hash().get_val(str_table[imm[pc]]); - if(gc.top[0].type==vm_none) + top[0]=val.hash().get_val(str_table[imm[pc]]); + if(top[0].type==vm_none) die("callh: member \""+str_table[imm[pc]]+"\" does not exist"); - if(gc.top[0].type==vm_func) - gc.top[0].func().local[0]=val;// 'me' + if(top[0].type==vm_func) + top[0].func().local[0]=val;// 'me' } inline void nasal_vm::opr_callfv() { - uint32_t argc=imm[pc]; // arguments counter - nasal_ref* local=gc.top-argc+1;// arguments begin address + uint32_t argc=imm[pc]; // arguments counter + nasal_ref* local=top-argc+1;// arguments begin address if(local[-1].type!=vm_func) die("callfv: must call a function"); auto& func=local[-1].func(); nasal_ref tmp=local[-1]; - local[-1]=gc.funcr; - gc.funcr=tmp; - if(gc.top-argc+func.lsize+2>=canary) // gc.top-argc+lsize(local) +1(old pc) +1(old localr) + local[-1]=funcr; + funcr=tmp; + if(top-argc+func.lsize+2>=canary) // top-argc+lsize(local) +1(old pc) +1(old localr) die("stack overflow"); uint32_t psize=func.psize-1; // parameter size is func->psize-1, 1 is reserved for "me" @@ -735,7 +747,7 @@ inline void nasal_vm::opr_callfv() die("callfv: lack argument(s)"); nasal_ref dynamic=nil; - gc.top=local+func.lsize; + top=local+func.lsize; if(func.dynpara>=0)// load dynamic arguments { dynamic=gc.alloc(vm_vec); @@ -751,29 +763,29 @@ inline void nasal_vm::opr_callfv() if(func.dynpara>=0) local[psize+1]=dynamic; - gc.top[0]={vm_addr,localr}; - (++gc.top)[0]={vm_ret,pc}; + top[0]={vm_addr,localr}; + (++top)[0]={vm_ret,pc}; pc=func.entry-1; localr=local; gc.upvalue.push_back(nil); } inline void nasal_vm::opr_callfh() { - auto& hash=gc.top[0].hash().elems; - if(gc.top[-1].type!=vm_func) + auto& hash=top[0].hash().elems; + if(top[-1].type!=vm_func) die("callfh: must call a function"); - auto& func=gc.top[-1].func(); - nasal_ref tmp=gc.top[-1]; - gc.top[-1]=gc.funcr; - gc.funcr=tmp; - if(gc.top+func.lsize+1>=canary) // gc.top -1(hash) +lsize(local) +1(old pc) +1(old localr) + auto& func=top[-1].func(); + nasal_ref tmp=top[-1]; + top[-1]=funcr; + funcr=tmp; + if(top+func.lsize+1>=canary) // top -1(hash) +lsize(local) +1(old pc) +1(old localr) die("stack overflow"); if(func.dynpara>=0) die("callfh: special call cannot use dynamic argument"); - nasal_ref* local=gc.top; - gc.top+=func.lsize; + nasal_ref* local=top; + top+=func.lsize; for(uint32_t i=0;i& ref=gc.top[-1].vec().elems; - std::vector& aim=gc.top[0].vec().elems; + nasal_ref val2=(top--)[0]; + nasal_ref val1=(top--)[0]; + std::vector& ref=top[-1].vec().elems; + std::vector& aim=top[0].vec().elems; uint8_t type1=val1.type,type2=val2.type; int num1=val1.to_number(); @@ -854,33 +871,33 @@ inline void nasal_vm::opr_slc2() } inline void nasal_vm::opr_mcallg() { - mem_addr=gc.stack+imm[pc]; - (++gc.top)[0]=mem_addr[0]; + memr=global+imm[pc]; + (++top)[0]=memr[0]; // push value in this memory space on stack // to avoid being garbage collected } inline void nasal_vm::opr_mcalll() { - mem_addr=localr+imm[pc]; - (++gc.top)[0]=mem_addr[0]; + memr=localr+imm[pc]; + (++top)[0]=memr[0]; // push value in this memory space on stack // to avoid being garbage collected } inline void nasal_vm::opr_mupval() { - mem_addr=&(gc.funcr.func().upvalue[(imm[pc]>>16)&0xffff].upval()[imm[pc]&0xffff]); - (++gc.top)[0]=mem_addr[0]; + memr=&(funcr.func().upvalue[(imm[pc]>>16)&0xffff].upval()[imm[pc]&0xffff]); + (++top)[0]=memr[0]; // push value in this memory space on stack // to avoid being garbage collected } inline void nasal_vm::opr_mcallv() { - nasal_ref val=gc.top[0]; - nasal_ref vec=(--gc.top)[0]; + nasal_ref val=top[0]; + nasal_ref vec=(--top)[0]; if(vec.type==vm_vec) { - mem_addr=vec.vec().get_mem(val.to_number()); - if(!mem_addr) + memr=vec.vec().get_mem(val.to_number()); + if(!memr) die("mcallv: index out of range:"+std::to_string(val.to_number())); } else if(vec.type==vm_hash) @@ -889,11 +906,11 @@ inline void nasal_vm::opr_mcallv() die("mcallv: must use string as the key"); nasal_hash& ref=vec.hash(); std::string& str=val.str(); - mem_addr=ref.get_mem(str); - if(!mem_addr) + memr=ref.get_mem(str); + if(!memr) { ref.elems[str]=nil; - mem_addr=ref.get_mem(str); + memr=ref.get_mem(str); } } else @@ -901,44 +918,44 @@ inline void nasal_vm::opr_mcallv() } inline void nasal_vm::opr_mcallh() { - nasal_ref hash=gc.top[0]; + nasal_ref hash=top[0]; if(hash.type!=vm_hash) die("mcallh: must call a hash"); nasal_hash& ref=hash.hash(); const std::string& str=str_table[imm[pc]]; - mem_addr=ref.get_mem(str); - if(!mem_addr) // create a new key + memr=ref.get_mem(str); + if(!memr) // create a new key { ref.elems[str]=nil; - mem_addr=ref.get_mem(str); + memr=ref.get_mem(str); } } inline void nasal_vm::opr_ret() { // +-----------------+ - // | return value | <- gc.top[0] + // | return value | <- top[0] // +-----------------+ - // | old pc | <- gc.top[-1] + // | old pc | <- top[-1] // +-----------------+ - // | old localr | <- gc.top[-2] + // | old localr | <- top[-2] // +-----------------+ // | local scope | // | ... | // +-----------------+ <- local pointer stored in localr - // | old funcr | <- old function stored in gc.funcr + // | old funcr | <- old function stored in funcr // +-----------------+ - nasal_ref ret=gc.top[0]; + nasal_ref ret=top[0]; nasal_ref* local=localr; - nasal_ref func=gc.funcr; + nasal_ref func=funcr; - pc=gc.top[-1].ret(); - localr=gc.top[-2].addr(); + pc=top[-1].ret(); + localr=top[-2].addr(); - gc.top=local-1; + top=local-1; func.func().local[0]=nil;// get func and set 'me' to nil - gc.funcr=gc.top[0]; + funcr=top[0]; - gc.top[0]=ret; // rewrite func with returned value + top[0]=ret; // rewrite func with returned value if(gc.upvalue.back().type==vm_upval) // synchronize upvalue { @@ -948,6 +965,12 @@ inline void nasal_vm::opr_ret() for(uint32_t i=0;istatus=nasal_co::dead; + gc.ctxreserve(); + return; + } gc.upvalue.pop_back(); } void nasal_vm::run( @@ -991,7 +1014,7 @@ void nasal_vm::run( goto *code[pc]; vmexit: - if(gc.top>=canary) + if(top>=canary) die("stack overflow"); if(opcnt) opcallsort(count); @@ -1001,7 +1024,7 @@ vmexit: imm.clear(); return; // may cause stackoverflow -#define exec_operand(op,num) {op();++count[num];if(gc.top"){ die("this runtime uses static code generator"); -} \ No newline at end of file +} + +var coroutine={ + create: func(function){return __builtin_cocreate;}, + resume: func(co) {return __builtin_coresume;}, + yield: func(args...) {return __builtin_coyield; }, + status: func(co) {return __builtin_costatus;}, + running:func() {return __builtin_corun; } +}; \ No newline at end of file diff --git a/test/calc.nas b/test/calc.nas index e01e74b..4156d4b 100644 --- a/test/calc.nas +++ b/test/calc.nas @@ -37,6 +37,7 @@ var testfile=[ "test/calc.nas ", "test/choice.nas ", "test/class.nas ", + "test/coroutine.nas ", "test/diff.nas ", "test/exception.nas ", "test/fib.nas ", diff --git a/test/coroutine.nas b/test/coroutine.nas new file mode 100644 index 0000000..5a39ca3 --- /dev/null +++ b/test/coroutine.nas @@ -0,0 +1,25 @@ +# coroutine.nas by ValKmjolnir +# 2022/5/19 +var fib=func(){ + var (a,b)=(1,1); + if(coroutine.running()){ + coroutine.yield(a); + coroutine.yield(b); + } + while(1){ + (a,b)=(b,a+b); + if(coroutine.running()) + coroutine.yield(b); + else + break; + } + return; +} + +var co=[coroutine.create(fib),coroutine.create(fib)]; +for(var i=0;i<45;i+=1){ + var res=[coroutine.resume(co[0]),coroutine.resume(co[1])]; + println('coroutine[0]:',res[0]==nil?nil:res[0][0],'\ncoroutine[1]:',res[1]==nil?nil:res[1][0]); +} + +fib(); \ No newline at end of file diff --git a/test/md5compare.nas b/test/md5compare.nas index d6dcc43..9d69efa 100644 --- a/test/md5compare.nas +++ b/test/md5compare.nas @@ -68,6 +68,7 @@ var filechecksum=func(){ "./test/calc.nas ", "./test/choice.nas ", "./test/class.nas ", + "./test/coroutine.nas ", "./test/diff.nas ", "./test/exception.nas ", "./test/fib.nas ",