Showing the changes to src/vm/vm.cpp (1 of 17 files). Show all
Unified · Split
| @@ -7,6 +7,7 @@ · 6 unmodified lines | ||
| 7 | 7 | #include <cassert> |
| 8 | 8 | #include <memory> |
| 9 | 9 | #include <algorithm> |
| 10 | +#include <sstream> | |
| 10 | 11 | #include <disvm.h> |
| 11 | 12 | #include <debug.h> |
| 12 | 13 | #include <runtime.h> |
| @@ -16,41 +17,107 @@ · 3 unmodified lines | ||
| 16 | 17 | #include "scheduler.h" |
| 17 | 18 | #include "garbage_collector.h" |
| 18 | 19 | #include "tool_dispatch.h" |
| 20 | +#include "module_resolver.h" | |
| 19 | 21 | |
| 20 | 22 | using namespace disvm; |
| 21 | 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 | 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 | 73 | // The Inferno implementation defined the thread quanta as 2048 (include/interp.h) |
| 27 | −const uint32_t vm_t::default_thread_quanta = 2048; | |
| 28 | − | |
| 29 | −vm_t::vm_t( | |
| 30 | − create_vm_interface_callback_t<runtime::vm_scheduler_t> create_scheduler, | |
| 31 | − create_vm_interface_callback_t<runtime::vm_garbage_collector_t> create_gc) | |
| 74 | +const uint32_t default_thread_quanta = 2048; | |
| 75 | +const uint32_t default_system_thread_count = 1; | |
| 76 | + | |
| 77 | +vm_config_t::vm_config_t() | |
| 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 | 96 | // Initialize built-in modules. |
| 34 | 97 | builtin::initialize_builtin_modules(); |
| 35 | 98 | |
| 36 | − if (create_gc == nullptr) | |
| 37 | − _gc = std::make_unique<default_garbage_collector_t>(*this); | |
| 38 | − else | |
| 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); | |
| 99 | + _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); | |
| 101 | + _module_resolvers.push_back(std::make_unique<default_resolver_t>(*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 | 106 | // Initialize built-in modules. |
| 50 | 107 | builtin::initialize_builtin_modules(); |
| 51 | 108 | |
| 52 | − _gc = std::make_unique<default_garbage_collector_t>(*this); | |
| 53 | − _scheduler = std::make_unique<default_scheduler_t>(*this, system_thread_count, thread_quanta); | |
| 109 | + if (config.create_gc == nullptr) | |
| 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 | 123 | vm_t::~vm_t() |
| @@ -74,7 +141,7 @@ vm_version_t vm_t::get_version() const · 17 unmodified lines | ||
| 74 | 141 | |
| 75 | 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 | 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 | 159 | auto entry_module = load_module(path); |
| 93 | 160 | |
| 94 | 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 | 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 | 168 | assert(entry_module != nullptr); |
| 102 | 169 | |
| 103 | 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 | 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 | 179 | runtime::vm_pc_t initial_pc) |
| 113 | 180 | { |
| 114 | 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 | 184 | auto thread = std::make_unique<vm_thread_t>(module_ref, parent_tid, initial_frame, initial_pc); |
| 118 | 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 | 240 | std::shared_ptr<runtime::vm_module_t> vm_t::load_module(const char *path) |
| 122 | 241 | { |
| 123 | 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 | 258 | // If an entry exists but is null, the module was loaded before. Read the module again and return. |
| 140 | 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 | 262 | module->vm_id = iter->vm_id; |
| 144 | 263 | |
| 145 | 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 | 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 | 280 | else |
| 161 | 281 | { |
| 162 | − // Read in the module | |
| 163 | − new_module = std::move(read_module(path)); | |
| 282 | + new_module = resolve_module_from_path(path, _module_resolvers); | |
| 283 | + assert(new_module != nullptr); | |
| 164 | 284 | } |
| 165 | 285 | |
| 166 | 286 | auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path)); |