Unified · Split
| @@ -93,13 +93,17 @@ namespace · 92 unmodified lines | |||
| 93 | if (args != nullptr) | 93 | if (args != nullptr) |
| 94 | mp_base[2] = args->get_allocation(); | 94 | mp_base[2] = args->get_allocation(); |
| 95 | 95 | ||
| 96 | // Define the frame offsets for the two arguments | ||
| 97 | const auto first_arg_offset = vm_frame_constants::limbo_first_arg_register_offset; | ||
| 98 | const auto second_arg_offset = first_arg_offset + vm_frame_constants::register_size_in_bytes; | ||
| 99 | |||
| 96 | e.code_section = | 100 | e.code_section = |
| 97 | { | 101 | { |
| 98 | { vm_exec_op_t | 102 | { vm_exec_op_t |
| 99 | { | 103 | { |
| 100 | opcode_t::load, | 104 | opcode_t::load, |
| 101 | { address_mode_t::offset_indirect_mp, 0, 0 }, // command module path | 105 | { address_mode_t::offset_indirect_mp, 0, 0 }, // command module path |
| 102 | { address_mode_middle_t::small_immediate, 0 }, | 106 | { address_mode_middle_t::small_immediate, 0 }, // import table index |
| 103 | { address_mode_t::offset_indirect_fp, 20, 0 } // module reference | 107 | { address_mode_t::offset_indirect_fp, 20, 0 } // module reference |
| 104 | } | 108 | } |
| 105 | }, | 109 | }, |
| @@ -107,7 +111,7 @@ namespace · 1 unmodified line | |||
| 107 | { | 111 | { |
| 108 | opcode_t::mframe, | 112 | opcode_t::mframe, |
| 109 | { address_mode_t::offset_indirect_fp, 20, 0 }, // module reference | 113 | { address_mode_t::offset_indirect_fp, 20, 0 }, // module reference |
| 110 | { address_mode_middle_t::small_immediate, 0 }, | 114 | { address_mode_middle_t::small_immediate, 0 }, // function index into module |
| 111 | { address_mode_t::offset_indirect_fp, 24, 0 } // module call frame | 115 | { address_mode_t::offset_indirect_fp, 24, 0 } // module call frame |
| 112 | } | 116 | } |
| 113 | }, | 117 | }, |
| @@ -116,7 +120,7 @@ namespace · 2 unmodified lines | |||
| 116 | opcode_t::movp, | 120 | opcode_t::movp, |
| 117 | { address_mode_t::offset_indirect_mp, 4, 0 }, // UI context | 121 | { address_mode_t::offset_indirect_mp, 4, 0 }, // UI context |
| 118 | { address_mode_middle_t::none }, | 122 | { address_mode_middle_t::none }, |
| 119 | { address_mode_t::offset_double_indirect_fp, 24, 32 } // module call frame -> UI context offset | 123 | { address_mode_t::offset_double_indirect_fp, 24, first_arg_offset } // module call frame -> UI context offset |
| 120 | } | 124 | } |
| 121 | }, | 125 | }, |
| 122 | { vm_exec_op_t | 126 | { vm_exec_op_t |
| @@ -124,14 +128,14 @@ namespace · 1 unmodified line | |||
| 124 | opcode_t::movp, | 128 | opcode_t::movp, |
| 125 | { address_mode_t::offset_indirect_mp, 8, 0 }, // Argument list | 129 | { address_mode_t::offset_indirect_mp, 8, 0 }, // Argument list |
| 126 | { address_mode_middle_t::none }, | 130 | { address_mode_middle_t::none }, |
| 127 | { address_mode_t::offset_double_indirect_fp, 24, 36 } // module call frame -> argument list offset | 131 | { address_mode_t::offset_double_indirect_fp, 24, second_arg_offset } // module call frame -> argument list offset |
| 128 | } | 132 | } |
| 129 | }, | 133 | }, |
| 130 | { vm_exec_op_t | 134 | { vm_exec_op_t |
| 131 | { | 135 | { |
| 132 | opcode_t::mcall, | 136 | opcode_t::mcall, |
| 133 | { address_mode_t::offset_indirect_fp, 24, 0 }, // module call frame | 137 | { address_mode_t::offset_indirect_fp, 24, 0 }, // module call frame |
| 134 | { address_mode_middle_t::small_immediate, 0 }, | 138 | { address_mode_middle_t::small_immediate, 0 }, // function index into module |
| 135 | { address_mode_t::offset_indirect_fp, 20, 0 } // module reference | 139 | { address_mode_t::offset_indirect_fp, 20, 0 } // module reference |
| 136 | } | 140 | } |
| 137 | }, | 141 | }, |
| @@ -66,9 +66,18 @@ namespace disvm · 65 unmodified lines | |||
| 66 | // Includes registers on base frame plus the implied return register | 66 | // Includes registers on base frame plus the implied return register |
| 67 | static const auto default_register_count = (sizeof(vm_frame_base_alloc_t) / register_size_in_bytes) + 1; | 67 | static const auto default_register_count = (sizeof(vm_frame_base_alloc_t) / register_size_in_bytes) + 1; |
| 68 | 68 | ||
| 69 | // See VM frame definition | 69 | // [SPEC] The fixed point registers are defined by their usage in the |
| 70 | static const auto fixed_point_register_1_frame_offset = default_register_count * register_size_in_bytes; | 70 | // Inferno implementation. They rely on the 'temp' registers defined below. |
| 71 | static const auto fixed_point_register_2_frame_offset = fixed_point_register_1_frame_offset + (2 * register_size_in_bytes); | 71 | // See fixed point op-codes in Inferno (libinterp/xec.c). |
| 72 | static const auto fixed_point_register_1_offset = default_register_count * register_size_in_bytes; | ||
| 73 | static const auto fixed_point_register_2_offset = fixed_point_register_1_offset + (2 * register_size_in_bytes); | ||
| 74 | |||
| 75 | // [SPEC] The official Limbo compiler allocates additional 'temp' registers on all call frames. | ||
| 76 | // See 'STemp', 'RTemp', and 'DTemp' in Inferno (limbo/limbo.h) | ||
| 77 | static const auto limbo_temp_register_count = 3; | ||
| 78 | |||
| 79 | // Define the first argument offset in a Limbo compiler generated call frame. | ||
| 80 | static const auto limbo_first_arg_register_offset = (default_register_count + limbo_temp_register_count) * register_size_in_bytes; | ||
| 72 | }; | 81 | }; |
| 73 | 82 | ||
| 74 | // | 83 | // |
| @@ -60,7 +60,7 @@ namespace disvm · 59 unmodified lines | |||
| 60 | { } | 60 | { } |
| 61 | }; | 61 | }; |
| 62 | 62 | ||
| 63 | class invalid_utf8 : public vm_user_exception | 63 | class invalid_utf8 final : public vm_user_exception |
| 64 | { | 64 | { |
| 65 | public: | 65 | public: |
| 66 | explicit invalid_utf8() | 66 | explicit invalid_utf8() |
| @@ -84,7 +84,7 @@ namespace disvm · 17 unmodified lines | |||
| 84 | const char *operation; | 84 | const char *operation; |
| 85 | }; | 85 | }; |
| 86 | 86 | ||
| 87 | class type_violation : public vm_user_exception | 87 | class type_violation final : public vm_user_exception |
| 88 | { | 88 | { |
| 89 | public: | 89 | public: |
| 90 | explicit type_violation() | 90 | explicit type_violation() |
| @@ -92,26 +92,23 @@ namespace disvm · 1 unmodified line | |||
| 92 | { } | 92 | { } |
| 93 | }; | 93 | }; |
| 94 | 94 | ||
| 95 | class out_of_range_memory final : public vm_user_exception | 95 | class out_of_range_memory : public vm_user_exception |
| 96 | { | 96 | { |
| 97 | public: | 97 | public: |
| 98 | explicit out_of_range_memory() | 98 | explicit out_of_range_memory() |
| 99 | : vm_user_exception("Out of range access") | 99 | : vm_user_exception("Out of range access") |
| 100 | , invalid_value{ 0 } | ||
| 101 | , range_defined{ false } | ||
| 102 | , valid_max{ 0 } | ||
| 103 | , valid_min{ 0 } | ||
| 104 | { } | 100 | { } |
| 101 | }; | ||
| 105 | 102 | ||
| 106 | explicit out_of_range_memory(word_t valid_min, word_t valid_max, word_t invalid_valid) | 103 | class index_out_of_range_memory final : public out_of_range_memory |
| 107 | : vm_user_exception("Out of range access") | 104 | { |
| 108 | , invalid_value{ invalid_valid } | 105 | public: |
| 109 | , range_defined{ true } | 106 | explicit index_out_of_range_memory(word_t valid_min, word_t valid_max, word_t invalid_value) |
| 107 | : invalid_value{ invalid_value } | ||
| 110 | , valid_max{ valid_max } | 108 | , valid_max{ valid_max } |
| 111 | , valid_min{ valid_min } | 109 | , valid_min{ valid_min } |
| 112 | { } | 110 | { } |
| 113 | 111 | ||
| 114 | const bool range_defined; | ||
| 115 | const word_t valid_min; | 112 | const word_t valid_min; |
| 116 | const word_t valid_max; | 113 | const word_t valid_max; |
| 117 | const word_t invalid_value; | 114 | const word_t invalid_value; |
| @@ -759,7 +759,7 @@ namespace disvm · 758 unmodified lines | |||
| 759 | 759 | ||
| 760 | ~vm_thread_t(); | 760 | ~vm_thread_t(); |
| 761 | 761 | ||
| 762 | vm_thread_state_t execute(vm_t &virtual_machine, uint32_t quanta); | 762 | vm_thread_state_t execute(vm_t &virtual_machine, const uint32_t quanta); |
| 763 | 763 | ||
| 764 | vm_thread_state_t get_state() const; | 764 | vm_thread_state_t get_state() const; |
| 765 | 765 | ||
| @@ -156,7 +156,7 @@ word_t vm_array_t::get_length() const · 155 unmodified lines | |||
| 156 | pointer_t vm_array_t::at(word_t index) const | 156 | pointer_t vm_array_t::at(word_t index) const |
| 157 | { | 157 | { |
| 158 | if (index < 0 || _length <= index) | 158 | if (index < 0 || _length <= index) |
| 159 | throw out_of_range_memory{ 0, _length - 1, index }; | 159 | throw index_out_of_range_memory{ 0, _length - 1, index }; |
| 160 | 160 | ||
| 161 | return reinterpret_cast<pointer_t>(_arr + (index * _element_type->size_in_bytes)); | 161 | return reinterpret_cast<pointer_t>(_arr + (index * _element_type->size_in_bytes)); |
| 162 | } | 162 | } |
| @@ -846,7 +846,7 @@ namespace · 845 unmodified lines | |||
| 846 | 846 | ||
| 847 | auto index = vt_ref<word_t>(r.dest); | 847 | auto index = vt_ref<word_t>(r.dest); |
| 848 | if (index < 0 || arr->get_length() <= index) | 848 | if (index < 0 || arr->get_length() <= index) |
| 849 | throw out_of_range_memory{ 0, arr->get_length(), index }; | 849 | throw index_out_of_range_memory{ 0, arr->get_length(), index }; |
| 850 | 850 | ||
| 851 | // Return the address, not the value | 851 | // Return the address, not the value |
| 852 | pt_ref(r.mid) = arr->at(index); | 852 | pt_ref(r.mid) = arr->at(index); |
| @@ -1577,6 +1577,10 @@ namespace · 724 unmodified lines | |||
| 1577 | 1577 | ||
| 1578 | const auto &function_ref = target_module_ref->get_function_ref(import_function_id); | 1578 | const auto &function_ref = target_module_ref->get_function_ref(import_function_id); |
| 1579 | 1579 | ||
| 1580 | // Spawning a built-in module is not permitted | ||
| 1581 | if (target_module_ref->is_builtin_module()) | ||
| 1582 | throw vm_user_exception{ "Spawning a built-in module is not permitted" }; | ||
| 1583 | |||
| 1580 | vm.fork(r.thread, *target_module_ref, *spawned_frame, function_ref.entry_pc); | 1584 | vm.fork(r.thread, *target_module_ref, *spawned_frame, function_ref.entry_pc); |
| 1581 | 1585 | ||
| 1582 | // After the new thread is forked pop off the argument passing frame. | 1586 | // After the new thread is forked pop off the argument passing frame. |
| @@ -1685,7 +1689,7 @@ namespace · 102 unmodified lines | |||
| 1685 | auto imported_module = std::shared_ptr<const vm_module_t>{}; | 1689 | auto imported_module = std::shared_ptr<const vm_module_t>{}; |
| 1686 | 1690 | ||
| 1687 | // Handle self loading module optimization. | 1691 | // Handle self loading module optimization. |
| 1688 | if (::strcmp(str->str(), "$self") == 0) | 1692 | if (std::strcmp(str->str(), "$self") == 0) |
| 1689 | { | 1693 | { |
| 1690 | imported_module = r.module_ref->module; | 1694 | imported_module = r.module_ref->module; |
| 1691 | } | 1695 | } |
| @@ -46,7 +46,7 @@ namespace disvm · 45 unmodified lines | |||
| 46 | std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override final; | 46 | std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override final; |
| 47 | 47 | ||
| 48 | private: | 48 | private: |
| 49 | struct thread_instance_t | 49 | struct thread_instance_t final |
| 50 | { | 50 | { |
| 51 | thread_instance_t(std::unique_ptr<vm_thread_t> t) | 51 | thread_instance_t(std::unique_ptr<vm_thread_t> t) |
| 52 | : vm_thread{ std::move(t) } | 52 | : vm_thread{ std::move(t) } |
| @@ -161,7 +161,7 @@ vm_module_ref_t *&vm_frame_t::prev_module_ref() const · 160 unmodified lines | |||
| 161 | 161 | ||
| 162 | pointer_t vm_frame_t::fixed_point_register_1() const | 162 | pointer_t vm_frame_t::fixed_point_register_1() const |
| 163 | { | 163 | { |
| 164 | const auto offset = vm_frame_constants::fixed_point_register_1_frame_offset; | 164 | const auto offset = vm_frame_constants::fixed_point_register_1_offset; |
| 165 | assert(offset < frame_type->size_in_bytes); | 165 | assert(offset < frame_type->size_in_bytes); |
| 166 | 166 | ||
| 167 | auto p = reinterpret_cast<uint8_t *>(base()) + offset; | 167 | auto p = reinterpret_cast<uint8_t *>(base()) + offset; |
| @@ -170,7 +170,7 @@ pointer_t vm_frame_t::fixed_point_register_1() const · 2 unmodified lines | |||
| 170 | 170 | ||
| 171 | pointer_t vm_frame_t::fixed_point_register_2() const | 171 | pointer_t vm_frame_t::fixed_point_register_2() const |
| 172 | { | 172 | { |
| 173 | const auto offset = vm_frame_constants::fixed_point_register_2_frame_offset; | 173 | const auto offset = vm_frame_constants::fixed_point_register_2_offset; |
| 174 | assert(offset < frame_type->size_in_bytes); | 174 | assert(offset < frame_type->size_in_bytes); |
| 175 | 175 | ||
| 176 | auto p = reinterpret_cast<uint8_t *>(base()) + offset; | 176 | auto p = reinterpret_cast<uint8_t *>(base()) + offset; |
| @@ -264,10 +264,10 @@ vm_string_t::vm_string_t(const vm_string_t &other, word_t begin_index, word_t en · 263 unmodified lines | |||
| 264 | throw out_of_range_memory{}; | 264 | throw out_of_range_memory{}; |
| 265 | 265 | ||
| 266 | if (begin_index < 0) | 266 | if (begin_index < 0) |
| 267 | throw out_of_range_memory{ 0, other._length, begin_index }; | 267 | throw index_out_of_range_memory{ 0, other._length, begin_index }; |
| 268 | 268 | ||
| 269 | if (other._length < end_index) | 269 | if (other._length < end_index) |
| 270 | throw out_of_range_memory{ 0, other._length, end_index }; | 270 | throw index_out_of_range_memory{ 0, other._length, end_index }; |
| 271 | 271 | ||
| 272 | // Allocate memory based on character count and size | 272 | // Allocate memory based on character count and size |
| 273 | auto src = other._mem.local; | 273 | auto src = other._mem.local; |
| @@ -384,7 +384,7 @@ word_t vm_string_t::get_length() const · 110 unmodified lines | |||
| 384 | rune_t vm_string_t::get_rune(word_t index) const | 384 | rune_t vm_string_t::get_rune(word_t index) const |
| 385 | { | 385 | { |
| 386 | if (index < 0 && _length <= index) | 386 | if (index < 0 && _length <= index) |
| 387 | throw out_of_range_memory{ 0, _length - 1, index }; | 387 | throw index_out_of_range_memory{ 0, _length - 1, index }; |
| 388 | 388 | ||
| 389 | // Determine if local or allocated memory | 389 | // Determine if local or allocated memory |
| 390 | auto source = reinterpret_cast<const char *>(_mem.local); | 390 | auto source = reinterpret_cast<const char *>(_mem.local); |
| @@ -410,7 +410,7 @@ void vm_string_t::set_rune(word_t index, rune_t value) · 19 unmodified lines | |||
| 410 | 410 | ||
| 411 | // Index equal to length is allowed to grow the string | 411 | // Index equal to length is allowed to grow the string |
| 412 | if (index < 0 && _length <= index) | 412 | if (index < 0 && _length <= index) |
| 413 | throw out_of_range_memory{ 0, _length, index }; | 413 | throw index_out_of_range_memory{ 0, _length, index }; |
| 414 | 414 | ||
| 415 | // Determine if local or allocated memory | 415 | // Determine if local or allocated memory |
| 416 | auto source = static_cast<uint8_t *>(_mem.local); | 416 | auto source = static_cast<uint8_t *>(_mem.local); |
| @@ -117,10 +117,9 @@ namespace · 116 unmodified lines | |||
| 117 | #endif | 117 | #endif |
| 118 | } | 118 | } |
| 119 | 119 | ||
| 120 | std::atomic<uint32_t> thread_id_counter(1); | ||
| 121 | |||
| 122 | uint32_t get_unique_thread_id() | 120 | uint32_t get_unique_thread_id() |
| 123 | { | 121 | { |
| 122 | static std::atomic<uint32_t> thread_id_counter{ 1 }; | ||
| 124 | return thread_id_counter.fetch_add(1); | 123 | return thread_id_counter.fetch_add(1); |
| 125 | } | 124 | } |
| 126 | } | 125 | } |
| @@ -206,15 +205,17 @@ vm_thread_t::vm_thread_t( · 79 unmodified lines | |||
| 206 | // Copy over the frame into this thread - pass arguments to the thread | 205 | // Copy over the frame into this thread - pass arguments to the thread |
| 207 | current_frame->copy_frame_contents(initial_frame); | 206 | current_frame->copy_frame_contents(initial_frame); |
| 208 | 207 | ||
| 209 | debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "init: vm thread: %d %d\n", _thread_id, _parent_thread_id); | 208 | if (debug::is_component_tracing_enabled<debug::component_trace_t::thread>()) |
| 209 | debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "init: vm thread: %d %d\n", _thread_id, _parent_thread_id); | ||
| 210 | } | 210 | } |
| 211 | 211 | ||
| 212 | vm_thread_t::~vm_thread_t() | 212 | vm_thread_t::~vm_thread_t() |
| 213 | { | 213 | { |
| 214 | debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "destroy: vm thread: %d %d\n", _thread_id, _parent_thread_id); | 214 | if (debug::is_component_tracing_enabled<debug::component_trace_t::thread>()) |
| 215 | debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "destroy: vm thread: %d %d\n", _thread_id, _parent_thread_id); | ||
| 215 | } | 216 | } |
| 216 | 217 | ||
| 217 | vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, uint32_t quanta) | 218 | vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, const uint32_t quanta) |
| 218 | { | 219 | { |
| 219 | _registers.current_thread_state = vm_thread_state_t::running; | 220 | _registers.current_thread_state = vm_thread_state_t::running; |
| 220 | assert(_registers.pc != runtime_constants::invalid_program_counter); | 221 | assert(_registers.pc != runtime_constants::invalid_program_counter); |
| @@ -236,9 +237,13 @@ vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, uint32_t quanta) · 15 unmodified lines | |||
| 236 | assert(static_cast<std::size_t>(_registers.pc) < code_section.size()); | 237 | assert(static_cast<std::size_t>(_registers.pc) < code_section.size()); |
| 237 | 238 | ||
| 238 | const auto &inst = code_section[_registers.pc].op; | 239 | const auto &inst = code_section[_registers.pc].op; |
| 240 | const auto op_code = static_cast<std::size_t>(inst.opcode); | ||
| 241 | if (static_cast<std::size_t>(opcode_t::last_opcode) < op_code) | ||
| 242 | throw vm_system_exception{ "Unknown op-code" }; | ||
| 243 | |||
| 239 | decode_address(inst, _registers); | 244 | decode_address(inst, _registers); |
| 240 | _registers.pc++; | 245 | _registers.pc++; |
| 241 | vm_exec_table[static_cast<std::size_t>(inst.opcode)](_registers, virtual_machine); | 246 | vm_exec_table[op_code](_registers, virtual_machine); |
| 242 | 247 | ||
| 243 | if (_registers.current_thread_state != vm_thread_state_t::running) | 248 | if (_registers.current_thread_state != vm_thread_state_t::running) |
| 244 | break; | 249 | break; |
| @@ -247,13 +252,17 @@ vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, uint32_t quanta) · 2 unmodified lines | |||
| 247 | catch (const unhandled_user_exception &uue) | 252 | catch (const unhandled_user_exception &uue) |
| 248 | { | 253 | { |
| 249 | // [TODO] Store the exception | 254 | // [TODO] Store the exception |
| 250 | debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "broken: vm thread: unhandled: >>%s<< %d >>%s<<\n", uue.exception_id, uue.program_counter, uue.module_name); | 255 | std::printf("%s - %s in %s at %d\n", uue.what(), uue.exception_id, uue.module_name, uue.program_counter); |
| 256 | _registers.current_thread_state = vm_thread_state_t::broken; | ||
| 257 | } | ||
| 258 | catch (const index_out_of_range_memory &ioor) | ||
| 259 | { | ||
| 260 | std::printf("%s - %d [%d,%d]\n", ioor.what(), ioor.invalid_value, ioor.valid_min, ioor.valid_max); | ||
| 251 | _registers.current_thread_state = vm_thread_state_t::broken; | 261 | _registers.current_thread_state = vm_thread_state_t::broken; |
| 252 | } | 262 | } |
| 253 | catch (const vm_user_exception &ue) | 263 | catch (const vm_user_exception &ue) |
| 254 | { | 264 | { |
| 255 | // [TODO] Store the exception | 265 | std::printf("%s\n", ue.what()); |
| 256 | debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "broken: vm thread: >>%s<<\n", ue.what()); | ||
| 257 | _registers.current_thread_state = vm_thread_state_t::broken; | 266 | _registers.current_thread_state = vm_thread_state_t::broken; |
| 258 | } | 267 | } |
| 259 | 268 | ||