#include "nasal_vm.h" #include "util/util.h" namespace nasal { void vm::vm_init_enrty(const std::vector& strs, const std::vector& nums, const std::vector& natives, const std::vector& code, const std::unordered_map& global_symbol, const std::vector& filenames, const std::vector& argv) { const_number = nums.data(); const_string = strs.data(); bytecode = code.data(); files = filenames.data(); global_size = global_symbol.size(); /* set native functions */ native_function = natives; /* set context and global */ if (!is_repl_mode || first_exec_flag) { context_and_global_init(); first_exec_flag = false; } /* init gc */ ngc.set(&ctx, global, global_size); ngc.init(strs, argv); /* init vm globals */ auto map_instance = ngc.alloc(vm_type::vm_map); global_symbol_name.resize(global_symbol.size()); global[global_symbol.at("globals")] = map_instance; for (const auto& i : global_symbol) { map_instance.map().mapper[i.first] = global + i.second; global_symbol_name[i.second] = i.first; } /* init vm arg */ auto arg_instance = ngc.alloc(vm_type::vm_vec); global[global_symbol.at("arg")] = arg_instance; arg_instance.vec().elems = ngc.env_argv; } void vm::context_and_global_init() { /* set canary and program counter */ ctx.pc = 0; ctx.localr = nullptr; ctx.memr = nullptr; ctx.funcr = nil; ctx.upvalr = nil; /* set canary = stack[VM_STACK_DEPTH-1] */ ctx.canary = ctx.stack+VM_STACK_DEPTH-1; /* nothing is on stack */ ctx.top = ctx.stack - 1; /* clear main stack and global */ for (u32 i = 0; i(val.addr()) << std::dec; } void vm::raw_string_info(var& val) { std::clog << "\"" << util::rawstr(val.str(), 24) << "\""; } void vm::upvalue_info(var& val) { std::clog << "[" << val.upval().size << " val] "; if (val.upval().on_stack) { std::clog << "offset:0x" << std::hex; std::clog << reinterpret_cast(val.upval().stack_frame_offset); std::clog << std::dec; } } void vm::vector_value_info(var& val) { std::clog << "[" << val.vec().size() << " val]"; } void vm::hash_value_info(var& val, const usize max_show_elems) { std::clog << "{"; usize count = 0; for (const auto& i : val.hash().elems) { ++count; if (count>max_show_elems) { break; } std::clog << i.first; if (count!=val.hash().size()) { std::clog << ", "; } } if (val.hash().size()>max_show_elems) { std::clog << "..."; } std::clog << "}"; } void vm::coroutine_value_info(var& val) { std::clog << "[ "; switch(val.co().status) { case nas_co::status::dead: std::clog << "dead"; break; case nas_co::status::running: std::clog << "running"; break; case nas_co::status::suspended: std::clog << "suspended"; break; } std::clog << " ] @0x"; std::clog << std::hex << reinterpret_cast(val.val.gcobj) << std::dec; } void vm::namespace_value_info(var& val, const usize max_show_elems) { std::clog << "{"; usize count = 0; for (const auto& i : val.map().mapper) { ++count; if (count>max_show_elems) { break; } std::clog << i.first; if (count!=val.map().size()) { std::clog << ", "; } } if (val.map().size()>max_show_elems) { std::clog << "..."; } std::clog << "}"; } void vm::value_name_form(const var& val) { std::clog << "| "; switch(val.type) { case vm_type::vm_none: std::clog << "null "; break; case vm_type::vm_ret: std::clog << "ret "; break; case vm_type::vm_addr: std::clog << "addr "; break; case vm_type::vm_cnt: std::clog << "cnt "; break; case vm_type::vm_nil: std::clog << "nil "; break; case vm_type::vm_num: std::clog << "num "; break; case vm_type::vm_str: std::clog << "str "; break; case vm_type::vm_func: std::clog << "func "; break; case vm_type::vm_upval: std::clog << "upval"; break; case vm_type::vm_vec: std::clog << "vec "; break; case vm_type::vm_hash: std::clog << "hash "; break; case vm_type::vm_ghost: std::clog << "ghost"; break; case vm_type::vm_co: std::clog << "co "; break; case vm_type::vm_map: std::clog << "map "; break; default: std::clog << "err "; break; } std::clog << " | "; } void vm::value_info(var& val) { value_name_form(val); switch(val.type) { case vm_type::vm_none: break; case vm_type::vm_ret: return_address_info(val); break; case vm_type::vm_addr: memory_address_info(val); break; case vm_type::vm_cnt: std::clog << val.cnt(); break; case vm_type::vm_nil: break; case vm_type::vm_num: std::clog << val.num(); break; case vm_type::vm_str: raw_string_info(val); break; case vm_type::vm_func: std::clog << val.func(); break; case vm_type::vm_upval: upvalue_info(val); break; case vm_type::vm_vec: vector_value_info(val); break; case vm_type::vm_hash: hash_value_info(val, 4); break; case vm_type::vm_ghost: std::clog << val.ghost(); break; case vm_type::vm_co: coroutine_value_info(val); break; case vm_type::vm_map: namespace_value_info(val, 4); break; default: std::clog << "unknown"; break; } std::clog << "\n"; } void vm::function_detail_info(const nas_func& func) { std::clog << "func "; std::vector argument_list = {}; argument_list.resize(func.keys.size()); for (const auto& key : func.keys) { argument_list[key.second-1] = key.first; } std::clog << "("; for (const auto& key : argument_list) { std::clog << key; if (key != argument_list.back()) { std::clog << ", "; } } if (func.dynamic_parameter_index>=0) { std::clog << (argument_list.size()? ", ":""); std::clog << func.dynamic_parameter_name << "..."; } std::clog << ") "; const auto& code = bytecode[func.entry]; std::clog << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; } void vm::function_call_trace() { util::windows_code_page_manager cp; cp.set_utf8_output(); var* bottom = ctx.stack; var* top = ctx.top; // generate trace back std::vector functions; std::vector callsite; var* prev_func = &ctx.funcr; functions.push_back(&prev_func->func()); for (var* i = top; i >= bottom; i--) { // +-------+------------------+ // | ret | 0x3bf | <-- i + 1 (should not be 0, except coroutine) // +-------+------------------+ // | addr | 0x7ff5f61ae020 | <-- i // +-------+------------------+ // | upval | ... | <-- i - 1 // +-------+------------------+ // | locals| ... | // +-------+------------------+ // | func | function | <-- i - 1 - prev_func->local_size - 1 // +-------+------------------+ if (i + 1 <= top && i[0].is_addr() && i[1].is_ret()) { auto r_addr = i[1].ret(); callsite.push_back(r_addr); i--; i -= prev_func->func().local_size; i--; if (i >= bottom && i[0].is_func()) { prev_func = i; functions.push_back(&prev_func->func()); } } } std::clog << "\ncall trace "; std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n"; std::clog << " crash occurred at\n "; function_detail_info(ctx.funcr.func()); std::clog << " at " << files[bytecode[ctx.pc].fidx] << ":"; std::clog << bytecode[ctx.pc].line << "\n"; const nas_func* prev = nullptr; u64 prev_addr = 0; u64 same_call_count = 0; for (int i = 0; i < functions.size(); ++i) { if (functions[i] == prev && callsite[i] == prev_addr) { same_call_count++; continue; } else if (same_call_count) { std::clog << " `--> " << same_call_count << " same call(s)\n"; same_call_count = 0; } // in coroutine if (callsite[i] == 0 && ngc.cort) { std::clog << " call by coroutine\n"; break; } std::clog << " call "; function_detail_info(*functions[i]); auto r_addr = callsite[i]; std::clog << " from " << files[bytecode[r_addr].fidx] << ":"; std::clog << bytecode[r_addr].line << "\n"; prev = functions[i]; prev_addr = r_addr; } if (same_call_count) { std::clog << " `--> " << same_call_count << " same call(s)\n"; } cp.restore_code_page(); } void vm::trace_back() { // generate trace back std::stack ret; for (var* i = ctx.stack; i<=ctx.top; ++i) { if (i->is_ret() && i->ret()!=0) { ret.push(i->ret()); } } // store the position program crashed ret.push(ctx.pc); std::clog << "\nback trace "; std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n"; codestream::set( const_number, const_string, global_symbol_name, native_function.data(), files ); for (u64 p = 0, same = 0, prev = 0xffffffff; !ret.empty(); prev = p, ret.pop()) { if ((p = ret.top())==prev) { ++same; continue; } else if (same) { std::clog << " 0x" << std::hex << std::setw(8) << std::setfill('0') << prev << std::dec << " " << same << " same call(s)\n"; same = 0; } std::clog << " " << codestream(bytecode[p], p) << "\n"; } // the first called place has no same calls } void vm::stack_info(const u64 limit) { var* top = ctx.top; var* bottom = ctx.stack; const auto stack_address = reinterpret_cast(bottom); std::clog << "\nstack (0x" << std::hex << stack_address << std::dec; std::clog << ", limit " << limit << ", total "; std::clog << (top(top-bottom+1)) << ")\n"; for (u32 i = 0; i=bottom; ++i, --top) { std::clog << " 0x" << std::hex << std::setw(8) << std::setfill('0') << static_cast(top-bottom) << std::dec << " "; value_info(top[0]); } } void vm::register_info() { std::clog << "\nregister (" << (ngc.cort? "coroutine":"main") << ")\n"; std::clog << std::hex << " [ pc ] | pc | 0x" << ctx.pc << "\n" << " [ global ] | addr | 0x" << reinterpret_cast(global) << "\n" << " [ local ] | addr | 0x" << reinterpret_cast(ctx.localr) << "\n" << " [ memr ] | addr | 0x" << reinterpret_cast(ctx.memr) << "\n" << " [ canary ] | addr | 0x" << reinterpret_cast(ctx.canary) << "\n" << " [ top ] | addr | 0x" << reinterpret_cast(ctx.top) << "\n" << std::dec; std::clog << " [ funcr ] "; value_info(ctx.funcr); std::clog << " [ upval ] "; value_info(ctx.upvalr); } void vm::global_state() { if (!global_size || global[0].is_none()) { return; } std::clog << "\nglobal (0x" << std::hex << reinterpret_cast(global) << ")\n" << std::dec; for (usize i = 0; i(i) << std::dec << " "; auto name = global_symbol_name[i]; if (name.length()>=10) { name = name.substr(0, 7) + "..."; } else { } std::clog << "| " << std::left << std::setw(10) << std::setfill(' ') << name << " " << std::internal; value_info(global[i]); } } void vm::local_state() { if (!ctx.localr || !ctx.funcr.func().local_size) { return; } const u32 lsize = ctx.funcr.func().local_size; std::clog << "\nlocal (0x" << std::hex << reinterpret_cast(ctx.localr) << " <+" << static_cast(ctx.localr-ctx.stack) << ">)\n" << std::dec; for (u32 i = 0; i upval[" << i << "]:\n"; auto& uv = upval[i].upval(); for (u32 j = 0; j argument_list = {}; argument_list.resize(func.keys.size()); for (const auto& i : func.keys) { argument_list[i.second-1] = i.first; } for (u32 i = 0; i=0) { result += argument_list.size()? ", ":""; result += const_string[func.dynamic_parameter_index] + "[dynamic]"; } result += ") "; std::stringstream out; const auto& code = bytecode[func.entry]; out << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; out << " @ 0x" << std::hex << reinterpret_cast(&func) << std::dec; return result + out.str(); } std::string vm::report_special_call_lack_arguments(var* local, const nas_func& func) const { auto result = std::string("lack argument(s) when calling function:\n func("); std::vector argument_list = {}; argument_list.resize(func.keys.size()); for (const auto& i : func.keys) { argument_list[i.second-1] = i.first; } for (const auto& key : argument_list) { if (local[func.keys.at(key)].is_none()) { result += key + ", "; } else { result += key + "[get], "; } } result = result.substr(0, result.length()-2); result += ") "; std::stringstream out; const auto& code = bytecode[func.entry]; out << "{ entry: " << files[code.fidx] << ":" << code.line << " }"; out << " @ 0x" << std::hex << reinterpret_cast(&func) << std::dec; return result + out.str(); } std::string vm::report_key_not_found(const std::string& not_found, const nas_hash& hash) const { auto result = "member \"" + not_found + "\" doesn't exist in hash {"; for (const auto& i : hash.elems) { result += i.first + ", "; } if (hash.elems.size()) { result = result.substr(0, result.length()-2); } result += "}"; return result; } std::string vm::report_out_of_range(f64 index, usize real_size) const { auto result = "index out of range: " + std::to_string(index); result += " but max size is " + std::to_string(real_size); if (!real_size) { return result; } result += ", index range is -" + std::to_string(real_size); result += "~" + std::to_string(real_size-1); return result; } std::string vm::type_name_string(const var& value) const { switch(value.type) { case vm_type::vm_none: return "none"; case vm_type::vm_cnt: return "counter"; case vm_type::vm_addr: return "address"; case vm_type::vm_ret: return "program counter"; case vm_type::vm_nil: return "nil"; case vm_type::vm_num: return "number"; case vm_type::vm_str: return "string"; case vm_type::vm_vec: return "vector"; case vm_type::vm_hash: return "hash"; case vm_type::vm_func: return "function"; case vm_type::vm_upval: return "upvalue"; case vm_type::vm_ghost: return "ghost type"; case vm_type::vm_co: return "coroutine"; case vm_type::vm_map: return "namespace"; default: break; } return "unknown"; } void vm::die(const std::string& str) { std::cerr << "[vm] error: " << str << "\n"; function_call_trace(); // trace back contains bytecode info, dump in verbose mode if (verbose) { trace_back(); } // verbose will dump more values on stack if (verbose) { stack_info(64); } // show verbose crash info if (verbose) { all_state_detail(); } if (!ngc.cort) { if (!verbose) { std::cerr << "\n[vm] use <-d> for detailed crash info.\n\n"; } // in main context, exit directly std::exit(1); } // in coroutine, shut down the coroutine and return to main context ctx.pc = 0; // mark coroutine 'dead' ngc.context_reserve(); // switch context to main ctx.top[0] = nil; // generate return value 'nil' } void vm::run(const codegen& gen, const linker& linker, const std::vector& argv) { vm_init_enrty( gen.strs(), gen.nums(), gen.natives(), gen.codes(), gen.globals(), linker.get_file_list(), argv ); #ifndef _MSC_VER // interrupt check macro for computed goto mode. #define CHECK_INTERRUPT { \ if (interrupt_ptr && interrupt_ptr->load()) { \ throw std::runtime_error("VM execution interrupted by timeout"); \ } \ } // using labels as values/computed goto const void* oprs[] = { &&vmexit, &&repl, &&intl, &&loadg, &&loadl, &&loadu, &&dup, &&pnum, &&pnil, &&pstr, &&newv, &&newh, &&newf, &&happ, &¶, &&deft, &&dyn, &&lnot, &&usub, &&bnot, &&btor, &&btxor, &&btand, &&add, &&sub, &&mul, &&div, &&lnk, &&addc, &&subc, &&mulc, &&divc, &&lnkc, &&addeq, &&subeq, &&muleq, &&diveq, &&lnkeq, &&bandeq, &&boreq, &&bxoreq, &&addeqc, &&subeqc, &&muleqc, &&diveqc, &&lnkeqc, &&addecp, &&subecp, &&mulecp, &&divecp, &&lnkecp, &&meq, &&eq, &&neq, &&less, &&leq, &&grt, &&geq, &&lessc, &&leqc, &&grtc, &&geqc, &&pop, &&jmp, &&jt, &&jf, &&cnt, &&findex, &&feach, &&callg, &&calll, &&upval, &&callv, &&callvi, &&callh, &&callfv, &&callfh, &&callb, &&slcbeg, &&slcend, &&slc, &&slc2, &&mcallg, &&mcalll, &&mupval, &&mcallv, &&mcallh, &&ret }; std::vector code; for (const auto& i : gen.codes()) { code.push_back(oprs[i.op]); imm.push_back(i.num); } CHECK_INTERRUPT; // goto the first operand goto *code[ctx.pc]; #else std::vector code; for (const auto& i : gen.codes()) { code.push_back(operand_function[i.op]); imm.push_back(i.num); } while (code[ctx.pc]) { if (interrupt_ptr && interrupt_ptr->load()) { throw std::runtime_error("VM execution interrupted by timeout"); } (this->*code[ctx.pc])(); if (ctx.top>=ctx.canary) { die("stack overflow"); } ++ctx.pc; } #endif // all nasal programs should end here vmexit: if (verbose) { ngc.status.dump_info(); } imm.clear(); if (!is_repl_mode) { ngc.clear(); } return; #ifndef _MSC_VER // IR which may cause stackoverflow #define exec_check(op) {\ op();\ CHECK_INTERRUPT;\ if (ctx.top