1035 lines
30 KiB
C++
1035 lines
30 KiB
C++
#pragma once
|
|
|
|
#include <iomanip>
|
|
#include <stack>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
|
|
#include "nasal_import.h"
|
|
#include "nasal_gc.h"
|
|
#include "nasal_codegen.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning (disable:4244)
|
|
#pragma warning (disable:4267)
|
|
#pragma warning (disable:4102)
|
|
#endif
|
|
|
|
namespace nasal {
|
|
|
|
class vm {
|
|
protected:
|
|
|
|
/* registers of vm */
|
|
context ctx;
|
|
|
|
/* constants */
|
|
const f64* const_number = nullptr; // constant numbers
|
|
const std::string* const_string = nullptr; // constant symbols and strings
|
|
std::vector<u32> imm; // immediate number table
|
|
std::vector<nasal_builtin_table> native_function;
|
|
|
|
/* garbage collector */
|
|
gc ngc;
|
|
|
|
/* main stack */
|
|
var* global = nullptr;
|
|
usize global_size = 0;
|
|
|
|
/* values used for debugger */
|
|
const std::string* files = nullptr; // file name list
|
|
const opcode* bytecode = nullptr; // bytecode buffer address
|
|
|
|
/* variables for repl mode */
|
|
bool is_repl_mode = false;
|
|
bool first_exec_flag = true;
|
|
bool allow_repl_output = false;
|
|
|
|
/* vm initializing function */
|
|
void init(
|
|
const std::vector<std::string>&,
|
|
const std::vector<f64>&,
|
|
const std::vector<nasal_builtin_table>&,
|
|
const std::vector<opcode>&,
|
|
const std::unordered_map<std::string, i32>&,
|
|
const std::vector<std::string>&,
|
|
const std::vector<std::string>&
|
|
);
|
|
void context_and_global_init();
|
|
|
|
/* debug functions */
|
|
bool verbose = false;
|
|
void value_info(var&);
|
|
void function_detail_info(const nas_func&);
|
|
void function_call_trace();
|
|
void trace_back();
|
|
void stack_info(const u32);
|
|
void register_info();
|
|
void global_state();
|
|
void local_state();
|
|
void upvalue_state();
|
|
void all_state_detail();
|
|
std::string report_lack_arguments(u32, const nas_func&) const;
|
|
std::string report_special_call_lack_arguments(var*, const nas_func&) const;
|
|
std::string report_key_not_found(const std::string&, const nas_hash&) const;
|
|
std::string report_out_of_range(f64, usize) const;
|
|
std::string type_name_string(const var&) const;
|
|
void die(const std::string&);
|
|
|
|
/* vm calculation functions*/
|
|
inline bool cond(var&);
|
|
|
|
/* vm operands */
|
|
inline void o_repl();
|
|
inline void o_intl();
|
|
inline void o_loadg();
|
|
inline void o_loadl();
|
|
inline void o_loadu();
|
|
inline void o_pnum();
|
|
inline void o_pnil();
|
|
inline void o_pstr();
|
|
inline void o_newv();
|
|
inline void o_newh();
|
|
inline void o_newf();
|
|
inline void o_happ();
|
|
inline void o_para();
|
|
inline void o_deft();
|
|
inline void o_dyn();
|
|
inline void o_lnot();
|
|
inline void o_usub();
|
|
inline void o_bnot();
|
|
inline void o_btor();
|
|
inline void o_btxor();
|
|
inline void o_btand();
|
|
inline void o_add();
|
|
inline void o_sub();
|
|
inline void o_mul();
|
|
inline void o_div();
|
|
inline void o_lnk();
|
|
inline void o_addc();
|
|
inline void o_subc();
|
|
inline void o_mulc();
|
|
inline void o_divc();
|
|
inline void o_lnkc();
|
|
inline void o_addeq();
|
|
inline void o_subeq();
|
|
inline void o_muleq();
|
|
inline void o_diveq();
|
|
inline void o_lnkeq();
|
|
inline void o_bandeq();
|
|
inline void o_boreq();
|
|
inline void o_bxoreq();
|
|
inline void o_addeqc();
|
|
inline void o_subeqc();
|
|
inline void o_muleqc();
|
|
inline void o_diveqc();
|
|
inline void o_lnkeqc();
|
|
inline void o_addecp();
|
|
inline void o_subecp();
|
|
inline void o_mulecp();
|
|
inline void o_divecp();
|
|
inline void o_lnkecp();
|
|
inline void o_meq();
|
|
inline void o_eq();
|
|
inline void o_neq();
|
|
inline void o_less();
|
|
inline void o_leq();
|
|
inline void o_grt();
|
|
inline void o_geq();
|
|
inline void o_lessc();
|
|
inline void o_leqc();
|
|
inline void o_grtc();
|
|
inline void o_geqc();
|
|
inline void o_pop();
|
|
inline void o_jmp();
|
|
inline void o_jt();
|
|
inline void o_jf();
|
|
inline void o_cnt();
|
|
inline void o_findex();
|
|
inline void o_feach();
|
|
inline void o_callg();
|
|
inline void o_calll();
|
|
inline void o_upval();
|
|
inline void o_callv();
|
|
inline void o_callvi();
|
|
inline void o_callh();
|
|
inline void o_callfv();
|
|
inline void o_callfh();
|
|
inline void o_callb();
|
|
inline void o_slcbeg();
|
|
inline void o_slcend();
|
|
inline void o_slc();
|
|
inline void o_slc2();
|
|
inline void o_mcallg();
|
|
inline void o_mcalll();
|
|
inline void o_mupval();
|
|
inline void o_mcallv();
|
|
inline void o_mcallh();
|
|
inline void o_ret();
|
|
|
|
public:
|
|
|
|
/* constructor of vm instance */
|
|
vm() {
|
|
ctx.stack = new var[STACK_DEPTH];
|
|
global = new var[STACK_DEPTH];
|
|
}
|
|
~vm() {
|
|
delete[] ctx.stack;
|
|
delete[] global;
|
|
}
|
|
|
|
/* execution entry */
|
|
void run(
|
|
const codegen&,
|
|
const linker&,
|
|
const std::vector<std::string>&
|
|
);
|
|
|
|
/* set detail report info flag */
|
|
void set_detail_report_info(bool flag) {verbose = flag;}
|
|
/* set repl mode flag */
|
|
void set_repl_mode_flag(bool flag) {is_repl_mode = flag;}
|
|
/* set repl output flag */
|
|
void set_allow_repl_output_flag(bool flag) {allow_repl_output = flag;}
|
|
};
|
|
|
|
inline bool vm::cond(var& val) {
|
|
if (val.type==vm_type::vm_num) {
|
|
return val.num();
|
|
} else if (val.type==vm_type::vm_str) {
|
|
const f64 num = str2num(val.str().c_str());
|
|
return std::isnan(num)? !val.str().empty():num;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline void vm::o_repl() {
|
|
// reserved for repl mode stack top value output
|
|
if (allow_repl_output) {
|
|
std::cout << ctx.top[0] << "\n";
|
|
}
|
|
}
|
|
|
|
inline void vm::o_intl() {
|
|
ctx.top[0].func().local.resize(imm[ctx.pc], nil);
|
|
ctx.top[0].func().local_size = imm[ctx.pc];
|
|
}
|
|
|
|
inline void vm::o_loadg() {
|
|
global[imm[ctx.pc]] = (ctx.top--)[0];
|
|
}
|
|
|
|
inline void vm::o_loadl() {
|
|
ctx.localr[imm[ctx.pc]] = (ctx.top--)[0];
|
|
}
|
|
|
|
inline void vm::o_loadu() {
|
|
ctx.funcr.func().upval[(imm[ctx.pc]>>16)&0xffff]
|
|
.upval()[imm[ctx.pc]&0xffff] = (ctx.top--)[0];
|
|
}
|
|
|
|
inline void vm::o_pnum() {
|
|
(++ctx.top)[0] = var::num(const_number[imm[ctx.pc]]);
|
|
}
|
|
|
|
inline void vm::o_pnil() {
|
|
(++ctx.top)[0] = nil;
|
|
}
|
|
|
|
inline void vm::o_pstr() {
|
|
(++ctx.top)[0] = ngc.strs[imm[ctx.pc]];
|
|
}
|
|
|
|
inline void vm::o_newv() {
|
|
var newv = ngc.alloc(vm_type::vm_vec);
|
|
auto& vec = newv.vec().elems;
|
|
vec.resize(imm[ctx.pc]);
|
|
// use top-=imm[pc]-1 here will cause error if imm[pc] is 0
|
|
ctx.top = ctx.top-imm[ctx.pc]+1;
|
|
for(u32 i = 0; i<imm[ctx.pc]; ++i) {
|
|
vec[i] = ctx.top[i];
|
|
}
|
|
ctx.top[0] = newv;
|
|
}
|
|
|
|
inline void vm::o_newh() {
|
|
(++ctx.top)[0] = ngc.alloc(vm_type::vm_hash);
|
|
}
|
|
|
|
inline void vm::o_newf() {
|
|
(++ctx.top)[0] = ngc.alloc(vm_type::vm_func);
|
|
auto& func = ctx.top[0].func();
|
|
func.entry = imm[ctx.pc];
|
|
func.parameter_size = 1;
|
|
|
|
/* this means you create a new function in local scope */
|
|
if (ctx.localr) {
|
|
func.upval = ctx.funcr.func().upval;
|
|
// function created in the same local scope shares one closure
|
|
// so this size & stk setting has no problem
|
|
var upval = (ctx.upvalr.type==vm_type::vm_nil)?
|
|
ngc.alloc(vm_type::vm_upval):
|
|
ctx.upvalr;
|
|
upval.upval().size = ctx.funcr.func().local_size;
|
|
upval.upval().stack_frame_offset = ctx.localr;
|
|
func.upval.push_back(upval);
|
|
ctx.upvalr = upval;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_happ() {
|
|
ctx.top[-1].hash().elems[const_string[imm[ctx.pc]]] = ctx.top[0];
|
|
--ctx.top;
|
|
}
|
|
|
|
inline void vm::o_para() {
|
|
auto& func = ctx.top[0].func();
|
|
// func->size has 1 place reserved for "me"
|
|
func.keys[const_string[imm[ctx.pc]]] = func.parameter_size;
|
|
func.local[func.parameter_size++] = var::none();
|
|
}
|
|
|
|
inline void vm::o_deft() {
|
|
var val = ctx.top[0];
|
|
auto& func = (--ctx.top)[0].func();
|
|
// func->size has 1 place reserved for "me"
|
|
func.keys[const_string[imm[ctx.pc]]] = func.parameter_size;
|
|
func.local[func.parameter_size++] = val;
|
|
}
|
|
|
|
inline void vm::o_dyn() {
|
|
ctx.top[0].func().dynamic_parameter_index = imm[ctx.pc];
|
|
}
|
|
|
|
inline void vm::o_lnot() {
|
|
var val = ctx.top[0];
|
|
switch(val.type) {
|
|
case vm_type::vm_nil: ctx.top[0] = one; break;
|
|
case vm_type::vm_num: ctx.top[0] = val.num()? zero:one; break;
|
|
case vm_type::vm_str: {
|
|
const f64 num = str2num(val.str().c_str());
|
|
if (std::isnan(num)) {
|
|
ctx.top[0] = var::num(static_cast<f64>(val.str().empty()));
|
|
} else {
|
|
ctx.top[0] = num? zero:one;
|
|
}
|
|
} break;
|
|
default:
|
|
die("cannot do not-operation on "+type_name_string(val));
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_usub() {
|
|
ctx.top[0] = var::num(-ctx.top[0].to_num());
|
|
}
|
|
|
|
inline void vm::o_bnot() {
|
|
ctx.top[0] = var::num(~static_cast<int32_t>(ctx.top[0].num()));
|
|
}
|
|
|
|
inline void vm::o_btor() {
|
|
ctx.top[-1] = var::num(
|
|
static_cast<i32>(ctx.top[-1].to_num())|
|
|
static_cast<i32>(ctx.top[0].to_num())
|
|
);
|
|
--ctx.top;
|
|
}
|
|
|
|
inline void vm::o_btxor() {
|
|
ctx.top[-1] = var::num(
|
|
static_cast<i32>(ctx.top[-1].to_num())^
|
|
static_cast<i32>(ctx.top[0].to_num())
|
|
);
|
|
--ctx.top;
|
|
}
|
|
|
|
inline void vm::o_btand() {
|
|
ctx.top[-1] = var::num(
|
|
static_cast<i32>(ctx.top[-1].to_num())&
|
|
static_cast<i32>(ctx.top[0].to_num())
|
|
);
|
|
--ctx.top;
|
|
}
|
|
|
|
#define op_calc(type)\
|
|
ctx.top[-1] = var::num(ctx.top[-1].to_num() type ctx.top[0].to_num());\
|
|
--ctx.top;
|
|
|
|
inline void vm::o_add() {op_calc(+);}
|
|
inline void vm::o_sub() {op_calc(-);}
|
|
inline void vm::o_mul() {op_calc(*);}
|
|
inline void vm::o_div() {op_calc(/);}
|
|
inline void vm::o_lnk() {
|
|
// concat two vectors into one
|
|
if (ctx.top[-1].type==vm_type::vm_vec && ctx.top[0].type==vm_type::vm_vec) {
|
|
ngc.temp = ngc.alloc(vm_type::vm_vec);
|
|
for(auto i : ctx.top[-1].vec().elems) {
|
|
ngc.temp.vec().elems.push_back(i);
|
|
}
|
|
for(auto i : ctx.top[0].vec().elems) {
|
|
ngc.temp.vec().elems.push_back(i);
|
|
}
|
|
ctx.top[-1] = ngc.temp;
|
|
ngc.temp = nil;
|
|
--ctx.top;
|
|
return;
|
|
}
|
|
// concat strings
|
|
ctx.top[-1] = ngc.newstr(ctx.top[-1].to_str()+ctx.top[0].to_str());
|
|
--ctx.top;
|
|
}
|
|
|
|
#define op_calc_const(type)\
|
|
ctx.top[0] = var::num(ctx.top[0].to_num() type const_number[imm[ctx.pc]]);
|
|
|
|
inline void vm::o_addc() {op_calc_const(+);}
|
|
inline void vm::o_subc() {op_calc_const(-);}
|
|
inline void vm::o_mulc() {op_calc_const(*);}
|
|
inline void vm::o_divc() {op_calc_const(/);}
|
|
inline void vm::o_lnkc() {
|
|
ctx.top[0] = ngc.newstr(ctx.top[0].to_str()+const_string[imm[ctx.pc]]);
|
|
}
|
|
|
|
// top[0] stores the value of memr[0], to avoid being garbage-collected
|
|
// so when the calculation ends, top-=1, then top-=imm[pc]
|
|
// because this return value is meaningless if on stack when imm[pc] = 1
|
|
// like this: func{a+=c;}(); the result of 'a+c' will no be used later, imm[pc] = 1
|
|
// but if b+=a+=c; the result of 'a+c' will be used later, imm[pc] = 0
|
|
#define op_calc_eq(type)\
|
|
ctx.top[-1] = ctx.memr[0] = var::num(\
|
|
ctx.memr[0].to_num() type ctx.top[-1].to_num()\
|
|
);\
|
|
ctx.memr = nullptr;\
|
|
ctx.top -= imm[ctx.pc]+1;
|
|
|
|
inline void vm::o_addeq() {op_calc_eq(+);}
|
|
inline void vm::o_subeq() {op_calc_eq(-);}
|
|
inline void vm::o_muleq() {op_calc_eq(*);}
|
|
inline void vm::o_diveq() {op_calc_eq(/);}
|
|
inline void vm::o_lnkeq() {
|
|
ctx.top[-1] = ctx.memr[0] = ngc.newstr(
|
|
ctx.memr[0].to_str()+ctx.top[-1].to_str()
|
|
);
|
|
ctx.memr = nullptr;
|
|
ctx.top -= imm[ctx.pc]+1;
|
|
}
|
|
|
|
inline void vm::o_bandeq() {
|
|
ctx.top[-1] = ctx.memr[0] = var::num(
|
|
static_cast<i32>(ctx.memr[0].to_num())&
|
|
static_cast<i32>(ctx.top[-1].to_num())
|
|
);
|
|
ctx.memr = nullptr;
|
|
ctx.top -= imm[ctx.pc]+1;
|
|
}
|
|
|
|
inline void vm::o_boreq() {
|
|
ctx.top[-1] = ctx.memr[0] = var::num(
|
|
static_cast<i32>(ctx.memr[0].to_num())|
|
|
static_cast<i32>(ctx.top[-1].to_num())
|
|
);
|
|
ctx.memr = nullptr;
|
|
ctx.top -= imm[ctx.pc]+1;
|
|
}
|
|
|
|
inline void vm::o_bxoreq() {
|
|
ctx.top[-1] = ctx.memr[0] = var::num(
|
|
static_cast<i32>(ctx.memr[0].to_num())^
|
|
static_cast<i32>(ctx.top[-1].to_num())
|
|
);
|
|
ctx.memr = nullptr;
|
|
ctx.top -= imm[ctx.pc]+1;
|
|
}
|
|
|
|
// top[0] stores the value of memr[0], to avoid being garbage-collected
|
|
// so when the calculation ends, top-=imm[pc]>>31
|
|
// because this return value is meaningless if on stack when imm[pc]>>31=1
|
|
// like this: func{a+=1;}(); the result of 'a+1' will no be used later, imm[pc]>>31=1
|
|
// but if b+=a+=1; the result of 'a+1' will be used later, imm[pc]>>31=0
|
|
#define op_calc_eq_const(type)\
|
|
ctx.top[0] = ctx.memr[0] = var::num(\
|
|
ctx.memr[0].to_num() type const_number[imm[ctx.pc]]\
|
|
);\
|
|
ctx.memr = nullptr;
|
|
|
|
inline void vm::o_addeqc() {op_calc_eq_const(+);}
|
|
inline void vm::o_subeqc() {op_calc_eq_const(-);}
|
|
inline void vm::o_muleqc() {op_calc_eq_const(*);}
|
|
inline void vm::o_diveqc() {op_calc_eq_const(/);}
|
|
inline void vm::o_lnkeqc() {
|
|
ctx.top[0] = ctx.memr[0] = ngc.newstr(
|
|
ctx.memr[0].to_str()+const_string[imm[ctx.pc]]
|
|
);
|
|
ctx.memr = nullptr;
|
|
}
|
|
|
|
#define op_calc_eq_const_and_pop(type)\
|
|
ctx.top[0] = ctx.memr[0] = var::num(\
|
|
ctx.memr[0].to_num() type const_number[imm[ctx.pc]]\
|
|
);\
|
|
ctx.memr = nullptr;\
|
|
--ctx.top;
|
|
|
|
inline void vm::o_addecp() {op_calc_eq_const_and_pop(+);}
|
|
inline void vm::o_subecp() {op_calc_eq_const_and_pop(-);}
|
|
inline void vm::o_mulecp() {op_calc_eq_const_and_pop(*);}
|
|
inline void vm::o_divecp() {op_calc_eq_const_and_pop(/);}
|
|
inline void vm::o_lnkecp() {
|
|
ctx.top[0] = ctx.memr[0] = ngc.newstr(
|
|
ctx.memr[0].to_str()+const_string[imm[ctx.pc]]
|
|
);
|
|
ctx.memr = nullptr;
|
|
--ctx.top;
|
|
}
|
|
|
|
inline void vm::o_meq() {
|
|
// 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
|
|
ctx.memr[0] = ctx.top[-1];
|
|
ctx.memr = nullptr;
|
|
ctx.top -= imm[ctx.pc]+1;
|
|
}
|
|
|
|
inline void vm::o_eq() {
|
|
var val2 = ctx.top[0];
|
|
var val1 = (--ctx.top)[0];
|
|
if (val1.type==vm_type::vm_nil && val2.type==vm_type::vm_nil) {
|
|
ctx.top[0] = one;
|
|
} else if (val1.type==vm_type::vm_str && val2.type==vm_type::vm_str) {
|
|
ctx.top[0] = (val1.str()==val2.str())? one:zero;
|
|
} else if ((val1.type==vm_type::vm_num || val2.type==vm_type::vm_num)
|
|
&& val1.type!=vm_type::vm_nil && val2.type!=vm_type::vm_nil) {
|
|
ctx.top[0] = (val1.to_num()==val2.to_num())? one:zero;
|
|
} else {
|
|
ctx.top[0] = (val1==val2)? one:zero;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_neq() {
|
|
var val2 = ctx.top[0];
|
|
var val1 = (--ctx.top)[0];
|
|
if (val1.type==vm_type::vm_nil && val2.type==vm_type::vm_nil) {
|
|
ctx.top[0] = zero;
|
|
} else if (val1.type==vm_type::vm_str && val2.type==vm_type::vm_str) {
|
|
ctx.top[0] = (val1.str()!=val2.str())? one:zero;
|
|
} else if ((val1.type==vm_type::vm_num || val2.type==vm_type::vm_num)
|
|
&& val1.type!=vm_type::vm_nil && val2.type!=vm_type::vm_nil) {
|
|
ctx.top[0] = (val1.to_num()!=val2.to_num())? one:zero;
|
|
} else {
|
|
ctx.top[0] = (val1!=val2)? one:zero;
|
|
}
|
|
}
|
|
|
|
#define op_cmp(type)\
|
|
--ctx.top;\
|
|
ctx.top[0] = (ctx.top[0].to_num() type ctx.top[1].to_num())? one:zero;
|
|
|
|
inline void vm::o_less() {op_cmp(<);}
|
|
inline void vm::o_leq() {op_cmp(<=);}
|
|
inline void vm::o_grt() {op_cmp(>);}
|
|
inline void vm::o_geq() {op_cmp(>=);}
|
|
|
|
#define op_cmp_const(type)\
|
|
ctx.top[0] = (ctx.top[0].to_num() type const_number[imm[ctx.pc]])? one:zero;
|
|
|
|
inline void vm::o_lessc() {op_cmp_const(<);}
|
|
inline void vm::o_leqc() {op_cmp_const(<=);}
|
|
inline void vm::o_grtc() {op_cmp_const(>);}
|
|
inline void vm::o_geqc() {op_cmp_const(>=);}
|
|
|
|
inline void vm::o_pop() {
|
|
--ctx.top;
|
|
}
|
|
|
|
inline void vm::o_jmp() {
|
|
ctx.pc = imm[ctx.pc]-1;
|
|
}
|
|
|
|
inline void vm::o_jt() {
|
|
// jump true needs to reserve the result on stack
|
|
// because conditional expression in nasal has return value
|
|
if (cond(ctx.top[0])) {
|
|
ctx.pc = imm[ctx.pc]-1;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_jf() {
|
|
// jump false doesn't need to reserve result
|
|
if (!cond(ctx.top[0])) {
|
|
ctx.pc = imm[ctx.pc]-1;
|
|
}
|
|
--ctx.top;
|
|
}
|
|
|
|
inline void vm::o_cnt() {
|
|
if (ctx.top[0].type!=vm_type::vm_vec) {
|
|
die("must use vector in forindex/foreach but get "+
|
|
type_name_string(ctx.top[0])
|
|
);
|
|
return;
|
|
}
|
|
(++ctx.top)[0] = var::cnt(-1);
|
|
}
|
|
|
|
inline void vm::o_findex() {
|
|
if ((usize)(++ctx.top[0].cnt())>=ctx.top[-1].vec().size()) {
|
|
ctx.pc = imm[ctx.pc]-1;
|
|
return;
|
|
}
|
|
ctx.top[1] = var::num(ctx.top[0].cnt());
|
|
++ctx.top;
|
|
}
|
|
|
|
inline void vm::o_feach() {
|
|
auto& ref = ctx.top[-1].vec().elems;
|
|
if ((usize)(++ctx.top[0].cnt())>=ref.size()) {
|
|
ctx.pc = imm[ctx.pc]-1;
|
|
return;
|
|
}
|
|
ctx.top[1] = ref[ctx.top[0].cnt()];
|
|
++ctx.top;
|
|
}
|
|
|
|
inline void vm::o_callg() {
|
|
// get main stack directly
|
|
(++ctx.top)[0] = global[imm[ctx.pc]];
|
|
}
|
|
|
|
inline void vm::o_calll() {
|
|
(++ctx.top)[0] = ctx.localr[imm[ctx.pc]];
|
|
}
|
|
|
|
inline void vm::o_upval() {
|
|
(++ctx.top)[0] = ctx.funcr.func()
|
|
.upval[(imm[ctx.pc]>>16)&0xffff]
|
|
.upval()[imm[ctx.pc]&0xffff];
|
|
}
|
|
|
|
inline void vm::o_callv() {
|
|
var val = ctx.top[0];
|
|
var vec = (--ctx.top)[0];
|
|
if (vec.type==vm_type::vm_vec) {
|
|
ctx.top[0] = vec.vec().get_value(val.to_num());
|
|
if (ctx.top[0].type==vm_type::vm_none) {
|
|
die(report_out_of_range(val.to_num(), vec.vec().size()));
|
|
return;
|
|
}
|
|
} else if (vec.type==vm_type::vm_hash) {
|
|
if (val.type!=vm_type::vm_str) {
|
|
die("must use string as the key but get "+type_name_string(val));
|
|
return;
|
|
}
|
|
ctx.top[0] = vec.hash().get_value(val.str());
|
|
if (ctx.top[0].type==vm_type::vm_none) {
|
|
die(report_key_not_found(val.str(), vec.hash()));
|
|
return;
|
|
} else if (ctx.top[0].type==vm_type::vm_func) {
|
|
ctx.top[0].func().local[0] = val; // 'me'
|
|
}
|
|
} else if (vec.type==vm_type::vm_str) {
|
|
const auto& str = vec.str();
|
|
i32 num = val.to_num();
|
|
i32 len = str.length();
|
|
if (num<-len || num>=len) {
|
|
die(report_out_of_range(num, str.size()));
|
|
return;
|
|
}
|
|
ctx.top[0] = var::num(
|
|
static_cast<f64>(static_cast<u8>(str[num>=0? num:num+len]))
|
|
);
|
|
} else if (vec.type==vm_type::vm_map) {
|
|
if (val.type!=vm_type::vm_str) {
|
|
die("must use string as the key but get "+type_name_string(val));
|
|
return;
|
|
}
|
|
ctx.top[0] = vec.map().get_value(val.str());
|
|
if (ctx.top[0].type==vm_type::vm_none) {
|
|
die("cannot find symbol \""+val.str()+"\"");
|
|
return;
|
|
}
|
|
} else {
|
|
die("must call a vector/hash/string but get "+type_name_string(vec));
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_callvi() {
|
|
var val = ctx.top[0];
|
|
if (val.type!=vm_type::vm_vec) {
|
|
die("must use a vector but get "+type_name_string(val));
|
|
return;
|
|
}
|
|
// cannot use operator[],because this may cause overflow
|
|
(++ctx.top)[0] = val.vec().get_value(imm[ctx.pc]);
|
|
if (ctx.top[0].type==vm_type::vm_none) {
|
|
die(report_out_of_range(imm[ctx.pc], val.vec().size()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_callh() {
|
|
var val = ctx.top[0];
|
|
if (val.type!=vm_type::vm_hash && val.type!=vm_type::vm_map) {
|
|
die("must call a hash but get "+type_name_string(val));
|
|
return;
|
|
}
|
|
const auto& str = const_string[imm[ctx.pc]];
|
|
if (val.type==vm_type::vm_hash) {
|
|
ctx.top[0] = val.hash().get_value(str);
|
|
} else {
|
|
ctx.top[0] = val.map().get_value(str);
|
|
}
|
|
if (ctx.top[0].type==vm_type::vm_none) {
|
|
val.type==vm_type::vm_hash?
|
|
die(report_key_not_found(str, val.hash())):
|
|
die("cannot find symbol \"" + str + "\"");
|
|
return;
|
|
} else if (ctx.top[0].type==vm_type::vm_func) {
|
|
ctx.top[0].func().local[0] = val; // 'me'
|
|
}
|
|
}
|
|
|
|
inline void vm::o_callfv() {
|
|
const u32 argc = imm[ctx.pc]; // arguments counter
|
|
var* local = ctx.top-argc+1; // arguments begin address
|
|
if (local[-1].type!=vm_type::vm_func) {
|
|
die("must call a function but get "+type_name_string(local[-1]));
|
|
return;
|
|
}
|
|
const auto& func = local[-1].func();
|
|
|
|
// swap funcr with local[-1]
|
|
var tmp = local[-1];
|
|
local[-1] = ctx.funcr;
|
|
ctx.funcr = tmp;
|
|
|
|
// top-argc+lsize(local) +1(old pc) +1(old localr) +1(old upvalr)
|
|
if (ctx.top-argc+func.local_size+3>=ctx.canary) {
|
|
die("stack overflow");
|
|
return;
|
|
}
|
|
// parameter size is func->psize-1, 1 is reserved for "me"
|
|
const u32 parameter_size = func.parameter_size-1;
|
|
if (argc<parameter_size && func.local[argc+1].type==vm_type::vm_none) {
|
|
die(report_lack_arguments(argc, func));
|
|
return;
|
|
}
|
|
|
|
// load dynamic argument, default nil, for better performance
|
|
var dynamic = nil;
|
|
if (func.dynamic_parameter_index>=0) {
|
|
// load dynamic argument
|
|
dynamic = ngc.alloc(vm_type::vm_vec);
|
|
for(u32 i = parameter_size; i<argc; ++i) {
|
|
dynamic.vec().elems.push_back(local[i]);
|
|
}
|
|
} else if (parameter_size<argc) {
|
|
// load arguments to default dynamic argument "arg", located at stack+1
|
|
dynamic = ngc.alloc(vm_type::vm_vec);
|
|
for(u32 i = parameter_size; i<argc; ++i) {
|
|
dynamic.vec().elems.push_back(local[i]);
|
|
}
|
|
}
|
|
// should reset stack top after allocating vector
|
|
// because this may cause gc
|
|
// then all the available values the vector needs
|
|
// are all outside the stack top and may be
|
|
// collected incorrectly
|
|
ctx.top = local+func.local_size;
|
|
|
|
const u32 min_size = (std::min)(parameter_size, argc); // avoid error in MSVC
|
|
for(u32 i = min_size; i>=1; --i) { // load arguments
|
|
local[i] = local[i-1];
|
|
}
|
|
local[0] = func.local[0];// load "me"
|
|
|
|
// load local scope & default arguments
|
|
for(u32 i = min_size+1; i<func.local_size; ++i) {
|
|
local[i] = func.local[i];
|
|
}
|
|
// load dynamic argument
|
|
local[func.dynamic_parameter_index>=0?
|
|
parameter_size+1:func.local_size-1] = dynamic;
|
|
|
|
ctx.top[0] = ctx.upvalr;
|
|
(++ctx.top)[0] = var::addr(ctx.localr);
|
|
(++ctx.top)[0] = var::ret(ctx.pc);
|
|
ctx.pc = func.entry-1;
|
|
ctx.localr = local;
|
|
ctx.upvalr = nil;
|
|
}
|
|
|
|
inline void vm::o_callfh() {
|
|
const auto& hash = ctx.top[0].hash().elems;
|
|
if (ctx.top[-1].type!=vm_type::vm_func) {
|
|
die("must call a function but get "+type_name_string(ctx.top[-1]));
|
|
return;
|
|
}
|
|
const auto& func = ctx.top[-1].func();
|
|
var tmp = ctx.top[-1];
|
|
ctx.top[-1] = ctx.funcr;
|
|
ctx.funcr = tmp;
|
|
|
|
// top -1(hash) +lsize(local) +1(old pc) +1(old localr) +1(old upvalr)
|
|
if (ctx.top+func.local_size+2>= ctx.canary) {
|
|
die("stack overflow");
|
|
return;
|
|
}
|
|
// dynamic parameter is not allowed in this kind of function call
|
|
if (func.dynamic_parameter_index>=0) {
|
|
die("special call cannot use dynamic argument \"" +
|
|
const_string[func.dynamic_parameter_index] + "\""
|
|
);
|
|
return;
|
|
}
|
|
|
|
var* local = ctx.top;
|
|
ctx.top += func.local_size;
|
|
for(u32 i = 0; i<func.local_size; ++i) {
|
|
local[i] = func.local[i];
|
|
}
|
|
|
|
bool lack_arguments_flag = false;
|
|
for(const auto& i : func.keys) {
|
|
const auto& key = i.first;
|
|
if (hash.count(key)) {
|
|
local[i.second] = hash.at(key);
|
|
} else if (local[i.second].type==vm_type::vm_none) {
|
|
lack_arguments_flag = true;
|
|
}
|
|
}
|
|
if (lack_arguments_flag) {
|
|
die(report_special_call_lack_arguments(local, func));
|
|
return;
|
|
}
|
|
|
|
ctx.top[0] = ctx.upvalr;
|
|
(++ctx.top)[0] = var::addr(ctx.localr);
|
|
(++ctx.top)[0] = var::ret(ctx.pc); // rewrite top with vm_ret
|
|
ctx.pc=func.entry-1;
|
|
ctx.localr = local;
|
|
ctx.upvalr = nil;
|
|
}
|
|
|
|
inline void vm::o_callb() {
|
|
// reserve place for native function return,
|
|
// this code is written for coroutine
|
|
(++ctx.top)[0] = nil;
|
|
|
|
// if running a native function about coroutine
|
|
// (top) will be set to another context.top, instead of main_context.top
|
|
auto function_pointer = native_function[imm[ctx.pc]].func;
|
|
var result = (*function_pointer)(&ctx, &ngc);
|
|
|
|
// so we use tmp variable to store this return value
|
|
// and set it to top[0] later
|
|
ctx.top[0] = result;
|
|
|
|
// if get none, this means errors occurred when calling this native function
|
|
if (ctx.top[0].type==vm_type::vm_none) {
|
|
die("error occurred in native function");
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_slcbeg() {
|
|
// +--------------+
|
|
// | slice_vector | <-- top[0]
|
|
// +--------------+
|
|
// | resource_vec | <-- top[-1]
|
|
// +--------------+
|
|
(++ctx.top)[0] = ngc.alloc(vm_type::vm_vec);
|
|
if (ctx.top[-1].type!=vm_type::vm_vec) {
|
|
die("must slice a vector but get "+type_name_string(ctx.top[-1]));
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_slcend() {
|
|
ctx.top[-1] = ctx.top[0];
|
|
--ctx.top;
|
|
}
|
|
|
|
inline void vm::o_slc() {
|
|
var val = (ctx.top--)[0];
|
|
var res = ctx.top[-1].vec().get_value(val.to_num());
|
|
if (res.type==vm_type::vm_none) {
|
|
die(report_out_of_range(val.to_num(), ctx.top[-1].vec().size()));
|
|
return;
|
|
}
|
|
ctx.top[0].vec().elems.push_back(res);
|
|
}
|
|
|
|
inline void vm::o_slc2() {
|
|
var val2 = (ctx.top--)[0];
|
|
var val1 = (ctx.top--)[0];
|
|
const auto& ref = ctx.top[-1].vec().elems;
|
|
auto& aim = ctx.top[0].vec().elems;
|
|
|
|
vm_type type1 = val1.type;
|
|
vm_type type2 = val2.type;
|
|
i32 num1 = val1.to_num();
|
|
i32 num2 = val2.to_num();
|
|
i32 size = ref.size();
|
|
if (type1==vm_type::vm_nil && type2==vm_type::vm_nil) {
|
|
num1 = 0;
|
|
num2 = size-1;
|
|
} else if (type1==vm_type::vm_nil && type2!=vm_type::vm_nil) {
|
|
num1 = num2<0? -size:0;
|
|
} else if (type1!=vm_type::vm_nil && type2==vm_type::vm_nil) {
|
|
num2 = num1<0? -1:size-1;
|
|
}
|
|
|
|
if (num1<-size || num1>=size || num2<-size || num2>=size) {
|
|
die("index " + std::to_string(num1) + ":" +
|
|
std::to_string(num2) + " out of range, real size is " +
|
|
std::to_string(size)
|
|
);
|
|
return;
|
|
} else if (num1<=num2) {
|
|
for(i32 i = num1; i<=num2; ++i) {
|
|
aim.push_back(i>=0? ref[i]:ref[i+size]);
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void vm::o_mcallg() {
|
|
ctx.memr = global+imm[ctx.pc];
|
|
(++ctx.top)[0] = ctx.memr[0];
|
|
// push value in this memory space on stack
|
|
// to avoid being garbage collected
|
|
}
|
|
|
|
inline void vm::o_mcalll() {
|
|
ctx.memr = ctx.localr+imm[ctx.pc];
|
|
(++ctx.top)[0] = ctx.memr[0];
|
|
// push value in this memory space on stack
|
|
// to avoid being garbage collected
|
|
}
|
|
|
|
inline void vm::o_mupval() {
|
|
ctx.memr = &(
|
|
ctx.funcr.func()
|
|
.upval[(imm[ctx.pc]>>16)&0xffff]
|
|
.upval()[imm[ctx.pc]&0xffff]
|
|
);
|
|
(++ctx.top)[0] = ctx.memr[0];
|
|
// push value in this memory space on stack
|
|
// to avoid being garbage collected
|
|
}
|
|
|
|
inline void vm::o_mcallv() {
|
|
var val = ctx.top[0]; // index
|
|
var vec = (--ctx.top)[0]; // mcall vector, reserved on stack to avoid gc
|
|
if (vec.type==vm_type::vm_vec) {
|
|
ctx.memr = vec.vec().get_memory(val.to_num());
|
|
if (!ctx.memr) {
|
|
die(report_out_of_range(val.to_num(), vec.vec().size()));
|
|
return;
|
|
}
|
|
} else if (vec.type==vm_type::vm_hash) { // do mcallh but use the mcallv way
|
|
if (val.type!=vm_type::vm_str) {
|
|
die("must use string as the key but get "+type_name_string(val));
|
|
return;
|
|
}
|
|
auto& ref = vec.hash();
|
|
const auto& str = val.str();
|
|
ctx.memr = ref.get_memory(str);
|
|
if (!ctx.memr) {
|
|
ref.elems[str] = nil;
|
|
ctx.memr = ref.get_memory(str);
|
|
}
|
|
} else if (vec.type==vm_type::vm_map) {
|
|
if (val.type!=vm_type::vm_str) {
|
|
die("must use string as the key but get "+type_name_string(val));
|
|
return;
|
|
}
|
|
auto& ref = vec.map();
|
|
const auto& str = val.str();
|
|
ctx.memr = ref.get_memory(str);
|
|
if (!ctx.memr) {
|
|
die("cannot find symbol \"" + str + "\"");
|
|
}
|
|
} else {
|
|
die("cannot get memory space in this type");
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void vm::o_mcallh() {
|
|
var hash = ctx.top[0]; // mcall hash, reserved on stack to avoid gc
|
|
if (hash.type!=vm_type::vm_hash && hash.type!=vm_type::vm_map) {
|
|
die("must call a hash/namespace but get "+type_name_string(hash));
|
|
return;
|
|
}
|
|
const auto& str = const_string[imm[ctx.pc]];
|
|
if (hash.type==vm_type::vm_map) {
|
|
ctx.memr = hash.map().get_memory(str);
|
|
if (!ctx.memr) {
|
|
die("cannot find symbol \"" + str + "\"");
|
|
}
|
|
return;
|
|
}
|
|
auto& ref = hash.hash();
|
|
ctx.memr = ref.get_memory(str);
|
|
// create a new key
|
|
if (!ctx.memr) {
|
|
ref.elems[str] = nil;
|
|
ctx.memr = ref.get_memory(str);
|
|
}
|
|
}
|
|
|
|
inline void vm::o_ret() {
|
|
/* +-------------+
|
|
* | return value| <- top[0]
|
|
* +-------------+
|
|
* | old pc | <- top[-1]
|
|
* +-------------+
|
|
* | old localr | <- top[-2]
|
|
* +-------------+
|
|
* | old upvalr | <- top[-3]
|
|
* +-------------+
|
|
* | local scope |
|
|
* +-------------+ <- local pointer stored in localr
|
|
* | old funcr | <- old function stored in funcr
|
|
* +-------------+
|
|
*/
|
|
var ret = ctx.top[0];
|
|
var* local = ctx.localr;
|
|
var func = ctx.funcr;
|
|
var up = ctx.upvalr;
|
|
|
|
ctx.pc = ctx.top[-1].ret();
|
|
ctx.localr = ctx.top[-2].addr();
|
|
ctx.upvalr = ctx.top[-3];
|
|
|
|
ctx.top = local-1;
|
|
ctx.funcr = ctx.top[0];
|
|
ctx.top[0] = ret; // rewrite func with returned value
|
|
|
|
// synchronize upvalue
|
|
if (up.type==vm_type::vm_upval) {
|
|
auto& upval = up.upval();
|
|
auto size = func.func().local_size;
|
|
upval.on_stack = false;
|
|
upval.elems.resize(size);
|
|
for(u32 i = 0; i<size; ++i) {
|
|
upval.elems[i] = local[i];
|
|
}
|
|
}
|
|
|
|
// cannot use gc.cort to judge,
|
|
// because there maybe another function call inside but return here
|
|
// coroutine function ends with setting pc to 0
|
|
if (!ctx.pc) {
|
|
ngc.context_reserve();
|
|
}
|
|
}
|
|
|
|
}
|