Unified · Split
| @@ -93,13 +93,17 @@ namespace · 92 unmodified lines | ||
| 93 | 93 | if (args != nullptr) |
| 94 | 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 | 100 | e.code_section = |
| 97 | 101 | { |
| 98 | 102 | { vm_exec_op_t |
| 99 | 103 | { |
| 100 | 104 | opcode_t::load, |
| 101 | 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 | 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 | 112 | opcode_t::mframe, |
| 109 | 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 | 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 | 120 | opcode_t::movp, |
| 117 | 121 | { address_mode_t::offset_indirect_mp, 4, 0 }, // UI context |
| 118 | 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 | 126 | { vm_exec_op_t |
| @@ -124,14 +128,14 @@ namespace · 1 unmodified line | ||
| 124 | 128 | opcode_t::movp, |
| 125 | 129 | { address_mode_t::offset_indirect_mp, 8, 0 }, // Argument list |
| 126 | 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 | 134 | { vm_exec_op_t |
| 131 | 135 | { |
| 132 | 136 | opcode_t::mcall, |
| 133 | 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 | 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 | 66 | // Includes registers on base frame plus the implied return register |
| 67 | 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 | |
| 70 | − static const auto fixed_point_register_1_frame_offset = default_register_count * register_size_in_bytes; | |
| 71 | − static const auto fixed_point_register_2_frame_offset = fixed_point_register_1_frame_offset + (2 * register_size_in_bytes); | |
| 69 | + // [SPEC] The fixed point registers are defined by their usage in the | |
| 70 | + // Inferno implementation. They rely on the 'temp' registers defined below. | |
| 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 | 65 | public: |
| 66 | 66 | explicit invalid_utf8() |
| @@ -84,7 +84,7 @@ namespace disvm · 17 unmodified lines | ||
| 84 | 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 | 89 | public: |
| 90 | 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 | 97 | public: |
| 98 | 98 | explicit out_of_range_memory() |
| 99 | 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) | |
| 107 | − : vm_user_exception("Out of range access") | |
| 108 | − , invalid_value{ invalid_valid } | |
| 109 | − , range_defined{ true } | |
| 103 | + class index_out_of_range_memory final : public out_of_range_memory | |
| 104 | + { | |
| 105 | + public: | |
| 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 | 108 | , valid_max{ valid_max } |
| 111 | 109 | , valid_min{ valid_min } |
| 112 | 110 | { } |
| 113 | 111 | |
| 114 | − const bool range_defined; | |
| 115 | 112 | const word_t valid_min; |
| 116 | 113 | const word_t valid_max; |
| 117 | 114 | const word_t invalid_value; |
| @@ -759,7 +759,7 @@ namespace disvm · 758 unmodified lines | ||
| 759 | 759 | |
| 760 | 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 | 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 | 156 | pointer_t vm_array_t::at(word_t index) const |
| 157 | 157 | { |
| 158 | 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 | 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 | 847 | auto index = vt_ref<word_t>(r.dest); |
| 848 | 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 | 851 | // Return the address, not the value |
| 852 | 852 | pt_ref(r.mid) = arr->at(index); |
| @@ -1577,6 +1577,10 @@ namespace · 724 unmodified lines | ||
| 1577 | 1577 | |
| 1578 | 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 | 1584 | vm.fork(r.thread, *target_module_ref, *spawned_frame, function_ref.entry_pc); |
| 1581 | 1585 | |
| 1582 | 1586 | // After the new thread is forked pop off the argument passing frame. |
| @@ -1685,7 +1689,7 @@ namespace · 102 unmodified lines | ||
| 1685 | 1689 | auto imported_module = std::shared_ptr<const vm_module_t>{}; |
| 1686 | 1690 | |
| 1687 | 1691 | // Handle self loading module optimization. |
| 1688 | − if (::strcmp(str->str(), "$self") == 0) | |
| 1692 | + if (std::strcmp(str->str(), "$self") == 0) | |
| 1689 | 1693 | { |
| 1690 | 1694 | imported_module = r.module_ref->module; |
| 1691 | 1695 | } |
| @@ -46,7 +46,7 @@ namespace disvm · 45 unmodified lines | ||
| 46 | 46 | std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override final; |
| 47 | 47 | |
| 48 | 48 | private: |
| 49 | − struct thread_instance_t | |
| 49 | + struct thread_instance_t final | |
| 50 | 50 | { |
| 51 | 51 | thread_instance_t(std::unique_ptr<vm_thread_t> t) |
| 52 | 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 | 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 | 165 | assert(offset < frame_type->size_in_bytes); |
| 166 | 166 | |
| 167 | 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 | 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 | 174 | assert(offset < frame_type->size_in_bytes); |
| 175 | 175 | |
| 176 | 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 | 264 | throw out_of_range_memory{}; |
| 265 | 265 | |
| 266 | 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 | 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 | 272 | // Allocate memory based on character count and size |
| 273 | 273 | auto src = other._mem.local; |
| @@ -384,7 +384,7 @@ word_t vm_string_t::get_length() const · 110 unmodified lines | ||
| 384 | 384 | rune_t vm_string_t::get_rune(word_t index) const |
| 385 | 385 | { |
| 386 | 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 | 389 | // Determine if local or allocated memory |
| 390 | 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 | 411 | // Index equal to length is allowed to grow the string |
| 412 | 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 | 415 | // Determine if local or allocated memory |
| 416 | 416 | auto source = static_cast<uint8_t *>(_mem.local); |
| @@ -117,10 +117,9 @@ namespace · 116 unmodified lines | ||
| 117 | 117 | #endif |
| 118 | 118 | } |
| 119 | 119 | |
| 120 | − std::atomic<uint32_t> thread_id_counter(1); | |
| 121 | − | |
| 122 | 120 | uint32_t get_unique_thread_id() |
| 123 | 121 | { |
| 122 | + static std::atomic<uint32_t> thread_id_counter{ 1 }; | |
| 124 | 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 | 205 | // Copy over the frame into this thread - pass arguments to the thread |
| 207 | 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 | 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 | 220 | _registers.current_thread_state = vm_thread_state_t::running; |
| 220 | 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 | 237 | assert(static_cast<std::size_t>(_registers.pc) < code_section.size()); |
| 237 | 238 | |
| 238 | 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 | 244 | decode_address(inst, _registers); |
| 240 | 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 | 248 | if (_registers.current_thread_state != vm_thread_state_t::running) |
| 244 | 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 | 252 | catch (const unhandled_user_exception &uue) |
| 248 | 253 | { |
| 249 | 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 | 261 | _registers.current_thread_state = vm_thread_state_t::broken; |
| 252 | 262 | } |
| 253 | 263 | catch (const vm_user_exception &ue) |
| 254 | 264 | { |
| 255 | − // [TODO] Store the exception | |
| 256 | − debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "broken: vm thread: >>%s<<\n", ue.what()); | |
| 265 | + std::printf("%s\n", ue.what()); | |
| 257 | 266 | _registers.current_thread_state = vm_thread_state_t::broken; |
| 258 | 267 | } |
| 259 | 268 | |