Showing the changes to src/vm/vm.cpp (1 of 17 files). Show all
Unified · Split
| @@ -7,6 +7,7 @@ · 6 unmodified lines | |||
| 7 | #include <cassert> | 7 | #include <cassert> |
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <algorithm> | 9 | #include <algorithm> |
| 10 | #include <sstream> | ||
| 10 | #include <disvm.h> | 11 | #include <disvm.h> |
| 11 | #include <debug.h> | 12 | #include <debug.h> |
| 12 | #include <runtime.h> | 13 | #include <runtime.h> |
| @@ -16,41 +17,107 @@ · 3 unmodified lines | |||
| 16 | #include "scheduler.h" | 17 | #include "scheduler.h" |
| 17 | #include "garbage_collector.h" | 18 | #include "garbage_collector.h" |
| 18 | #include "tool_dispatch.h" | 19 | #include "tool_dispatch.h" |
| 20 | #include "module_resolver.h" | ||
| 19 | 21 | ||
| 20 | using namespace disvm; | 22 | using namespace disvm; |
| 21 | using namespace disvm::runtime; | 23 | using namespace disvm::runtime; |
| 22 | 24 | ||
| 25 | namespace | ||
| 26 | { | ||
| 27 | // Ideally this would be custom assembly based on the CPU, | ||
| 28 | // but since this is just for the setting of the last error | ||
| 29 | // message there is already a large cost associated with the | ||
| 30 | // fact an error occurred so this is okay. | ||
| 31 | struct inefficient_spin_lock_t final | ||
| 32 | { | ||
| 33 | inefficient_spin_lock_t(std::atomic_flag &is_lock) | ||
| 34 | : _lf{ is_lock } | ||
| 35 | { | ||
| 36 | while (!_lf.test_and_set()) | ||
| 37 | ; | ||
| 38 | } | ||
| 39 | |||
| 40 | ~inefficient_spin_lock_t() | ||
| 41 | { | ||
| 42 | _lf.clear(); | ||
| 43 | } | ||
| 44 | |||
| 45 | private: | ||
| 46 | std::atomic_flag &_lf; | ||
| 47 | }; | ||
| 48 | } | ||
| 49 | |||
| 50 | void disvm::runtime::push_syscall_error_message(disvm::vm_t &vm, const char *msg) noexcept | ||
| 51 | { | ||
| 52 | inefficient_spin_lock_t sl{ vm._last_syscall_error_message_lock }; | ||
| 53 | |||
| 54 | assert(msg != nullptr); | ||
| 55 | auto bytes_to_copy = std::min(std::strlen(msg), vm._last_syscall_error_message.size() - 1); | ||
| 56 | std::memcpy(vm._last_syscall_error_message.data(), msg, bytes_to_copy); | ||
| 57 | |||
| 58 | // Ensure there is always a null | ||
| 59 | vm._last_syscall_error_message[bytes_to_copy] = '\0'; | ||
| 60 | } | ||
| 61 | |||
| 62 | std::string disvm::runtime::pop_syscall_error_message(disvm::vm_t &vm) | ||
| 63 | { | ||
| 64 | inefficient_spin_lock_t sl{ vm._last_syscall_error_message_lock }; | ||
| 65 | |||
| 66 | std::string msg{ vm._last_syscall_error_message.data() }; | ||
| 67 | vm._last_syscall_error_message[0] = '\0'; | ||
| 68 | return msg; | ||
| 69 | } | ||
| 70 | |||
| 23 | const uint32_t vm_t::root_vm_thread_id = 0; | 71 | const uint32_t vm_t::root_vm_thread_id = 0; |
| 24 | const uint32_t vm_t::default_system_thread_count = 1; | ||
| 25 | 72 | ||
| 26 | // The Inferno implementation defined the thread quanta as 2048 (include/interp.h) | 73 | // The Inferno implementation defined the thread quanta as 2048 (include/interp.h) |
| 27 | const uint32_t vm_t::default_thread_quanta = 2048; | 74 | const uint32_t default_thread_quanta = 2048; |
| 28 | 75 | const uint32_t default_system_thread_count = 1; | |
| 29 | vm_t::vm_t( | 76 | |
| 30 | create_vm_interface_callback_t<runtime::vm_scheduler_t> create_scheduler, | 77 | vm_config_t::vm_config_t() |
| 31 | create_vm_interface_callback_t<runtime::vm_garbage_collector_t> create_gc) | 78 | : create_gc{ nullptr } |
| 79 | , create_scheduler{ nullptr } | ||
| 80 | , sys_thread_pool_size{ default_system_thread_count } | ||
| 81 | , thread_quanta{ default_thread_quanta } | ||
| 82 | { } | ||
| 83 | |||
| 84 | vm_config_t::vm_config_t(vm_config_t &&other) | ||
| 85 | : additional_resolvers{ std::move(other.additional_resolvers) } | ||
| 86 | , create_gc{ other.create_gc } | ||
| 87 | , create_scheduler{ other.create_scheduler } | ||
| 88 | , probing_paths{ std::move(other.probing_paths) } | ||
| 89 | , sys_thread_pool_size{ other.sys_thread_pool_size } | ||
| 90 | , thread_quanta{ other.thread_quanta } | ||
| 91 | { } | ||
| 92 | |||
| 93 | vm_t::vm_t() | ||
| 94 | : _last_syscall_error_message_lock{ ATOMIC_FLAG_INIT } | ||
| 32 | { | 95 | { |
| 33 | // Initialize built-in modules. | 96 | // Initialize built-in modules. |
| 34 | builtin::initialize_builtin_modules(); | 97 | builtin::initialize_builtin_modules(); |
| 35 | 98 | ||
| 36 | if (create_gc == nullptr) | 99 | _gc = std::make_unique<default_garbage_collector_t>(*this); |
| 37 | _gc = std::make_unique<default_garbage_collector_t>(*this); | 100 | _scheduler = std::make_unique<default_scheduler_t>(*this, default_system_thread_count, default_thread_quanta); |
| 38 | else | 101 | _module_resolvers.push_back(std::make_unique<default_resolver_t>(*this)); |
| 39 | _gc = create_gc(*this); | ||
| 40 | |||
| 41 | if (create_scheduler == nullptr) | ||
| 42 | _scheduler = std::make_unique<default_scheduler_t>(*this, default_system_thread_count, default_thread_quanta); | ||
| 43 | else | ||
| 44 | _scheduler = create_scheduler(*this); | ||
| 45 | } | 102 | } |
| 46 | 103 | ||
| 47 | vm_t::vm_t(uint32_t system_thread_count, uint32_t thread_quanta) | 104 | vm_t::vm_t(vm_config_t config) |
| 48 | { | 105 | { |
| 49 | // Initialize built-in modules. | 106 | // Initialize built-in modules. |
| 50 | builtin::initialize_builtin_modules(); | 107 | builtin::initialize_builtin_modules(); |
| 51 | 108 | ||
| 52 | _gc = std::make_unique<default_garbage_collector_t>(*this); | 109 | if (config.create_gc == nullptr) |
| 53 | _scheduler = std::make_unique<default_scheduler_t>(*this, system_thread_count, thread_quanta); | 110 | _gc = std::make_unique<default_garbage_collector_t>(*this); |
| 111 | else | ||
| 112 | _gc = config.create_gc(*this); | ||
| 113 | |||
| 114 | if (config.create_scheduler == nullptr) | ||
| 115 | _scheduler = std::make_unique<default_scheduler_t>(*this, config.sys_thread_pool_size, config.thread_quanta); | ||
| 116 | else | ||
| 117 | _scheduler = config.create_scheduler(*this); | ||
| 118 | |||
| 119 | _module_resolvers = std::move(config.additional_resolvers); | ||
| 120 | _module_resolvers.push_back(std::make_unique<default_resolver_t>(*this, std::move(config.probing_paths))); | ||
| 54 | } | 121 | } |
| 55 | 122 | ||
| 56 | vm_t::~vm_t() | 123 | vm_t::~vm_t() |
| @@ -74,7 +141,7 @@ vm_version_t vm_t::get_version() const · 17 unmodified lines | |||
| 74 | 141 | ||
| 75 | namespace | 142 | namespace |
| 76 | { | 143 | { |
| 77 | std::unique_ptr<runtime::vm_thread_t> _create_thread_safe(std::unique_ptr<runtime::vm_module_ref_t> &entry_module_ref) | 144 | std::unique_ptr<runtime::vm_thread_t> _create_thread_safe(std::unique_ptr<runtime::vm_module_ref_t> entry_module_ref) |
| 78 | { | 145 | { |
| 79 | auto thread = std::make_unique<vm_thread_t>(*entry_module_ref, disvm::vm_t::root_vm_thread_id); | 146 | auto thread = std::make_unique<vm_thread_t>(*entry_module_ref, disvm::vm_t::root_vm_thread_id); |
| 80 | 147 | ||
| @@ -92,7 +159,7 @@ const runtime::vm_thread_t& vm_t::exec(const char *path) · 11 unmodified lines | |||
| 92 | auto entry_module = load_module(path); | 159 | auto entry_module = load_module(path); |
| 93 | 160 | ||
| 94 | auto entry_module_ref = std::make_unique<vm_module_ref_t>(entry_module); | 161 | auto entry_module_ref = std::make_unique<vm_module_ref_t>(entry_module); |
| 95 | auto thread = _create_thread_safe(entry_module_ref); | 162 | auto thread = _create_thread_safe(std::move(entry_module_ref)); |
| 96 | return schedule_thread(std::move(thread)); | 163 | return schedule_thread(std::move(thread)); |
| 97 | } | 164 | } |
| 98 | 165 | ||
| @@ -101,7 +168,7 @@ const runtime::vm_thread_t& vm_t::exec(std::unique_ptr<runtime::vm_module_t> ent · 2 unmodified lines | |||
| 101 | assert(entry_module != nullptr); | 168 | assert(entry_module != nullptr); |
| 102 | 169 | ||
| 103 | auto entry_module_ref = std::make_unique<vm_module_ref_t>(std::move(entry_module)); | 170 | auto entry_module_ref = std::make_unique<vm_module_ref_t>(std::move(entry_module)); |
| 104 | auto thread = _create_thread_safe(entry_module_ref); | 171 | auto thread = _create_thread_safe(std::move(entry_module_ref)); |
| 105 | return schedule_thread(std::move(thread)); | 172 | return schedule_thread(std::move(thread)); |
| 106 | } | 173 | } |
| 107 | 174 | ||
| @@ -112,12 +179,64 @@ const runtime::vm_thread_t& vm_t::fork( · 4 unmodified lines | |||
| 112 | runtime::vm_pc_t initial_pc) | 179 | runtime::vm_pc_t initial_pc) |
| 113 | { | 180 | { |
| 114 | if (initial_pc < 0 || static_cast<vm_pc_t>(module_ref.code_section.size()) <= initial_pc) | 181 | if (initial_pc < 0 || static_cast<vm_pc_t>(module_ref.code_section.size()) <= initial_pc) |
| 115 | throw vm_system_exception{ "Invalid entry program counter" }; | 182 | throw vm_user_exception{ "Invalid entry program counter" }; |
| 116 | 183 | ||
| 117 | auto thread = std::make_unique<vm_thread_t>(module_ref, parent_tid, initial_frame, initial_pc); | 184 | auto thread = std::make_unique<vm_thread_t>(module_ref, parent_tid, initial_frame, initial_pc); |
| 118 | return schedule_thread(std::move(thread)); | 185 | return schedule_thread(std::move(thread)); |
| 119 | } | 186 | } |
| 120 | 187 | ||
| 188 | namespace | ||
| 189 | { | ||
| 190 | std::unique_ptr<vm_module_t> resolve_module_from_path(const char *path, const std::vector<std::unique_ptr<runtime::vm_module_resolver_t>> &resolvers) | ||
| 191 | { | ||
| 192 | assert(!resolvers.empty()); | ||
| 193 | std::unique_ptr<vm_module_t> resolved_module; | ||
| 194 | |||
| 195 | // Check if the path is for the Inferno OS. | ||
| 196 | const char *inferno_root_path = "/dis/"; | ||
| 197 | if (std::strncmp(path, inferno_root_path, sizeof(inferno_root_path)) == 0) | ||
| 198 | { | ||
| 199 | if (debug::is_component_tracing_enabled<debug::component_trace_t::module>()) | ||
| 200 | debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "load: vm module: Inferno OS path detected - >>%s<<", path); | ||
| 201 | |||
| 202 | // Plus 1 for the next character. We know this will either be | ||
| 203 | // null or valid character since we searched for a string | ||
| 204 | // containing the character above. | ||
| 205 | const char *module_name = std::strrchr(path, '/') + 1; | ||
| 206 | |||
| 207 | if (*module_name != '\0') | ||
| 208 | { | ||
| 209 | for (auto &r : resolvers) | ||
| 210 | { | ||
| 211 | // Since we are using a modified path, throwing an exception | ||
| 212 | // isn't fair. We will catch all exceptions here and rely on | ||
| 213 | // non-modified path resolution to trigger the actual failure. | ||
| 214 | try | ||
| 215 | { | ||
| 216 | if (r->try_resolve_module(module_name, resolved_module)) | ||
| 217 | return resolved_module; | ||
| 218 | } | ||
| 219 | catch (...) | ||
| 220 | { | ||
| 221 | // No-op | ||
| 222 | } | ||
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | // Attempt to resolve the path | ||
| 228 | for (auto &r : resolvers) | ||
| 229 | { | ||
| 230 | if (r->try_resolve_module(path, resolved_module)) | ||
| 231 | return resolved_module; | ||
| 232 | } | ||
| 233 | |||
| 234 | std::stringstream ss; | ||
| 235 | ss << "Failed to resolve path to module: " << path; | ||
| 236 | throw vm_module_exception{ ss.str().c_str() }; | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 121 | std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) | 240 | std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) |
| 122 | { | 241 | { |
| 123 | if (path == nullptr) | 242 | if (path == nullptr) |
| @@ -139,12 +258,13 @@ std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) · 15 unmodified lines | |||
| 139 | // If an entry exists but is null, the module was loaded before. Read the module again and return. | 258 | // If an entry exists but is null, the module was loaded before. Read the module again and return. |
| 140 | if (module == nullptr) | 259 | if (module == nullptr) |
| 141 | { | 260 | { |
| 142 | module = std::shared_ptr<vm_module_t>{ std::move(read_module(path)) }; | 261 | module = std::shared_ptr<vm_module_t>{ std::move(resolve_module_from_path(path, _module_resolvers)) }; |
| 143 | module->vm_id = iter->vm_id; | 262 | module->vm_id = iter->vm_id; |
| 144 | 263 | ||
| 145 | iter->module = module; | 264 | iter->module = module; |
| 146 | 265 | ||
| 147 | debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<", path); | 266 | if (debug::is_component_tracing_enabled<debug::component_trace_t::module>()) |
| 267 | debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<", path); | ||
| 148 | } | 268 | } |
| 149 | 269 | ||
| 150 | return module; | 270 | return module; |
| @@ -159,8 +279,8 @@ std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) · 8 unmodified lines | |||
| 159 | } | 279 | } |
| 160 | else | 280 | else |
| 161 | { | 281 | { |
| 162 | // Read in the module | 282 | new_module = resolve_module_from_path(path, _module_resolvers); |
| 163 | new_module = std::move(read_module(path)); | 283 | assert(new_module != nullptr); |
| 164 | } | 284 | } |
| 165 | 285 | ||
| 166 | auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path)); | 286 | auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path)); |