Unified · Split
| @@ -0,0 +1,23 @@ | ||
| 1 | +# Ignore VS noise | |
| 2 | +Debug | |
| 3 | +Release | |
| 4 | +*suo | |
| 5 | +*filters | |
| 6 | +*sdf | |
| 7 | +*sln | |
| 8 | +*proj | |
| 9 | +*.VC.opendb | |
| 10 | +*.db | |
| 11 | +*.user | |
| 12 | + | |
| 13 | +# Ignore all log files | |
| 14 | +*.log | |
| 15 | + | |
| 16 | +# Ignore VIM temporary files | |
| 17 | +*~ | |
| 18 | +*.swp | |
| 19 | + | |
| 20 | +# Ignore limbo binary files | |
| 21 | +*.dis | |
| 22 | +*.s | |
| 23 | +*.sbl | |
| @@ -0,0 +1,21 @@ | ||
| 1 | +Dis VM Project Overview | |
| 2 | +======================== | |
| 3 | + | |
| 4 | +# Build instructions | |
| 5 | + | |
| 6 | +Dis VM build scripts can be generated using gyp (Generate-Your-Project) | |
| 7 | + | |
| 8 | +### Requirements | |
| 9 | + * gyp - http://gyp.gsrc.io | |
| 10 | + * Python 2.7 - http://www.python.org/download/releases/2.7/ | |
| 11 | + | |
| 12 | +1. Install Python 2.7 | |
| 13 | +2. Get the current source for gyp | |
| 14 | +3. Generate build file for your platform | |
| 15 | + * Windows - gyp.bat src/sh/sh.gyp | |
| 16 | + * Other - gyp_main.py src/sh/sh.gyp | |
| 17 | + | |
| 18 | +# Source Comment Definitions: | |
| 19 | +[TODO] - Work to be done in the future. | |
| 20 | +[SPEC] - A departure or undocumented assumption in the Dis VM specification. | |
| 21 | +[PERF] - Potential for performance improvement. | |
| @@ -0,0 +1,66 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: builtin_module.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_BUILTIN_MODULE_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_BUILTIN_MODULE_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include <memory> | |
| 12 | +#include "runtime.h" | |
| 13 | + | |
| 14 | +#define BUILTIN_MODULE_PREFIX_STR "$" | |
| 15 | +#define BUILTIN_MODULE_PREFIX_CHAR '$' | |
| 16 | + | |
| 17 | +namespace disvm | |
| 18 | +{ | |
| 19 | + namespace runtime | |
| 20 | + { | |
| 21 | + namespace builtin | |
| 22 | + { | |
| 23 | + // Called to initialize built-in modules. | |
| 24 | + // This will be called once per process hosting a VM. | |
| 25 | + void initialize_builtin_modules(); | |
| 26 | + | |
| 27 | + // Defined as 'Runtab' in Inferno (limbo/stubs.c) | |
| 28 | + struct vm_runtab_t | |
| 29 | + { | |
| 30 | + // Function identification | |
| 31 | + char *name; | |
| 32 | + uint32_t sig; | |
| 33 | + | |
| 34 | + // Function pointer | |
| 35 | + vm_exec_t fn; | |
| 36 | + | |
| 37 | + // Function frame type | |
| 38 | + word_t frame_size; | |
| 39 | + word_t frame_pointer_count; | |
| 40 | + byte_t frame_pointer_map[16]; | |
| 41 | + }; | |
| 42 | + | |
| 43 | + // Called by the built-in module during initialization | |
| 44 | + void register_module_exports(const char *name, word_t table_length, const vm_runtab_t *module_runtime_table); | |
| 45 | + | |
| 46 | + // Get the supplied built-in module. | |
| 47 | + std::shared_ptr<vm_module_t> get_builtin_module(const char *name); | |
| 48 | + } | |
| 49 | + | |
| 50 | + // [SPEC] All allocated frames have the following base layout. | |
| 51 | + // See 'struct Frame' in Inferno (include/interp.h) | |
| 52 | + struct vm_frame_base_alloc_t | |
| 53 | + { | |
| 54 | + vm_pc_t p_pc; // Previous PC | |
| 55 | + vm_frame_t *p_fp; // Previous frame pointer | |
| 56 | + vm_module_ref_t *p_mr; // Previous module ref | |
| 57 | + pointer_t reserved; // Reserved for VM | |
| 58 | + }; | |
| 59 | + | |
| 60 | + static_assert( | |
| 61 | + sizeof(vm_frame_base_alloc_t) == runtime_constants::default_register_count * runtime_constants::default_register_size_bytes, | |
| 62 | + "Default register allocation should match base allocation type"); | |
| 63 | + } | |
| 64 | +} | |
| 65 | + | |
| 66 | +#endif // _DISVM_SRC_INCLUDE_BUILTIN_MODULE_H_ | |
| @@ -0,0 +1,99 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: debug.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_DEBUG_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_DEBUG_H_ | |
| 9 | + | |
| 10 | +#include <array> | |
| 11 | +#include <cstdarg> | |
| 12 | +#include <cassert> | |
| 13 | + | |
| 14 | +namespace disvm | |
| 15 | +{ | |
| 16 | + namespace debug | |
| 17 | + { | |
| 18 | + // Components that have tracing | |
| 19 | + enum class component_trace_t | |
| 20 | + { | |
| 21 | + builtin, | |
| 22 | + module, | |
| 23 | + thread, | |
| 24 | + stack, | |
| 25 | + channel, | |
| 26 | + exception, | |
| 27 | + memory, // Noisy component | |
| 28 | + addressing, // Noisy component [DEBUG build only] | |
| 29 | + | |
| 30 | + // Potentially logged from a VM extension. | |
| 31 | + scheduler, | |
| 32 | + garbage_collector, | |
| 33 | + | |
| 34 | + // Tracking purposes | |
| 35 | + last = garbage_collector | |
| 36 | + }; | |
| 37 | + | |
| 38 | + // Trace component enable state. | |
| 39 | + extern std::array<bool, static_cast<std::size_t>(disvm::debug::component_trace_t::last) + 1> trace_enabled; | |
| 40 | + | |
| 41 | + // Set tracing for a component | |
| 42 | + inline void set_component_tracing(const component_trace_t c, bool is_enabled) | |
| 43 | + { | |
| 44 | + trace_enabled[static_cast<const std::size_t>(c)] = is_enabled; | |
| 45 | + } | |
| 46 | + | |
| 47 | + // Check if tracing is enabled on a component (compile time optimization) | |
| 48 | + template<component_trace_t C> | |
| 49 | + bool is_component_tracing_enabled() | |
| 50 | + { | |
| 51 | + return trace_enabled[static_cast<const std::size_t>(C)]; | |
| 52 | + } | |
| 53 | + | |
| 54 | + // Check if tracing is enabled on a component | |
| 55 | + inline bool is_component_tracing_enabled(const component_trace_t c) | |
| 56 | + { | |
| 57 | + return trace_enabled[static_cast<const std::size_t>(c)]; | |
| 58 | + } | |
| 59 | + | |
| 60 | + // Message log level | |
| 61 | + enum class log_level_t | |
| 62 | + { | |
| 63 | + warning, | |
| 64 | + debug, | |
| 65 | + }; | |
| 66 | + | |
| 67 | + using log_callback_t = void(*)(const component_trace_t origin, const log_level_t level, const char *msg_fmt, std::va_list args); | |
| 68 | + | |
| 69 | + // Set the logging callback | |
| 70 | + // Returns the previous callback. | |
| 71 | + log_callback_t set_logging_callback(log_callback_t cb); | |
| 72 | + | |
| 73 | + // Log a message | |
| 74 | + void log_msg(const component_trace_t origin, const log_level_t level, const char *msg_fmt, ...); | |
| 75 | + | |
| 76 | + // | |
| 77 | + // Memory debugging | |
| 78 | + // | |
| 79 | + | |
| 80 | + inline void assign_debug_memory(void *dest, std::size_t size_in_bytes) | |
| 81 | + { | |
| 82 | +#ifndef NDEBUG | |
| 83 | + const auto debug_memory = int{ 0xda }; // Dante Alighieri's initials | |
| 84 | + std::memset(dest, debug_memory, size_in_bytes); | |
| 85 | +#endif | |
| 86 | + } | |
| 87 | + | |
| 88 | + template<typename T> | |
| 89 | + void assign_debug_pointer(T **t) | |
| 90 | + { | |
| 91 | +#ifndef NDEBUG | |
| 92 | + assert(t != nullptr); | |
| 93 | + *t = reinterpret_cast<T *>(0xbada110c); | |
| 94 | +#endif | |
| 95 | + } | |
| 96 | + } | |
| 97 | +} | |
| 98 | + | |
| 99 | +#endif // _DISVM_SRC_INCLUDE_DEBUG_H_ | |
| @@ -0,0 +1,98 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: disvm.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_DISVM_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_DISVM_H_ | |
| 9 | + | |
| 10 | +#include "runtime.h" | |
| 11 | +#include <iosfwd> | |
| 12 | +#include <memory> | |
| 13 | +#include <functional> | |
| 14 | +#include <mutex> | |
| 15 | +#include <forward_list> | |
| 16 | + | |
| 17 | +namespace disvm | |
| 18 | +{ | |
| 19 | + // Read in the module at the supplied path | |
| 20 | + std::unique_ptr<runtime::vm_module_t> read_module(const char *path); | |
| 21 | + | |
| 22 | + // Read in a module from the supplied stream | |
| 23 | + std::unique_ptr<runtime::vm_module_t> read_module(std::istream &data); | |
| 24 | + | |
| 25 | + template <typename C> | |
| 26 | + using create_vm_interface_callback_t = std::function<std::unique_ptr<C>(vm_t &)>; | |
| 27 | + | |
| 28 | + // Virtual machine | |
| 29 | + class vm_t final | |
| 30 | + { | |
| 31 | + public: // static | |
| 32 | + static const auto root_vm_thread_id = uint32_t{ 0 }; | |
| 33 | + | |
| 34 | + public: | |
| 35 | + explicit vm_t( | |
| 36 | + create_vm_interface_callback_t<runtime::vm_scheduler_t> create_scheduler = nullptr, | |
| 37 | + create_vm_interface_callback_t<runtime::vm_garbage_collector_t> create_gc = nullptr); | |
| 38 | + | |
| 39 | + explicit vm_t(uint32_t thread_pool_size, uint32_t thread_quanta); | |
| 40 | + | |
| 41 | + ~vm_t(); | |
| 42 | + | |
| 43 | + public: | |
| 44 | + // Begin execution of the module at the supplied path | |
| 45 | + const runtime::vm_thread_t& exec(const char *path); | |
| 46 | + | |
| 47 | + // Begin execution of the supplied module | |
| 48 | + const runtime::vm_thread_t& exec(std::unique_ptr<runtime::vm_module_t> entry_module); | |
| 49 | + | |
| 50 | + // Begin execution of a child thread in the supplied module at the indicated program counter | |
| 51 | + const runtime::vm_thread_t& fork( | |
| 52 | + const runtime::vm_thread_t &parent, | |
| 53 | + runtime::vm_module_ref_t &module_ref, | |
| 54 | + const runtime::vm_frame_t &initial_frame, | |
| 55 | + runtime::vm_pc_t initial_pc); | |
| 56 | + | |
| 57 | + // Load the module at the supplied path into the virtual machine | |
| 58 | + std::shared_ptr<const runtime::vm_module_t> load_module(const char *path); | |
| 59 | + | |
| 60 | + // Access the scheduler control for this VM. | |
| 61 | + runtime::vm_scheduler_control_t &get_scheduler_control() const; | |
| 62 | + | |
| 63 | + // Access the garbage collector for this VM. | |
| 64 | + runtime::vm_garbage_collector_t &get_garbage_collector() const; | |
| 65 | + | |
| 66 | + // Spin and sleep until the VM is idle, then return. | |
| 67 | + // Idle is defined as no vm threads executing, scheduled to be executed, or blocked. | |
| 68 | + void spin_sleep_till_idle(std::chrono::milliseconds sleep_interval) const; | |
| 69 | + | |
| 70 | + private: | |
| 71 | + // Loaded module reference | |
| 72 | + class loaded_module_t final | |
| 73 | + { | |
| 74 | + public: | |
| 75 | + loaded_module_t(std::unique_ptr<runtime::vm_string_t> origin, std::shared_ptr<const runtime::vm_module_t> module); | |
| 76 | + loaded_module_t(const loaded_module_t &) = delete; | |
| 77 | + loaded_module_t &operator=(const loaded_module_t &) = delete; | |
| 78 | + | |
| 79 | + loaded_module_t(loaded_module_t &&); | |
| 80 | + ~loaded_module_t(); | |
| 81 | + | |
| 82 | + // Origin of module | |
| 83 | + std::unique_ptr<runtime::vm_string_t> origin; | |
| 84 | + | |
| 85 | + // Weak reference to a loaded module | |
| 86 | + std::weak_ptr<const runtime::vm_module_t> module; | |
| 87 | + }; | |
| 88 | + | |
| 89 | + using loaded_modules_t = std::forward_list<loaded_module_t>; | |
| 90 | + std::mutex _modules_lock; | |
| 91 | + loaded_modules_t _modules; | |
| 92 | + | |
| 93 | + std::unique_ptr<runtime::vm_scheduler_t> _scheduler; | |
| 94 | + std::unique_ptr<runtime::vm_garbage_collector_t> _gc; | |
| 95 | + }; | |
| 96 | +} | |
| 97 | + | |
| 98 | +#endif // _DISVM_SRC_INCLUDE_DISVM_H_ | |
| @@ -0,0 +1,137 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: exceptions.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_EXCEPTIONS_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_EXCEPTIONS_H_ | |
| 9 | + | |
| 10 | +#include <cassert> | |
| 11 | +#include <string> | |
| 12 | +#include <stdexcept> | |
| 13 | +#include "runtime.h" | |
| 14 | + | |
| 15 | +namespace disvm | |
| 16 | +{ | |
| 17 | + namespace runtime | |
| 18 | + { | |
| 19 | + class vm_user_exception : public std::runtime_error | |
| 20 | + { | |
| 21 | + public: | |
| 22 | + explicit vm_user_exception(const char *message) | |
| 23 | + : std::runtime_error(message) | |
| 24 | + { } | |
| 25 | + }; | |
| 26 | + | |
| 27 | + class vm_system_exception : public std::runtime_error | |
| 28 | + { | |
| 29 | + public: | |
| 30 | + explicit vm_system_exception(const char *message) | |
| 31 | + : std::runtime_error(message) | |
| 32 | + { } | |
| 33 | + }; | |
| 34 | + | |
| 35 | + class vm_module_exception : public vm_system_exception | |
| 36 | + { | |
| 37 | + public: | |
| 38 | + explicit vm_module_exception(const char *message) | |
| 39 | + : vm_system_exception(message) | |
| 40 | + { } | |
| 41 | + }; | |
| 42 | + | |
| 43 | + class module_reader_exception final : public vm_module_exception | |
| 44 | + { | |
| 45 | + public: | |
| 46 | + explicit module_reader_exception() | |
| 47 | + : vm_module_exception("Invalid module") | |
| 48 | + { } | |
| 49 | + | |
| 50 | + explicit module_reader_exception(const char *message) | |
| 51 | + : vm_module_exception(message) | |
| 52 | + { } | |
| 53 | + }; | |
| 54 | + | |
| 55 | + class divide_by_zero final : public vm_user_exception | |
| 56 | + { | |
| 57 | + public: | |
| 58 | + explicit divide_by_zero() | |
| 59 | + : vm_user_exception("Divide by 0") | |
| 60 | + { } | |
| 61 | + }; | |
| 62 | + | |
| 63 | + class invalid_utf8 : public vm_user_exception | |
| 64 | + { | |
| 65 | + public: | |
| 66 | + explicit invalid_utf8() | |
| 67 | + : vm_user_exception("Invalid UTF-8 string") | |
| 68 | + { } | |
| 69 | + }; | |
| 70 | + | |
| 71 | + class dereference_nil final : public vm_user_exception | |
| 72 | + { | |
| 73 | + public: | |
| 74 | + explicit dereference_nil() | |
| 75 | + : vm_user_exception("Dereference of nil value") | |
| 76 | + , operation{ nullptr } | |
| 77 | + { } | |
| 78 | + | |
| 79 | + explicit dereference_nil(const char *operation) | |
| 80 | + : vm_user_exception("Dereference of nil value") | |
| 81 | + , operation{ operation } | |
| 82 | + { } | |
| 83 | + | |
| 84 | + const char *operation; | |
| 85 | + }; | |
| 86 | + | |
| 87 | + class type_violation : public vm_user_exception | |
| 88 | + { | |
| 89 | + public: | |
| 90 | + explicit type_violation() | |
| 91 | + : vm_user_exception("Inconsistent types in operation") | |
| 92 | + { } | |
| 93 | + }; | |
| 94 | + | |
| 95 | + class out_of_range_memory final : public vm_user_exception | |
| 96 | + { | |
| 97 | + public: | |
| 98 | + explicit out_of_range_memory() | |
| 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 | + { } | |
| 105 | + | |
| 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 } | |
| 110 | + , valid_max{ valid_max } | |
| 111 | + , valid_min{ valid_min } | |
| 112 | + { } | |
| 113 | + | |
| 114 | + const bool range_defined; | |
| 115 | + const word_t valid_min; | |
| 116 | + const word_t valid_max; | |
| 117 | + const word_t invalid_value; | |
| 118 | + }; | |
| 119 | + | |
| 120 | + class unhandled_user_exception final : public vm_user_exception | |
| 121 | + { | |
| 122 | + public: | |
| 123 | + explicit unhandled_user_exception(const char *exception_id, const char *module_name, vm_pc_t current_pc) | |
| 124 | + : vm_user_exception("Unhandled user exception") | |
| 125 | + , exception_id{ exception_id } | |
| 126 | + , module_name{ module_name } | |
| 127 | + , program_counter{ current_pc } | |
| 128 | + { } | |
| 129 | + | |
| 130 | + const char *exception_id; | |
| 131 | + const char *module_name; | |
| 132 | + const vm_pc_t program_counter; | |
| 133 | + }; | |
| 134 | + } | |
| 135 | +} | |
| 136 | + | |
| 137 | +#endif // _DISVM_SRC_INCLUDE_EXCEPTIONS_H_ | |
| @@ -0,0 +1,29 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: module_reader.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_MODULE_READER_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_MODULE_READER_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include "runtime.h" | |
| 12 | + | |
| 13 | +namespace disvm | |
| 14 | +{ | |
| 15 | + namespace format | |
| 16 | + { | |
| 17 | + using operand_t = int32_t; | |
| 18 | + static_assert(sizeof(operand_t) == sizeof(runtime::word_t), "Operand and word should be the same size"); | |
| 19 | + static_assert(sizeof(operand_t) == sizeof(runtime::vm_pc_t), "Operand and VM program counter type should be the same size"); | |
| 20 | + | |
| 21 | + struct magic_number_constants final | |
| 22 | + { | |
| 23 | + static const auto xmagic = operand_t{ 819248 }; // Normal module (0xC8030) | |
| 24 | + static const auto smagic = operand_t{ 923426 }; // Signed module (0xE1722) | |
| 25 | + }; | |
| 26 | + } | |
| 27 | +} | |
| 28 | + | |
| 29 | +#endif // _DISVM_SRC_INCLUDE_MODULE_READER_H_ | |
| @@ -0,0 +1,199 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: opcodes.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_OPCODES_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_OPCODES_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | + | |
| 12 | +namespace disvm | |
| 13 | +{ | |
| 14 | + // Instruction codes for the Dis VM | |
| 15 | + enum class opcode_t | |
| 16 | + { | |
| 17 | + invalid = 0, | |
| 18 | + alt, | |
| 19 | + nbalt, | |
| 20 | + goto_, | |
| 21 | + call, | |
| 22 | + frame, | |
| 23 | + spawn, | |
| 24 | + runt, | |
| 25 | + load, | |
| 26 | + mcall, | |
| 27 | + mspawn, | |
| 28 | + mframe, | |
| 29 | + ret, | |
| 30 | + jmp, | |
| 31 | + casew, | |
| 32 | + exit, | |
| 33 | + new_, | |
| 34 | + newa, | |
| 35 | + newcb, | |
| 36 | + newcw, | |
| 37 | + newcf, | |
| 38 | + newcp, | |
| 39 | + newcm, | |
| 40 | + newcmp, | |
| 41 | + send, | |
| 42 | + recv, | |
| 43 | + consb, | |
| 44 | + consw, | |
| 45 | + consp, | |
| 46 | + consf, | |
| 47 | + consm, | |
| 48 | + consmp = 0x1f, | |
| 49 | + headb, | |
| 50 | + headw, | |
| 51 | + headp, | |
| 52 | + headf, | |
| 53 | + headm, | |
| 54 | + headmp, | |
| 55 | + tail, | |
| 56 | + lea, | |
| 57 | + indx, | |
| 58 | + movp, | |
| 59 | + movm, | |
| 60 | + movmp, | |
| 61 | + movb, | |
| 62 | + movw, | |
| 63 | + movf, | |
| 64 | + cvtbw, | |
| 65 | + cvtwb, | |
| 66 | + cvtfw, | |
| 67 | + cvtwf, | |
| 68 | + cvtca, | |
| 69 | + cvtac, | |
| 70 | + cvtwc, | |
| 71 | + cvtcw, | |
| 72 | + cvtfc, | |
| 73 | + cvtcf, | |
| 74 | + addb, | |
| 75 | + addw, | |
| 76 | + addf, | |
| 77 | + subb, | |
| 78 | + subw, | |
| 79 | + subf, | |
| 80 | + mulb = 0x3f, | |
| 81 | + mulw, | |
| 82 | + mulf, | |
| 83 | + divb, | |
| 84 | + divw, | |
| 85 | + divf, | |
| 86 | + modw, | |
| 87 | + modb, | |
| 88 | + andb, | |
| 89 | + andw, | |
| 90 | + orb, | |
| 91 | + orw, | |
| 92 | + xorb, | |
| 93 | + xorw, | |
| 94 | + shlb, | |
| 95 | + shlw, | |
| 96 | + shrb, | |
| 97 | + shrw, | |
| 98 | + insc, | |
| 99 | + indc, | |
| 100 | + addc, | |
| 101 | + lenc, | |
| 102 | + lena, | |
| 103 | + lenl, | |
| 104 | + beqb, | |
| 105 | + bneb, | |
| 106 | + bltb, | |
| 107 | + bleb, | |
| 108 | + bgtb, | |
| 109 | + bgeb, | |
| 110 | + beqw, | |
| 111 | + bnew, | |
| 112 | + bltw = 0x5f, | |
| 113 | + blew, | |
| 114 | + bgtw, | |
| 115 | + bgew, | |
| 116 | + beqf, | |
| 117 | + bnef, | |
| 118 | + bltf, | |
| 119 | + blef, | |
| 120 | + bgtf, | |
| 121 | + bgef, | |
| 122 | + beqc, | |
| 123 | + bnec, | |
| 124 | + bltc, | |
| 125 | + blec, | |
| 126 | + bgtc, | |
| 127 | + bgec, | |
| 128 | + slicea, | |
| 129 | + slicela, | |
| 130 | + slicec, | |
| 131 | + indw, | |
| 132 | + indf, | |
| 133 | + indb, | |
| 134 | + negf, | |
| 135 | + movl, | |
| 136 | + addl, | |
| 137 | + subl, | |
| 138 | + divl, | |
| 139 | + modl, | |
| 140 | + mull, | |
| 141 | + andl, | |
| 142 | + orl, | |
| 143 | + xorl, | |
| 144 | + shll = 0x7f, | |
| 145 | + shrl, | |
| 146 | + bnel, | |
| 147 | + bltl, | |
| 148 | + blel, | |
| 149 | + bgtl, | |
| 150 | + bgel, | |
| 151 | + beql, | |
| 152 | + cvtlf, | |
| 153 | + cvtfl, | |
| 154 | + cvtlw, | |
| 155 | + cvtwl, | |
| 156 | + cvtlc, | |
| 157 | + cvtcl, | |
| 158 | + headl, | |
| 159 | + consl, | |
| 160 | + newcl, | |
| 161 | + casec, | |
| 162 | + indl, | |
| 163 | + movpc, | |
| 164 | + tcmp, | |
| 165 | + mnewz, | |
| 166 | + cvtrf, | |
| 167 | + cvtfr, | |
| 168 | + cvtws, | |
| 169 | + cvtsw, | |
| 170 | + lsrw, | |
| 171 | + lsrl, | |
| 172 | + eclr, // not used | |
| 173 | + newz, | |
| 174 | + newaz, | |
| 175 | + raise, | |
| 176 | + casel, | |
| 177 | + mulx, | |
| 178 | + divx, | |
| 179 | + cvtxx, | |
| 180 | + mulx0, | |
| 181 | + divx0, | |
| 182 | + cvtxx0, | |
| 183 | + mulx1, | |
| 184 | + divx1, | |
| 185 | + cvtxx1, | |
| 186 | + cvtfx, | |
| 187 | + cvtxf, | |
| 188 | + expw, | |
| 189 | + expl, | |
| 190 | + expf, | |
| 191 | + self, | |
| 192 | + | |
| 193 | + // markers | |
| 194 | + first_opcode = invalid, | |
| 195 | + last_opcode = self | |
| 196 | + }; | |
| 197 | +} | |
| 198 | + | |
| 199 | +#endif // _DISVM_SRC_INCLUDE_OPCODES_H_ | |
| @@ -0,0 +1,846 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: runtime.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_RUNTIME_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_RUNTIME_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include <cassert> | |
| 12 | +#include <atomic> | |
| 13 | +#include <vector> | |
| 14 | +#include <string> | |
| 15 | +#include <memory> | |
| 16 | +#include <deque> | |
| 17 | +#include <unordered_map> | |
| 18 | +#include <mutex> | |
| 19 | +#include <functional> | |
| 20 | +#include "opcodes.h" | |
| 21 | + | |
| 22 | +namespace disvm | |
| 23 | +{ | |
| 24 | + // VM forward declation | |
| 25 | + class vm_t; | |
| 26 | + | |
| 27 | + namespace runtime | |
| 28 | + { | |
| 29 | + // | |
| 30 | + // VM primitive data type definitions | |
| 31 | + // | |
| 32 | + | |
| 33 | + using byte_t = uint8_t; | |
| 34 | + using short_word_t = int16_t; | |
| 35 | + using word_t = int32_t; | |
| 36 | + using short_real_t = float; | |
| 37 | + using real_t = double; | |
| 38 | + using big_t = int64_t; | |
| 39 | + using pointer_t = std::size_t *; | |
| 40 | + using vm_pc_t = word_t; | |
| 41 | + | |
| 42 | + // | |
| 43 | + // VM memory management | |
| 44 | + // | |
| 45 | + | |
| 46 | + // VM forward declation | |
| 47 | + class vm_alloc_t; | |
| 48 | + | |
| 49 | + // Finalizer callback for allocations. | |
| 50 | + using vm_alloc_instance_finalizer_t = void(*)(vm_alloc_t *); | |
| 51 | + | |
| 52 | + // VM type description | |
| 53 | + class type_descriptor_t final | |
| 54 | + { | |
| 55 | + public: // static | |
| 56 | + static vm_alloc_instance_finalizer_t no_finalizer; | |
| 57 | + | |
| 58 | + // Create a type descriptor for type of the supplied size and pointers based on the supplied map. | |
| 59 | + static std::shared_ptr<const type_descriptor_t> create(const word_t size); | |
| 60 | + static std::shared_ptr<const type_descriptor_t> create(const word_t size, const std::vector<byte_t> &pointer_map); | |
| 61 | + static std::shared_ptr<const type_descriptor_t> create( | |
| 62 | + const word_t size, | |
| 63 | + const word_t pointer_map_length, | |
| 64 | + const byte_t *pointer_map, | |
| 65 | + vm_alloc_instance_finalizer_t finalizer = type_descriptor_t::no_finalizer); | |
| 66 | + | |
| 67 | + private: | |
| 68 | + type_descriptor_t(word_t size_in_bytes, word_t map_in_bytes, const byte_t * pointer_map, vm_alloc_instance_finalizer_t finalizer); | |
| 69 | + | |
| 70 | + public: | |
| 71 | + type_descriptor_t(const type_descriptor_t&) = delete; | |
| 72 | + type_descriptor_t& operator=(const type_descriptor_t&) = delete; | |
| 73 | + | |
| 74 | + const word_t size_in_bytes; | |
| 75 | + const word_t map_in_bytes; | |
| 76 | + const byte_t * const pointer_map; | |
| 77 | + const vm_alloc_instance_finalizer_t finalizer; | |
| 78 | + }; | |
| 79 | + | |
| 80 | + // Access to type descriptors for VM intrinsic types. | |
| 81 | + class intrinsic_type_desc final | |
| 82 | + { | |
| 83 | + private: | |
| 84 | + friend vm_t; | |
| 85 | + static std::atomic_bool is_initialized; | |
| 86 | + static void initialize(); | |
| 87 | + | |
| 88 | + public: | |
| 89 | + // Templated type descriptor getter | |
| 90 | + template<typename IntrinsicType> | |
| 91 | + static std::shared_ptr<const type_descriptor_t> type(); | |
| 92 | + }; | |
| 93 | + | |
| 94 | + // Type used to indicate allocated memory should be zeroed out. | |
| 95 | + struct zero_memory_t | |
| 96 | + { }; | |
| 97 | + | |
| 98 | + // VM memory allocation | |
| 99 | + class vm_alloc_t | |
| 100 | + { | |
| 101 | + public: // static | |
| 102 | + static const zero_memory_t zero_memory; | |
| 103 | + | |
| 104 | + static void *operator new(std::size_t sz); | |
| 105 | + static void operator delete(void *ptr); | |
| 106 | + | |
| 107 | + static vm_alloc_t *allocate(std::shared_ptr<const type_descriptor_t> td); | |
| 108 | + static vm_alloc_t *allocate(std::shared_ptr<const type_descriptor_t> td, const zero_memory_t &); | |
| 109 | + static vm_alloc_t *copy(const vm_alloc_t &other); | |
| 110 | + static vm_alloc_t *from_allocation(pointer_t allocation); | |
| 111 | + | |
| 112 | + template <typename T> | |
| 113 | + static T *from_allocation(pointer_t allocation) | |
| 114 | + { | |
| 115 | + auto alloc_inst = vm_alloc_t::from_allocation(allocation); | |
| 116 | + assert(alloc_inst == nullptr || alloc_inst->alloc_type == intrinsic_type_desc::type<T>()); | |
| 117 | + return dynamic_cast<T *>(alloc_inst); | |
| 118 | + } | |
| 119 | + | |
| 120 | + protected: | |
| 121 | + vm_alloc_t(std::shared_ptr<const type_descriptor_t> td); | |
| 122 | + | |
| 123 | + public: | |
| 124 | + virtual ~vm_alloc_t(); | |
| 125 | + | |
| 126 | + std::size_t add_ref(); | |
| 127 | + std::size_t release(); | |
| 128 | + std::size_t get_ref_count() const; | |
| 129 | + | |
| 130 | + std::shared_ptr<const type_descriptor_t> alloc_type; | |
| 131 | + | |
| 132 | + // Reserved for use by the garbage collector. | |
| 133 | + // This should not be accessed by any other component. | |
| 134 | + pointer_t gc_reserved; | |
| 135 | + | |
| 136 | + public: | |
| 137 | + pointer_t allocation() const; | |
| 138 | + | |
| 139 | + private: | |
| 140 | + std::atomic<std::size_t> _ref_count; | |
| 141 | + }; | |
| 142 | + | |
| 143 | + // | |
| 144 | + // VM intrinsic type definitions | |
| 145 | + // | |
| 146 | + | |
| 147 | + // Type to contain the maximum UTF-8 character value | |
| 148 | + using rune_t = uint32_t; | |
| 149 | + | |
| 150 | + // VM string type | |
| 151 | + class vm_string_t final : public vm_alloc_t | |
| 152 | + { | |
| 153 | + public: //static | |
| 154 | + static std::shared_ptr<const type_descriptor_t> type_desc(); | |
| 155 | + | |
| 156 | + // Compare two string objects | |
| 157 | + static int compare(const vm_string_t *s1, const vm_string_t *s2); | |
| 158 | + | |
| 159 | + public: | |
| 160 | + vm_string_t(); | |
| 161 | + vm_string_t(std::size_t encoded_str_len, const uint8_t *encoded_str); | |
| 162 | + vm_string_t(const vm_string_t &s1, const vm_string_t &s2); | |
| 163 | + vm_string_t(const vm_string_t &other, word_t begin_index, word_t end_index); | |
| 164 | + vm_string_t(const vm_string_t &) = delete; | |
| 165 | + ~vm_string_t(); | |
| 166 | + | |
| 167 | + // Returns this string. | |
| 168 | + vm_string_t &append(const vm_string_t &other); | |
| 169 | + | |
| 170 | + word_t get_length() const; | |
| 171 | + | |
| 172 | + rune_t get_rune(word_t index) const; | |
| 173 | + | |
| 174 | + void set_rune(word_t index, rune_t value); | |
| 175 | + | |
| 176 | + int compare_to(const vm_string_t *s) const; | |
| 177 | + | |
| 178 | + const char *str() const; | |
| 179 | + | |
| 180 | + private: | |
| 181 | + word_t _length; | |
| 182 | + word_t _length_max; | |
| 183 | + mutable std::mutex _encoded_str_lock; | |
| 184 | + mutable char *_encoded_str; | |
| 185 | + mutable union string_memory_t | |
| 186 | + { | |
| 187 | + uint8_t local[16]; // Optimization for short strings | |
| 188 | + uint8_t *alloc; | |
| 189 | + } _mem; | |
| 190 | + | |
| 191 | + uint8_t _character_size; // 1 byte (ASCII) or 4 bytes (multi-byte) | |
| 192 | + bool _is_alloc; | |
| 193 | + }; | |
| 194 | + | |
| 195 | + // VM array type | |
| 196 | + class vm_array_t final : public vm_alloc_t | |
| 197 | + { | |
| 198 | + public: //static | |
| 199 | + static std::shared_ptr<const type_descriptor_t> type_desc(); | |
| 200 | + | |
| 201 | + public: | |
| 202 | + vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t count); | |
| 203 | + vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t count, const zero_memory_t&); | |
| 204 | + vm_array_t(vm_array_t *original, word_t begin_index, word_t count); | |
| 205 | + vm_array_t(const vm_string_t *s); | |
| 206 | + ~vm_array_t(); | |
| 207 | + | |
| 208 | + std::shared_ptr<const type_descriptor_t> get_element_type() const; | |
| 209 | + | |
| 210 | + word_t get_length() const; | |
| 211 | + | |
| 212 | + pointer_t at(word_t index) const; | |
| 213 | + | |
| 214 | + template<typename T> | |
| 215 | + T& at(word_t index) const | |
| 216 | + { | |
| 217 | + return *reinterpret_cast<T *>(at(index)); | |
| 218 | + } | |
| 219 | + | |
| 220 | + // Copy the contents of the source array into this array starting at index | |
| 221 | + void copy_from(const vm_array_t &source, word_t this_begin_index); | |
| 222 | + | |
| 223 | + private: | |
| 224 | + std::shared_ptr<const type_descriptor_t> _element_type; | |
| 225 | + vm_array_t *_original; | |
| 226 | + word_t _length; | |
| 227 | + byte_t *_arr; | |
| 228 | + }; | |
| 229 | + | |
| 230 | + // VM list type | |
| 231 | + class vm_list_t final : public vm_alloc_t | |
| 232 | + { | |
| 233 | + public: //static | |
| 234 | + static std::shared_ptr<const type_descriptor_t> type_desc(); | |
| 235 | + | |
| 236 | + public: | |
| 237 | + vm_list_t(std::shared_ptr<const type_descriptor_t> td, vm_list_t *tail = nullptr); | |
| 238 | + ~vm_list_t(); | |
| 239 | + | |
| 240 | + std::shared_ptr<const type_descriptor_t> get_element_type() const; | |
| 241 | + | |
| 242 | + word_t get_length() const; | |
| 243 | + | |
| 244 | + vm_list_t* get_tail() const; | |
| 245 | + | |
| 246 | + vm_list_t* drop_tail(); | |
| 247 | + | |
| 248 | + // Returns a pointer to the value | |
| 249 | + pointer_t value() const; | |
| 250 | + | |
| 251 | + private: | |
| 252 | + std::shared_ptr<const type_descriptor_t> _element_type; | |
| 253 | + vm_list_t *_tail; | |
| 254 | + | |
| 255 | + // Utilizing a union so that all value types can | |
| 256 | + // be stored without allocating any extra memory. | |
| 257 | + mutable union element_memory_t | |
| 258 | + { | |
| 259 | + pointer_t alloc; | |
| 260 | + uint64_t local; | |
| 261 | + } _mem; | |
| 262 | + | |
| 263 | + const bool _is_alloc; | |
| 264 | + }; | |
| 265 | + | |
| 266 | + // VM forward declation | |
| 267 | + class vm_channel_t; | |
| 268 | + | |
| 269 | + // VM request mutex | |
| 270 | + struct vm_request_mutex_t | |
| 271 | + { | |
| 272 | + vm_request_mutex_t(); | |
| 273 | + | |
| 274 | + std::mutex pending_request_lock; | |
| 275 | + bool pending_request; | |
| 276 | + }; | |
| 277 | + | |
| 278 | + // VM channel request | |
| 279 | + class vm_channel_request_t final | |
| 280 | + { | |
| 281 | + public: | |
| 282 | + // Create a request using the ID of the requesting thread and an associated request mutex. | |
| 283 | + vm_channel_request_t(uint32_t thread_id, vm_request_mutex_t &request_mutex); | |
| 284 | + | |
| 285 | + uint32_t thread_id; | |
| 286 | + | |
| 287 | + // Callback used to indicate the request was handled in the supplied channel. | |
| 288 | + // This is only called in the event the request was not immediately serviced. | |
| 289 | + std::function<void(const vm_channel_t &)> request_handled_callback; | |
| 290 | + | |
| 291 | + // Data of the request | |
| 292 | + pointer_t data; | |
| 293 | + | |
| 294 | + std::reference_wrapper<vm_request_mutex_t> request_mutex; | |
| 295 | + }; | |
| 296 | + | |
| 297 | + // VM channel type | |
| 298 | + class vm_channel_t final : public vm_alloc_t | |
| 299 | + { | |
| 300 | + public: //static | |
| 301 | + static std::shared_ptr<const type_descriptor_t> type_desc(); | |
| 302 | + | |
| 303 | + public: | |
| 304 | + vm_channel_t( | |
| 305 | + std::shared_ptr<const type_descriptor_t> td, | |
| 306 | + std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer, | |
| 307 | + word_t buffer_len); | |
| 308 | + ~vm_channel_t(); | |
| 309 | + | |
| 310 | + std::shared_ptr<const type_descriptor_t> get_data_type() const; | |
| 311 | + | |
| 312 | + // Send data into the channel. | |
| 313 | + // Returns true if the data was sent, otherwise request is queued and false returned. | |
| 314 | + bool send_data(vm_channel_request_t &send_request); | |
| 315 | + | |
| 316 | + // Receive data from the channel. | |
| 317 | + // Returns true if data was received, otherwise request is queued and false returned. | |
| 318 | + bool receive_data(vm_channel_request_t &receive_request); | |
| 319 | + | |
| 320 | + // Cancel an outstanding request from a thread. | |
| 321 | + void cancel_request(uint32_t thread_id); | |
| 322 | + | |
| 323 | + private: | |
| 324 | + // Try to send data into the channel buffer. | |
| 325 | + // Returns true if data was sent to buffer, otherwise false. | |
| 326 | + bool try_send_to_buffer(vm_channel_request_t &send_request); | |
| 327 | + | |
| 328 | + // Try to receive data from the channel buffer. | |
| 329 | + // Returns true if data was received from buffer, otherwise false. | |
| 330 | + bool try_receive_from_buffer(vm_channel_request_t &receive_request); | |
| 331 | + | |
| 332 | + private: | |
| 333 | + std::mutex _data_lock; | |
| 334 | + std::shared_ptr<const type_descriptor_t> _data_type; | |
| 335 | + std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> _data_transfer; | |
| 336 | + | |
| 337 | + vm_array_t *_data_buffer; | |
| 338 | + uint32_t _data_buffer_head; // Next element to remove from buffer | |
| 339 | + uint32_t _data_buffer_count; // Count of elements in buffer | |
| 340 | + | |
| 341 | + using request_queue_t = std::deque<vm_channel_request_t>; | |
| 342 | + request_queue_t _data_senders; | |
| 343 | + request_queue_t _data_receivers; | |
| 344 | + }; | |
| 345 | + | |
| 346 | + // Alt channel entry. | |
| 347 | + static_assert(sizeof(word_t) == sizeof(pointer_t), "Alt channel stack layout will be invalid."); | |
| 348 | + struct vm_alt_channel_stack_layout_t | |
| 349 | + { | |
| 350 | + word_t alloc; | |
| 351 | + word_t data; | |
| 352 | + }; | |
| 353 | + | |
| 354 | + // Alt channel layout as defined by the specification for the 'alt' instruction. | |
| 355 | + struct vm_alt_stack_layout_t | |
| 356 | + { | |
| 357 | + word_t send_count; | |
| 358 | + word_t receive_count; | |
| 359 | + vm_alt_channel_stack_layout_t channels[1]; | |
| 360 | + }; | |
| 361 | + | |
| 362 | + // Runtime constants | |
| 363 | + struct runtime_constants | |
| 364 | + { | |
| 365 | + static const auto nil = std::size_t{ 0 }; | |
| 366 | + | |
| 367 | + static const auto invalid_program_counter = vm_pc_t{ ~0 }; | |
| 368 | + | |
| 369 | + static const auto default_register_count = std::size_t{ 4 }; | |
| 370 | + static const auto default_register_size_bytes = sizeof(word_t); | |
| 371 | + | |
| 372 | + // See VM frame definition | |
| 373 | + static const auto fixed_point_register_1_frame_offset = std::size_t{ default_register_count * default_register_size_bytes }; | |
| 374 | + static const auto fixed_point_register_2_frame_offset = fixed_point_register_1_frame_offset + (2 * default_register_size_bytes); | |
| 375 | + }; | |
| 376 | + | |
| 377 | + // Addressing mode for middle instruction data | |
| 378 | + enum class address_mode_middle_t | |
| 379 | + { | |
| 380 | + none = 0, | |
| 381 | + small_immediate = 1, | |
| 382 | + small_offset_indirect_fp = 2, | |
| 383 | + small_offset_indirect_mp = 3, | |
| 384 | + }; | |
| 385 | + | |
| 386 | + // Middle instruction data | |
| 387 | + struct middle_data_t | |
| 388 | + { | |
| 389 | + address_mode_middle_t mode; | |
| 390 | + word_t register1; | |
| 391 | + }; | |
| 392 | + | |
| 393 | + // Addressing mode for source and destination instruction data | |
| 394 | + enum class address_mode_t | |
| 395 | + { | |
| 396 | + offset_indirect_mp = 0, | |
| 397 | + offset_indirect_fp = 1, | |
| 398 | + immediate = 2, | |
| 399 | + none = 3, | |
| 400 | + offset_double_indirect_mp = 4, | |
| 401 | + offset_double_indirect_fp = 5, | |
| 402 | + reserved_1 = 6, | |
| 403 | + reserved_2 = 7, | |
| 404 | + }; | |
| 405 | + | |
| 406 | + // Source and destination instruction data | |
| 407 | + struct inst_data_generic_t | |
| 408 | + { | |
| 409 | + address_mode_t mode; | |
| 410 | + word_t register1; | |
| 411 | + word_t register2; | |
| 412 | + }; | |
| 413 | + | |
| 414 | + using src_data_t = inst_data_generic_t; | |
| 415 | + using dest_data_t = inst_data_generic_t; | |
| 416 | + | |
| 417 | + // VM forward declation | |
| 418 | + class vm_registers_t; | |
| 419 | + | |
| 420 | + // Instruction execution operation | |
| 421 | + using vm_exec_t = void(*)(vm_registers_t &, vm_t &); | |
| 422 | + | |
| 423 | + // Opcode execution operation | |
| 424 | + struct vm_exec_op_t | |
| 425 | + { | |
| 426 | + opcode_t opcode; | |
| 427 | + src_data_t source; | |
| 428 | + middle_data_t middle; | |
| 429 | + dest_data_t destination; | |
| 430 | + }; | |
| 431 | + | |
| 432 | + // VM Instruction (code section of module) | |
| 433 | + union vm_instruction_t | |
| 434 | + { | |
| 435 | + vm_exec_op_t op; | |
| 436 | + vm_exec_t native; | |
| 437 | + }; | |
| 438 | + | |
| 439 | + // Flags that dictate the runtime operations of the module | |
| 440 | + // [SPEC] Values past 'shared_module' are not fully documented. | |
| 441 | + enum class runtime_flags_t | |
| 442 | + { | |
| 443 | + must_compile = 1 << 0, | |
| 444 | + dont_compile = 1 << 1, | |
| 445 | + share_module = 1 << 2, // [TODO] Respect this setting | |
| 446 | + reserved_1 = 1 << 3, // DYNMOD | |
| 447 | + has_import_deprecated = 1 << 4, | |
| 448 | + has_handler = 1 << 5, | |
| 449 | + has_import = 1 << 6, | |
| 450 | + | |
| 451 | + builtin = 1 << 7, | |
| 452 | + }; | |
| 453 | + | |
| 454 | + // Module header | |
| 455 | + struct header_t | |
| 456 | + { | |
| 457 | + word_t magic_number; | |
| 458 | + struct | |
| 459 | + { | |
| 460 | + word_t length; | |
| 461 | + std::unique_ptr<byte_t[]> signature; | |
| 462 | + } Signature; | |
| 463 | + | |
| 464 | + runtime_flags_t runtime_flag; | |
| 465 | + word_t stack_extent; | |
| 466 | + word_t code_size; | |
| 467 | + word_t data_size; | |
| 468 | + word_t type_size; | |
| 469 | + word_t export_size; | |
| 470 | + vm_pc_t entry_pc; | |
| 471 | + word_t entry_type; // Stack frame type | |
| 472 | + }; | |
| 473 | + | |
| 474 | + // Export function entry | |
| 475 | + struct export_function_t | |
| 476 | + { | |
| 477 | + export_function_t() = default; | |
| 478 | + export_function_t(export_function_t &&); | |
| 479 | + | |
| 480 | + export_function_t(const export_function_t &) = delete; | |
| 481 | + export_function_t &operator=(const export_function_t &) = delete; | |
| 482 | + | |
| 483 | + ~export_function_t(); | |
| 484 | + | |
| 485 | + vm_pc_t pc; | |
| 486 | + word_t frame_type; | |
| 487 | + word_t sig; | |
| 488 | + std::unique_ptr<vm_string_t> name; | |
| 489 | + }; | |
| 490 | + | |
| 491 | + // Import function entry | |
| 492 | + struct import_function_t | |
| 493 | + { | |
| 494 | + import_function_t() = default; | |
| 495 | + import_function_t(import_function_t &&); | |
| 496 | + | |
| 497 | + import_function_t(const import_function_t &) = delete; | |
| 498 | + import_function_t &operator=(const import_function_t &) = delete; | |
| 499 | + | |
| 500 | + ~import_function_t(); | |
| 501 | + | |
| 502 | + word_t sig; | |
| 503 | + std::unique_ptr<vm_string_t> name; | |
| 504 | + }; | |
| 505 | + | |
| 506 | + // Import table for module | |
| 507 | + struct import_vm_module_t | |
| 508 | + { | |
| 509 | + import_vm_module_t() = default; | |
| 510 | + import_vm_module_t(import_vm_module_t &&); | |
| 511 | + | |
| 512 | + import_vm_module_t(const import_vm_module_t &) = delete; | |
| 513 | + import_vm_module_t &operator=(const import_vm_module_t &) = delete; | |
| 514 | + | |
| 515 | + ~import_vm_module_t() = default; | |
| 516 | + | |
| 517 | + std::vector<import_function_t> functions; | |
| 518 | + }; | |
| 519 | + | |
| 520 | + // Exception entry for handler | |
| 521 | + struct exception_t | |
| 522 | + { | |
| 523 | + exception_t() = default; | |
| 524 | + exception_t(exception_t &&); | |
| 525 | + | |
| 526 | + exception_t(const exception_t &) = delete; | |
| 527 | + exception_t &operator=(const exception_t &) = delete; | |
| 528 | + | |
| 529 | + ~exception_t(); | |
| 530 | + | |
| 531 | + vm_pc_t pc; | |
| 532 | + std::unique_ptr<vm_string_t> name; | |
| 533 | + }; | |
| 534 | + | |
| 535 | + // Exception handler | |
| 536 | + struct handler_t | |
| 537 | + { | |
| 538 | + handler_t() = default; | |
| 539 | + handler_t(handler_t &&); | |
| 540 | + | |
| 541 | + handler_t(const handler_t &) = delete; | |
| 542 | + handler_t &operator=(const handler_t &) = delete; | |
| 543 | + | |
| 544 | + ~handler_t() = default; | |
| 545 | + | |
| 546 | + // Byte offset in the handling frame the exception should be placed. | |
| 547 | + word_t exception_offset; | |
| 548 | + vm_pc_t begin_pc; | |
| 549 | + vm_pc_t end_pc; | |
| 550 | + | |
| 551 | + // Number of cases in exception table where the caught exception is of exception type. | |
| 552 | + // See exceptions in Limbo language for details. | |
| 553 | + word_t exception_type_count; | |
| 554 | + | |
| 555 | + std::shared_ptr<const type_descriptor_t> type_desc; | |
| 556 | + std::vector<exception_t> exception_table; | |
| 557 | + }; | |
| 558 | + | |
| 559 | + using code_section_t = std::vector<vm_instruction_t>; | |
| 560 | + using type_section_map_t = std::vector<std::shared_ptr<const type_descriptor_t>>; | |
| 561 | + using export_section_t = std::unordered_multimap<word_t, const export_function_t>; | |
| 562 | + using import_section_t = std::vector<import_vm_module_t>; | |
| 563 | + using handler_section_t = std::vector<handler_t>; | |
| 564 | + | |
| 565 | + // VM module | |
| 566 | + struct vm_module_t | |
| 567 | + { | |
| 568 | + vm_module_t() = default; | |
| 569 | + vm_module_t(const vm_module_t &) = delete; | |
| 570 | + vm_module_t &operator=(const vm_module_t &) = delete; | |
| 571 | + | |
| 572 | + ~vm_module_t(); | |
| 573 | + | |
| 574 | + // Contains a magic number, digital signature, runtime flags, sizes of the other sections, and a description of the entry point | |
| 575 | + header_t header; | |
| 576 | + | |
| 577 | + // Describes a sequence of instructions for the virtual machine | |
| 578 | + code_section_t code_section; | |
| 579 | + | |
| 580 | + // template module pointer | |
| 581 | + std::unique_ptr<vm_alloc_t> original_mp; | |
| 582 | + | |
| 583 | + // Type descriptors describing the layout of pointers within data types | |
| 584 | + type_section_map_t type_section; | |
| 585 | + | |
| 586 | + // Module name | |
| 587 | + std::unique_ptr<vm_string_t> module_name; | |
| 588 | + | |
| 589 | + // List of functions exported by this module | |
| 590 | + export_section_t export_section; | |
| 591 | + | |
| 592 | + // List of all functions imported from other modules | |
| 593 | + import_section_t import_section; | |
| 594 | + | |
| 595 | + // Lists all exception handlers declared in the module | |
| 596 | + handler_section_t handler_section; | |
| 597 | + }; | |
| 598 | + | |
| 599 | + // Reference to a function in an imported module. | |
| 600 | + struct vm_module_function_ref_t | |
| 601 | + { | |
| 602 | + vm_pc_t entry_pc; | |
| 603 | + word_t frame_type; | |
| 604 | + }; | |
| 605 | + | |
| 606 | + // Reference to a module | |
| 607 | + class vm_module_ref_t final : public vm_alloc_t | |
| 608 | + { | |
| 609 | + public: //static | |
| 610 | + static std::shared_ptr<const type_descriptor_t> type_desc(); | |
| 611 | + | |
| 612 | + public: | |
| 613 | + vm_module_ref_t(std::shared_ptr<const vm_module_t> module); | |
| 614 | + vm_module_ref_t(std::shared_ptr<const vm_module_t> module, const import_vm_module_t &imports); | |
| 615 | + ~vm_module_ref_t(); | |
| 616 | + | |
| 617 | + std::shared_ptr<const vm_module_t> module; | |
| 618 | + vm_alloc_t *mp_base; | |
| 619 | + const code_section_t &code_section; | |
| 620 | + const type_section_map_t &type_section; | |
| 621 | + | |
| 622 | + bool is_builtin_module() const; | |
| 623 | + const vm_module_function_ref_t& get_function_ref(word_t index) const; | |
| 624 | + | |
| 625 | + private: | |
| 626 | + const bool _builtin_module; | |
| 627 | + std::unique_ptr<vm_array_t> _function_refs; | |
| 628 | + }; | |
| 629 | + | |
| 630 | + // VM stack frame | |
| 631 | + class vm_frame_t final | |
| 632 | + { | |
| 633 | + public: | |
| 634 | + vm_frame_t(std::shared_ptr<const type_descriptor_t> &frame_type); | |
| 635 | + ~vm_frame_t(); | |
| 636 | + | |
| 637 | + std::shared_ptr<const type_descriptor_t> frame_type; | |
| 638 | + | |
| 639 | + // Copy the frame contents | |
| 640 | + void copy_frame_contents(const vm_frame_t &other); | |
| 641 | + | |
| 642 | + // Pointer to the base of the frame. | |
| 643 | + pointer_t base() const; | |
| 644 | + | |
| 645 | + template<typename T> | |
| 646 | + T& base() const | |
| 647 | + { | |
| 648 | + return *reinterpret_cast<T *>(base()); | |
| 649 | + } | |
| 650 | + | |
| 651 | + vm_pc_t &prev_pc() const; | |
| 652 | + vm_frame_t *&prev_frame() const; | |
| 653 | + | |
| 654 | + // This is only set if this frame represents a module transition. | |
| 655 | + vm_module_ref_t *&prev_module_ref() const; | |
| 656 | + | |
| 657 | + // Fixed point register positions in frame | |
| 658 | + // [SPEC] These are not defined in the vm spec. | |
| 659 | + | |
| 660 | + // 'Stmp' in libinterp/xec.c | |
| 661 | + pointer_t fixed_point_register_1() const; | |
| 662 | + | |
| 663 | + // 'Dtmp' in libinterp/xec.c | |
| 664 | + pointer_t fixed_point_register_2() const; | |
| 665 | + }; | |
| 666 | + | |
| 667 | + // Callback for stack walk. | |
| 668 | + // Returning 'false' during the stack walk will terminate the walk. | |
| 669 | + // The second parameter is reserved for internal use by the VM and should not be manipulated. | |
| 670 | + using vm_stack_walk_callback_t = std::function<bool(const vm_frame_t *f, const void *reserved)>; | |
| 671 | + | |
| 672 | + // VM stack | |
| 673 | + class vm_stack_t final | |
| 674 | + { | |
| 675 | + public: | |
| 676 | + vm_stack_t(std::size_t stack_extent); | |
| 677 | + ~vm_stack_t(); | |
| 678 | + | |
| 679 | + // Allocate a new frame instance. | |
| 680 | + vm_frame_t *alloc_frame(std::shared_ptr<const type_descriptor_t> frame_type); | |
| 681 | + | |
| 682 | + // Push the latest allocated frame as the new top frame. | |
| 683 | + vm_frame_t *push_frame(); | |
| 684 | + | |
| 685 | + // Remove the top frame and return the next frame in the stack. | |
| 686 | + vm_frame_t *pop_frame(); | |
| 687 | + | |
| 688 | + // Return the current top frame. | |
| 689 | + vm_frame_t *peek_frame() const; | |
| 690 | + | |
| 691 | + // Walk the stack getting a callback for each frame. | |
| 692 | + // This function is __not__ thread safe. | |
| 693 | + void walk_stack(vm_stack_walk_callback_t callback) const; | |
| 694 | + | |
| 695 | + private: | |
| 696 | + std::unique_ptr<vm_alloc_t> _mem; | |
| 697 | + }; | |
| 698 | + | |
| 699 | + // Walk the supplied stack and faulting module find the handler, handling stack frame, and program counter for the supplied exception ID. | |
| 700 | + // If an exception handler cannot be found an 'unhandled_user_exception' exception will be thrown. | |
| 701 | + std::tuple<const handler_t *, vm_frame_t *, vm_pc_t> find_exception_handler( | |
| 702 | + const vm_stack_t &stack, | |
| 703 | + const vm_module_ref_t &faulting_module, | |
| 704 | + const vm_pc_t faulting_pc, | |
| 705 | + const vm_string_t &exception_id); | |
| 706 | + | |
| 707 | + // VM thread states | |
| 708 | + enum class vm_thread_state_t | |
| 709 | + { | |
| 710 | + unknown = 0, | |
| 711 | + | |
| 712 | + blocked_in_alt, // blocked in alt instruction | |
| 713 | + blocked_sending, // blocked waiting to send | |
| 714 | + blocked_receiving, // blocked waiting to receive | |
| 715 | + | |
| 716 | + debug, // thread is running under a debugger | |
| 717 | + ready, // ready to run | |
| 718 | + running, | |
| 719 | + | |
| 720 | + release, // interpreter released - executing non-vm instructions | |
| 721 | + | |
| 722 | + exiting, // exit instruction | |
| 723 | + empty_stack, | |
| 724 | + | |
| 725 | + broken, // thread crashed | |
| 726 | + }; | |
| 727 | + | |
| 728 | + // Forward declaration | |
| 729 | + class vm_thread_t; | |
| 730 | + | |
| 731 | + // VM registers | |
| 732 | + class vm_registers_t | |
| 733 | + { | |
| 734 | + public: | |
| 735 | + vm_registers_t( | |
| 736 | + const vm_thread_t &thread, | |
| 737 | + vm_module_ref_t &entry); | |
| 738 | + ~vm_registers_t(); | |
| 739 | + | |
| 740 | + const vm_thread_t &thread; | |
| 741 | + vm_stack_t stack; // Frame pointer access (FP) | |
| 742 | + vm_pc_t pc; // Program counter (Index into module code section) | |
| 743 | + vm_alloc_t *mp_base; // Module data base pointer (MP) | |
| 744 | + vm_module_ref_t *module_ref; // Module reference | |
| 745 | + vm_request_mutex_t request_mutex; | |
| 746 | + vm_thread_state_t current_thread_state; | |
| 747 | + pointer_t src; | |
| 748 | + pointer_t mid; | |
| 749 | + pointer_t dest; | |
| 750 | + }; | |
| 751 | + | |
| 752 | + // VM thread | |
| 753 | + class vm_thread_t final : public vm_alloc_t | |
| 754 | + { | |
| 755 | + public: //static | |
| 756 | + static std::shared_ptr<const type_descriptor_t> type_desc(); | |
| 757 | + | |
| 758 | + public: | |
| 759 | + vm_thread_t( | |
| 760 | + vm_module_ref_t &entry, | |
| 761 | + uint32_t parent_thread_id); | |
| 762 | + vm_thread_t( | |
| 763 | + vm_module_ref_t &entry, | |
| 764 | + const vm_frame_t &initial_frame, | |
| 765 | + vm_pc_t start_pc, | |
| 766 | + uint32_t parent_thread_id); | |
| 767 | + | |
| 768 | + ~vm_thread_t(); | |
| 769 | + | |
| 770 | + vm_thread_state_t execute(vm_t &virtual_machine, uint32_t quanta); | |
| 771 | + | |
| 772 | + vm_thread_state_t get_state() const; | |
| 773 | + | |
| 774 | + uint32_t get_thread_id() const; | |
| 775 | + | |
| 776 | + uint32_t get_parent_thread_id() const; | |
| 777 | + | |
| 778 | + const vm_registers_t& get_registers() const; | |
| 779 | + | |
| 780 | + private: | |
| 781 | + const uint32_t _thread_id; | |
| 782 | + const uint32_t _parent_thread_id; | |
| 783 | + vm_registers_t _registers; | |
| 784 | + }; | |
| 785 | + | |
| 786 | + // | |
| 787 | + // VM interfaces | |
| 788 | + // | |
| 789 | + | |
| 790 | + // VM thread scheduler control interface | |
| 791 | + class vm_scheduler_control_t | |
| 792 | + { | |
| 793 | + public: | |
| 794 | + virtual ~vm_scheduler_control_t() = 0; | |
| 795 | + | |
| 796 | + // Indicates the scheduler should enqueue the blocked thread with the supplied ID. | |
| 797 | + // There is no indication if this function failed or succeeded. | |
| 798 | + virtual void enqueue_blocked_thread(uint32_t thread_id) = 0; | |
| 799 | + | |
| 800 | + // Get all VM threads | |
| 801 | + virtual std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const = 0; | |
| 802 | + }; | |
| 803 | + | |
| 804 | + // VM thread scheduler interface | |
| 805 | + class vm_scheduler_t | |
| 806 | + { | |
| 807 | + public: | |
| 808 | + virtual ~vm_scheduler_t() = 0; | |
| 809 | + | |
| 810 | + // Returns true if there are no vm threads executing, scheduled to be executed, or blocked. | |
| 811 | + virtual bool is_idle() const = 0; | |
| 812 | + | |
| 813 | + // Get controller | |
| 814 | + virtual vm_scheduler_control_t &get_controller() const = 0; | |
| 815 | + | |
| 816 | + // Schedule the supplied thread for execution | |
| 817 | + virtual const vm_thread_t& schedule_thread(std::unique_ptr<vm_thread_t> thread) = 0; | |
| 818 | + }; | |
| 819 | + | |
| 820 | + // VM garbage collector interface | |
| 821 | + class vm_garbage_collector_t | |
| 822 | + { | |
| 823 | + public: | |
| 824 | + // The supplied allocation is guaranteed to be valid for the lifetime of the callback. | |
| 825 | + using vm_alloc_callback_t = std::function<void(const vm_alloc_t *)>; | |
| 826 | + | |
| 827 | + public: | |
| 828 | + virtual ~vm_garbage_collector_t() = 0; | |
| 829 | + | |
| 830 | + // Supply an allocation for the collector to track. | |
| 831 | + // Note that it is assumed the collector implementation will increment the reference count on the supplied alloc. | |
| 832 | + virtual void track_allocation(vm_alloc_t *alloc) = 0; | |
| 833 | + | |
| 834 | + // Enumerate allocations that are being tracked by the collector. | |
| 835 | + // See callback type description. | |
| 836 | + virtual void enum_tracked_allocations(vm_alloc_callback_t callback) const = 0; | |
| 837 | + | |
| 838 | + // Called by the scheduler to inform the collector it should attempt to free resources. | |
| 839 | + // All VM threads are guaranteed to be idle when called. | |
| 840 | + // Returns true if the collection algorithm was run, otherwise false. | |
| 841 | + virtual bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) = 0; | |
| 842 | + }; | |
| 843 | + } | |
| 844 | +} | |
| 845 | + | |
| 846 | +#endif // _DISVM_SRC_INCLUDE_RUNTIME_H_ | |
| @@ -0,0 +1,57 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: utf8.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_UTF8_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_UTF8_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include "runtime.h" | |
| 12 | + | |
| 13 | +namespace disvm | |
| 14 | +{ | |
| 15 | + namespace runtime | |
| 16 | + { | |
| 17 | + namespace utf8 | |
| 18 | + { | |
| 19 | + // As defined in http://tools.ietf.org/html/rfc3629 | |
| 20 | + struct constants | |
| 21 | + { | |
| 22 | + static const rune_t multi_byte_value_mask = 0x3f; | |
| 23 | + | |
| 24 | + static const rune_t max_codepoint_ascii = 0x7f; | |
| 25 | + static const rune_t max_codepoint_2_byte = 0x7ff; | |
| 26 | + static const rune_t max_codepoint_3_byte = 0xffff; | |
| 27 | + static const rune_t max_codepoint_4_byte = 0x10ffff; | |
| 28 | + | |
| 29 | + static const rune_t max_supported_codepoint = max_codepoint_4_byte; | |
| 30 | + }; | |
| 31 | + | |
| 32 | + // Compute the number of code points in the supplied string | |
| 33 | + // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. | |
| 34 | + std::size_t count_codepoints(const uint8_t *str, const std::size_t max_len); | |
| 35 | + | |
| 36 | + enum class decode_state | |
| 37 | + { | |
| 38 | + accept = 0, | |
| 39 | + reject = 12 | |
| 40 | + }; | |
| 41 | + | |
| 42 | + // Decode a byte at a time to form a codepoint. | |
| 43 | + // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. | |
| 44 | + decode_state decode_step(decode_state &state, rune_t &codepoint, uint8_t byte); | |
| 45 | + | |
| 46 | + // Decode the next codepoint in the supplied string. | |
| 47 | + // Returns number of characters in string consumed. | |
| 48 | + std::size_t decode(const uint8_t *str, rune_t &codepoint); | |
| 49 | + | |
| 50 | + // Encode the rune into the supplied buffer. | |
| 51 | + // Returns updated pointer in the buffer or null on error. | |
| 52 | + uint8_t *encode(const rune_t rune, uint8_t *str); | |
| 53 | + } | |
| 54 | + } | |
| 55 | +} | |
| 56 | + | |
| 57 | +#endif // _DISVM_SRC_INCLUDE_UTF8_H_ | |
| @@ -0,0 +1,47 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: utils.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_UTILS_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_UTILS_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include <type_traits> | |
| 12 | + | |
| 13 | +namespace disvm | |
| 14 | +{ | |
| 15 | + namespace util | |
| 16 | + { | |
| 17 | + template<typename Flags> | |
| 18 | + inline bool has_flag(const Flags flags, const Flags flag) | |
| 19 | + { | |
| 20 | + return (static_cast<std::underlying_type_t<Flags>>(flags) & static_cast<std::underlying_type_t<Flags>>(flag)) != 0; | |
| 21 | + } | |
| 22 | + | |
| 23 | + template <typename T1, typename T2> | |
| 24 | + inline bool are_equal(const T1 &l, const T2 &r) | |
| 25 | + { | |
| 26 | + return (l == r); | |
| 27 | + } | |
| 28 | + | |
| 29 | + template <typename T1, typename T2, typename... Others> | |
| 30 | + inline bool are_equal(const T1 &l, const T2 &r, const Others &... args) | |
| 31 | + { | |
| 32 | + return (l == r) && are_equal(r, args...); | |
| 33 | + } | |
| 34 | + } | |
| 35 | +} | |
| 36 | + | |
| 37 | +// Adopted from 'winnt.h' in the Windows SDK | |
| 38 | +#define DEFINE_ENUM_FLAG_OPERATORS(TYPE) \ | |
| 39 | +inline TYPE operator | (TYPE a, TYPE b) { return static_cast<TYPE>(static_cast<std::underlying_type_t<TYPE>>(a) | static_cast<std::underlying_type_t<TYPE>>(b)); } \ | |
| 40 | +inline TYPE &operator |= (TYPE &a, TYPE b) { a = a | b; return a; } \ | |
| 41 | +inline TYPE operator & (TYPE a, TYPE b) { return static_cast<TYPE>(static_cast<std::underlying_type_t<TYPE>>(a) & static_cast<std::underlying_type_t<TYPE>>(b)); } \ | |
| 42 | +inline TYPE &operator &= (TYPE &a, TYPE b) { a = a & b; return a; } \ | |
| 43 | +inline TYPE operator ^ (TYPE a, TYPE b) { return static_cast<TYPE>(static_cast<std::underlying_type_t<TYPE>>(a) ^ static_cast<std::underlying_type_t<TYPE>>(b)); } \ | |
| 44 | +inline TYPE &operator ^= (TYPE &a, TYPE b) { a = a ^ b; return a; } \ | |
| 45 | +inline TYPE operator ~ (TYPE a) { return static_cast<TYPE>(~static_cast<std::underlying_type_t<TYPE>>(a)); } | |
| 46 | + | |
| 47 | +#endif // _DISVM_SRC_INCLUDE_UTILS_H_ | |
| @@ -0,0 +1,109 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: vm_memory.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_INCLUDE_VM_MEMORY_H_ | |
| 8 | +#define _DISVM_SRC_INCLUDE_VM_MEMORY_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include <cassert> | |
| 12 | +#include <mutex> | |
| 13 | +#include <memory> | |
| 14 | +#include <bitset> | |
| 15 | +#include <functional> | |
| 16 | +#include "runtime.h" | |
| 17 | + | |
| 18 | +namespace disvm | |
| 19 | +{ | |
| 20 | + namespace runtime | |
| 21 | + { | |
| 22 | + class global_alloc_lock_t final | |
| 23 | + { | |
| 24 | + public: | |
| 25 | + global_alloc_lock_t(const global_alloc_lock_t &) = delete; | |
| 26 | + global_alloc_lock_t& operator=(const global_alloc_lock_t &) = delete; | |
| 27 | + | |
| 28 | + global_alloc_lock_t(global_alloc_lock_t &&); | |
| 29 | + | |
| 30 | + ~global_alloc_lock_t(); | |
| 31 | + | |
| 32 | + private: | |
| 33 | + bool _owner; | |
| 34 | + global_alloc_lock_t(bool owner); | |
| 35 | + friend global_alloc_lock_t get_global_alloc_lock(); | |
| 36 | + }; | |
| 37 | + | |
| 38 | + // Take the global allocation lock for the VM heap. | |
| 39 | + // While this lock is taken all further allocations will block. | |
| 40 | + global_alloc_lock_t get_global_alloc_lock(); | |
| 41 | + | |
| 42 | + // Allocate memory from the VM heap | |
| 43 | + void *alloc_memory(std::size_t amount_in_bytes); | |
| 44 | + | |
| 45 | + template<typename T> | |
| 46 | + T *alloc_memory(std::size_t amount_in_bytes = sizeof(T)) | |
| 47 | + { | |
| 48 | + return reinterpret_cast<T *>(alloc_memory(amount_in_bytes)); | |
| 49 | + } | |
| 50 | + | |
| 51 | + // Allocate memory from the VM heap and initialize to 0. | |
| 52 | + void *calloc_memory(std::size_t amount_in_bytes); | |
| 53 | + | |
| 54 | + template<typename T> | |
| 55 | + T *calloc_memory(std::size_t amount_in_bytes = sizeof(T)) | |
| 56 | + { | |
| 57 | + return reinterpret_cast<T *>(calloc_memory(amount_in_bytes)); | |
| 58 | + } | |
| 59 | + | |
| 60 | + // Free memory on the VM heap | |
| 61 | + void free_memory(void *memory); | |
| 62 | + | |
| 63 | + // Initialize supplied memory based on the type descriptor | |
| 64 | + void init_memory(const type_descriptor_t &type_desc, void *data); | |
| 65 | + | |
| 66 | + // Destroy supplied memory based on the type descriptor | |
| 67 | + void destroy_memory(const type_descriptor_t &type_desc, void *data); | |
| 68 | + | |
| 69 | + // Increment the ref count of all points in the supplied memory allocation | |
| 70 | + void inc_ref_count_in_memory(const type_descriptor_t &type_desc, void *data); | |
| 71 | + | |
| 72 | + // Decrement the ref count and if 0 free the allocation. | |
| 73 | + void dec_ref_count_and_free(vm_alloc_t *alloc); | |
| 74 | + | |
| 75 | + using pointer_field_callback_t = std::function<void(pointer_t)>; | |
| 76 | + | |
| 77 | + // The callback will be called on all non-null pointer fields. | |
| 78 | + inline void enum_pointer_fields(const type_descriptor_t &type_desc, void *data, pointer_field_callback_t callback) | |
| 79 | + { | |
| 80 | + assert(data != nullptr && callback != nullptr); | |
| 81 | + auto memory_word_len = std::size_t{ type_desc.size_in_bytes / sizeof(word_t) }; | |
| 82 | + if (memory_word_len == 0) | |
| 83 | + return; | |
| 84 | + | |
| 85 | + auto memory = reinterpret_cast<word_t *>(data); | |
| 86 | + assert((memory_word_len / sizeof(word_t)) <= static_cast<std::size_t>(type_desc.size_in_bytes)); // type desc is in bytes convert len to bytes from words. | |
| 87 | + for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8) | |
| 88 | + { | |
| 89 | + const auto words8 = type_desc.pointer_map[i]; | |
| 90 | + if (words8 != 0) | |
| 91 | + { | |
| 92 | + auto flags = std::bitset<sizeof(words8) * 8>{ words8 }; | |
| 93 | + | |
| 94 | + // Enumerating the flags in reverse order so memory access is linear. | |
| 95 | + if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0])); | |
| 96 | + if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1])); | |
| 97 | + if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2])); | |
| 98 | + if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3])); | |
| 99 | + if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4])); | |
| 100 | + if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5])); | |
| 101 | + if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6])); | |
| 102 | + if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7])); | |
| 103 | + } | |
| 104 | + } | |
| 105 | + } | |
| 106 | + } | |
| 107 | +} | |
| 108 | + | |
| 109 | +#endif // _DISVM_SRC_INCLUDE_VM_MEMORY_H_ | |
| @@ -0,0 +1,52 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM - Shell | |
| 3 | +// File: main.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cstdio> | |
| 8 | +#include <cstdarg> | |
| 9 | +#include <ctime> | |
| 10 | +#include <disvm.h> | |
| 11 | +#include <debug.h> | |
| 12 | + | |
| 13 | +int main(int argc, char* argv[]) | |
| 14 | +{ | |
| 15 | + auto module = "C:\\Users\\linuxuser27\\source\\disvm\\basic_limbo_tests.dis"; | |
| 16 | + //auto module = "C:\\inferno\\dis\\limbo.dis"; | |
| 17 | + //auto module = "C:\\Users\\linuxuser27\\source\\disvm\\empty.dis"; | |
| 18 | + | |
| 19 | + if (argc >= 2) | |
| 20 | + module = argv[1]; | |
| 21 | + | |
| 22 | + clock_t t = ::clock(); | |
| 23 | + | |
| 24 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::memory, true); | |
| 25 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::module, true); | |
| 26 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::exception, true); | |
| 27 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::scheduler, true); | |
| 28 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::channel, true); | |
| 29 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::addressing, true); | |
| 30 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::stack, true); | |
| 31 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::thread, true); | |
| 32 | + //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::garbage_collector, true); | |
| 33 | + disvm::debug::set_logging_callback([](const disvm::debug::component_trace_t, const disvm::debug::log_level_t l, const char *msg, std::va_list ls) { std::vprintf(msg, ls); }); | |
| 34 | + | |
| 35 | + int c = 1; | |
| 36 | + //for (;;) | |
| 37 | + { | |
| 38 | + ::printf("####### VM test loop (%d) #######\n", c++); | |
| 39 | + disvm::vm_t v{ 1, 2048 }; | |
| 40 | + | |
| 41 | + for (auto i = 0; i < 1; ++i) | |
| 42 | + v.exec(module); | |
| 43 | + | |
| 44 | + v.spin_sleep_till_idle(std::chrono::milliseconds(10)); | |
| 45 | + } | |
| 46 | + | |
| 47 | + t = std::clock() - t; | |
| 48 | + std::printf("\n\n%d msec.\n", t); | |
| 49 | + | |
| 50 | + return 0; | |
| 51 | +} | |
| 52 | + | |
| @@ -0,0 +1,72 @@ | ||
| 1 | +{ | |
| 2 | + 'targets': [ | |
| 3 | + { | |
| 4 | + 'target_name': 'disvm-sh', | |
| 5 | + 'type': 'executable', | |
| 6 | + 'sources': [ | |
| 7 | + 'main.cpp' | |
| 8 | + ], | |
| 9 | + 'configurations': { | |
| 10 | + 'Release': { | |
| 11 | + 'msvs_settings': { | |
| 12 | + 'VCCLCompilerTool': { | |
| 13 | + 'ExceptionHandling': 1, | |
| 14 | + } | |
| 15 | + } | |
| 16 | + } | |
| 17 | + }, | |
| 18 | + 'include_dirs': [ | |
| 19 | + '../include' | |
| 20 | + ], | |
| 21 | + 'dependencies': [ | |
| 22 | + '../vm/disvm.gyp:disvm' | |
| 23 | + ], | |
| 24 | + 'conditions': [ | |
| 25 | + [ 'OS=="mac"', { | |
| 26 | + 'include_dirs': [ | |
| 27 | + '../include/osx' | |
| 28 | + ], | |
| 29 | + 'xcode_settings': { | |
| 30 | + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', | |
| 31 | + 'OTHER_CPLUSPLUSFLAGS' : ['-std=c++1y','-stdlib=libc++'], | |
| 32 | + 'OTHER_LDFLAGS': ['-stdlib=libc++'], | |
| 33 | + 'MACOSX_DEPLOYMENT_TARGET': '10.10' | |
| 34 | + } | |
| 35 | + }], | |
| 36 | + ['OS=="win"', { | |
| 37 | + 'include_dirs': [ | |
| 38 | + '../include/win32' | |
| 39 | + ], | |
| 40 | + 'configurations': { | |
| 41 | + 'Release': { | |
| 42 | + 'msvs_settings': { | |
| 43 | + 'VCCLCompilerTool': { | |
| 44 | + 'ExceptionHandling': 1, | |
| 45 | + } | |
| 46 | + } | |
| 47 | + }, | |
| 48 | + 'Debug': { | |
| 49 | + 'msvs_settings': { | |
| 50 | + 'VCCLCompilerTool': { | |
| 51 | + 'ExceptionHandling': 1, | |
| 52 | + } | |
| 53 | + } | |
| 54 | + } | |
| 55 | + }, | |
| 56 | + 'msvs_settings': { | |
| 57 | + 'VCCLCompilerTool': { | |
| 58 | + 'ExceptionHandling': '1', # /EHsc - This seems to only impact Debug, not Release | |
| 59 | + 'TreatWChar_tAsBuiltInType': 'false', | |
| 60 | + 'WarningLevel': '2', # Level 3 | |
| 61 | + 'DebugInformationFormat': '3', # PDB | |
| 62 | + } | |
| 63 | + } | |
| 64 | + }], | |
| 65 | + ['OS=="linux"', { | |
| 66 | + 'cflags': ['-std=c++1y'], | |
| 67 | + 'cflags_cc': ['-fexceptions'], | |
| 68 | + }] | |
| 69 | + ] | |
| 70 | + } | |
| 71 | + ] | |
| 72 | +} | |
| @@ -0,0 +1,188 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: array.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <runtime.h> | |
| 8 | +#include <vm_memory.h> | |
| 9 | +#include <debug.h> | |
| 10 | +#include <exceptions.h> | |
| 11 | + | |
| 12 | +using namespace disvm; | |
| 13 | +using namespace disvm::runtime; | |
| 14 | + | |
| 15 | +std::shared_ptr<const type_descriptor_t> vm_array_t::type_desc() | |
| 16 | +{ | |
| 17 | + return intrinsic_type_desc::type<vm_array_t>(); | |
| 18 | +} | |
| 19 | + | |
| 20 | +vm_array_t::vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t count) | |
| 21 | + : vm_alloc_t(vm_array_t::type_desc()) | |
| 22 | + , _arr{ nullptr } | |
| 23 | + , _element_type{ td } | |
| 24 | + , _length{ count } | |
| 25 | + , _original{ nullptr } | |
| 26 | +{ | |
| 27 | + assert(_element_type != nullptr); | |
| 28 | + assert(_length >= 0); | |
| 29 | + _arr = alloc_memory<byte_t>(_length * _element_type->size_in_bytes); | |
| 30 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: %d\n", _length); | |
| 31 | + | |
| 32 | + // Initialize the array | |
| 33 | + auto arr_local = _arr; | |
| 34 | + const auto array_len = _length; | |
| 35 | + const auto &element_type = *_element_type; | |
| 36 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "begin: init: elements: %d\n", array_len); | |
| 37 | + for (word_t i = 0; i < array_len; ++i) | |
| 38 | + { | |
| 39 | + init_memory(element_type, arr_local); | |
| 40 | + arr_local += element_type.size_in_bytes; | |
| 41 | + } | |
| 42 | + | |
| 43 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "end: init: elements: %d\n", array_len); | |
| 44 | +} | |
| 45 | + | |
| 46 | +vm_array_t::vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t count, const zero_memory_t&) | |
| 47 | + : vm_alloc_t(vm_array_t::type_desc()) | |
| 48 | + , _arr{ nullptr } | |
| 49 | + , _element_type{ td } | |
| 50 | + , _length{ count } | |
| 51 | + , _original{ nullptr } | |
| 52 | +{ | |
| 53 | + assert(_element_type != nullptr); | |
| 54 | + assert(_length >= 0); | |
| 55 | + _arr = calloc_memory<byte_t>(_length * _element_type->size_in_bytes); | |
| 56 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "initz: vm array: %d\n", _length); | |
| 57 | +} | |
| 58 | + | |
| 59 | +vm_array_t::vm_array_t(vm_array_t *original, word_t begin_index, word_t count) | |
| 60 | + : vm_alloc_t(vm_array_t::type_desc()) | |
| 61 | + , _arr{ nullptr } | |
| 62 | + , _element_type{ original->get_element_type() } | |
| 63 | + , _length{ count } | |
| 64 | + , _original{ original } | |
| 65 | +{ | |
| 66 | + assert(_original != nullptr); | |
| 67 | + assert(0 <= begin_index && begin_index < _original->get_length()); | |
| 68 | + assert(_length >= 0 && (count - begin_index) < _original->get_length()); | |
| 69 | + | |
| 70 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 71 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: slice: %d %d\n", begin_index, _length); | |
| 72 | + | |
| 73 | + // Set the data to reference the original array | |
| 74 | + _arr = _original->_arr + (begin_index * _element_type->size_in_bytes); | |
| 75 | + | |
| 76 | + // Check if this is a slice of a slice. | |
| 77 | + if (_original->_original != nullptr) | |
| 78 | + { | |
| 79 | + auto actual_original = _original->_original; | |
| 80 | + dec_ref_count_and_free(_original); | |
| 81 | + | |
| 82 | + _original = actual_original; | |
| 83 | + _original->add_ref(); | |
| 84 | + } | |
| 85 | +} | |
| 86 | + | |
| 87 | +vm_array_t::vm_array_t(const vm_string_t *s) | |
| 88 | + : vm_alloc_t(vm_array_t::type_desc()) | |
| 89 | + , _arr{ nullptr } | |
| 90 | + , _element_type{ intrinsic_type_desc::type<byte_t>() } | |
| 91 | + , _length{ 0 } | |
| 92 | + , _original{ nullptr } | |
| 93 | +{ | |
| 94 | + if (s != nullptr && s->get_length() > 0) | |
| 95 | + { | |
| 96 | + auto str = s->str(); | |
| 97 | + | |
| 98 | + // Ignoring the null terminator. | |
| 99 | + _length = std::strlen(str); | |
| 100 | + _arr = alloc_memory<byte_t>(_length); | |
| 101 | + static_assert(sizeof(byte_t) == sizeof(char), "String characters should be byte size"); | |
| 102 | + | |
| 103 | + std::memcpy(_arr, str, _length); | |
| 104 | + } | |
| 105 | + | |
| 106 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 107 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: vm string: %d\n", _length); | |
| 108 | +} | |
| 109 | + | |
| 110 | +vm_array_t::~vm_array_t() | |
| 111 | +{ | |
| 112 | + if (_original != nullptr) | |
| 113 | + { | |
| 114 | + // If the array is a sliced (based off of another array) then the | |
| 115 | + // inner pointer simply points into the original array. | |
| 116 | + debug::assign_debug_pointer(&_arr); | |
| 117 | + | |
| 118 | + dec_ref_count_and_free(_original); | |
| 119 | + debug::assign_debug_pointer(&_original); | |
| 120 | + | |
| 121 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm array: slice\n"); | |
| 122 | + } | |
| 123 | + else | |
| 124 | + { | |
| 125 | + // Free the array | |
| 126 | + auto arr_local = _arr; | |
| 127 | + const auto array_len = _length; | |
| 128 | + const auto &element_type = *_element_type; | |
| 129 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "begin: destroy: elements: %d\n", array_len); | |
| 130 | + for (word_t i = 0; i < array_len; ++i) | |
| 131 | + { | |
| 132 | + destroy_memory(element_type, arr_local); | |
| 133 | + arr_local += element_type.size_in_bytes; | |
| 134 | + } | |
| 135 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "end: destroy: elements: %d\n", array_len); | |
| 136 | + | |
| 137 | + // Free array memory | |
| 138 | + free_memory(_arr); | |
| 139 | + | |
| 140 | + debug::assign_debug_pointer(&_arr); | |
| 141 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm array\n"); | |
| 142 | + } | |
| 143 | + | |
| 144 | +} | |
| 145 | + | |
| 146 | +std::shared_ptr<const type_descriptor_t> vm_array_t::get_element_type() const | |
| 147 | +{ | |
| 148 | + return _element_type; | |
| 149 | +} | |
| 150 | + | |
| 151 | +word_t vm_array_t::get_length() const | |
| 152 | +{ | |
| 153 | + return _length; | |
| 154 | +} | |
| 155 | + | |
| 156 | +pointer_t vm_array_t::at(word_t index) const | |
| 157 | +{ | |
| 158 | + if (index < 0 || _length <= index) | |
| 159 | + throw out_of_range_memory{ 0, _length - 1, index }; | |
| 160 | + | |
| 161 | + return reinterpret_cast<pointer_t>(_arr + (index * _element_type->size_in_bytes)); | |
| 162 | +} | |
| 163 | + | |
| 164 | +void vm_array_t::copy_from(const vm_array_t &source, word_t this_begin_index) | |
| 165 | +{ | |
| 166 | + if (this_begin_index < 0 || _length < (this_begin_index + source._length)) | |
| 167 | + throw out_of_range_memory{}; | |
| 168 | + | |
| 169 | + if (source._element_type != _element_type) | |
| 170 | + throw type_violation{}; | |
| 171 | + | |
| 172 | + auto dest_arr = _arr + (this_begin_index * _element_type->size_in_bytes); | |
| 173 | + | |
| 174 | + // Increment all pointers if we have reference types. | |
| 175 | + if (_element_type->map_in_bytes != 0) | |
| 176 | + { | |
| 177 | + assert(false && "Not implemented"); | |
| 178 | + //auto &type_desc = *_element_type; | |
| 179 | + //auto source_array_element = source->_arr; | |
| 180 | + //for (word_t i = 0; i < source->_length; ++i) | |
| 181 | + //{ | |
| 182 | + // source_array_element += (i * type_desc.size_in_bytes); | |
| 183 | + // inc_ref_count_in_alloc(type_desc, source_array_element); | |
| 184 | + //} | |
| 185 | + } | |
| 186 | + | |
| 187 | + std::memcpy(dest_arr, source._arr, (source._length * _element_type->size_in_bytes)); | |
| 188 | +} | |
| @@ -0,0 +1,125 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: buffered_reader.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_VM_BUFFERED_READER_H_ | |
| 8 | +#define _DISVM_SRC_VM_BUFFERED_READER_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include <cassert> | |
| 12 | +#include <vector> | |
| 13 | +#include <tuple> | |
| 14 | +#include <iosfwd> | |
| 15 | + | |
| 16 | +namespace disvm | |
| 17 | +{ | |
| 18 | + namespace util | |
| 19 | + { | |
| 20 | + class buffered_reader_t | |
| 21 | + { | |
| 22 | + private: // static | |
| 23 | + static const std::size_t buffer_size = 64 * 1024; // 64k is an optimization for the Windows filesystem manager | |
| 24 | + | |
| 25 | + public: | |
| 26 | + buffered_reader_t(std::istream &stream) | |
| 27 | + : _buffer(buffered_reader_t::buffer_size) | |
| 28 | + , _current_index{ 0 } | |
| 29 | + , _current_size{ 0 } | |
| 30 | + , _stream{ stream } | |
| 31 | + { | |
| 32 | + assert(!stream.fail()); | |
| 33 | + } | |
| 34 | + | |
| 35 | + buffered_reader_t(const buffered_reader_t&) = delete; | |
| 36 | + buffered_reader_t& operator=(const buffered_reader_t&) = delete; | |
| 37 | + | |
| 38 | + // Get the next 'n' number of bytes. | |
| 39 | + // Returns the number of bytes retrieved. | |
| 40 | + uint32_t get_next_bytes(uint32_t bytes_requested, uint8_t *buffer) | |
| 41 | + { | |
| 42 | + if (bytes_requested == 0) | |
| 43 | + return 0; | |
| 44 | + | |
| 45 | + assert(buffer != nullptr); | |
| 46 | + | |
| 47 | + auto bytes_requested_acc = uint32_t{0}; | |
| 48 | + std::size_t amount_in_buffer; | |
| 49 | + | |
| 50 | + for (;;) | |
| 51 | + { | |
| 52 | + bool success = check_buffer_content(amount_in_buffer); | |
| 53 | + if (!success || amount_in_buffer == 0) | |
| 54 | + return 0; | |
| 55 | + | |
| 56 | + // Buffer contains requested amount | |
| 57 | + if (bytes_requested <= amount_in_buffer) | |
| 58 | + { | |
| 59 | + std::memcpy(buffer, (_buffer.data() + _current_index), bytes_requested); | |
| 60 | + _current_index += bytes_requested; | |
| 61 | + bytes_requested_acc += bytes_requested; | |
| 62 | + return bytes_requested_acc; | |
| 63 | + } | |
| 64 | + else | |
| 65 | + { | |
| 66 | + // Buffer does not contain all the data requested | |
| 67 | + std::memcpy(buffer, (_buffer.data() + _current_index), amount_in_buffer); | |
| 68 | + _current_index += amount_in_buffer; | |
| 69 | + | |
| 70 | + // Subtract the bytes consumed | |
| 71 | + bytes_requested -= amount_in_buffer; | |
| 72 | + | |
| 73 | + bytes_requested_acc += amount_in_buffer; | |
| 74 | + buffer += amount_in_buffer; | |
| 75 | + } | |
| 76 | + } | |
| 77 | + } | |
| 78 | + | |
| 79 | + // Get the next byte in the buffer. | |
| 80 | + // Returns success and the byte. | |
| 81 | + std::tuple<bool, uint8_t> get_next_byte() | |
| 82 | + { | |
| 83 | + std::size_t amount_in_buffer; | |
| 84 | + bool success = check_buffer_content(amount_in_buffer); | |
| 85 | + if (!success || amount_in_buffer == 0) | |
| 86 | + return std::make_tuple(false, 0); | |
| 87 | + | |
| 88 | + return std::make_tuple(true, _buffer[_current_index++]); | |
| 89 | + } | |
| 90 | + | |
| 91 | + private: | |
| 92 | + bool check_buffer_content(std::size_t &amount_in_buffer) | |
| 93 | + { | |
| 94 | + // Signed so checking for underflow is easier. | |
| 95 | + amount_in_buffer = 0; | |
| 96 | + auto amount_in_buffer_local = _current_size - _current_index; | |
| 97 | + if (amount_in_buffer_local <= 0) | |
| 98 | + { | |
| 99 | + _current_index = 0; | |
| 100 | + _current_size = 0; | |
| 101 | + | |
| 102 | + if (_stream.fail()) | |
| 103 | + return false; | |
| 104 | + | |
| 105 | + _stream.read(_buffer.data(), buffered_reader_t::buffer_size); | |
| 106 | + _current_size = _stream.fail() ? static_cast<std::size_t>(_stream.gcount()) : buffered_reader_t::buffer_size; | |
| 107 | + | |
| 108 | + // Reset amount in buffer | |
| 109 | + amount_in_buffer_local = buffered_reader_t::buffer_size; | |
| 110 | + } | |
| 111 | + | |
| 112 | + amount_in_buffer = amount_in_buffer_local; | |
| 113 | + return true; | |
| 114 | + } | |
| 115 | + | |
| 116 | + private: | |
| 117 | + std::istream &_stream; | |
| 118 | + std::size_t _current_index; | |
| 119 | + std::size_t _current_size; | |
| 120 | + std::vector<char> _buffer; | |
| 121 | + }; | |
| 122 | + } | |
| 123 | +} | |
| 124 | + | |
| 125 | +#endif // _DISVM_SRC_VM_BUFFERED_READER_H_ | |
| @@ -0,0 +1,113 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: builtin_module.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cassert> | |
| 8 | +#include <atomic> | |
| 9 | +#include <mutex> | |
| 10 | +#include <forward_list> | |
| 11 | +#include <runtime.h> | |
| 12 | +#include <module_reader.h> | |
| 13 | +#include <builtin_module.h> | |
| 14 | +#include <debug.h> | |
| 15 | +#include <exceptions.h> | |
| 16 | + | |
| 17 | +using namespace disvm::runtime; | |
| 18 | +using namespace disvm::runtime::builtin; | |
| 19 | + | |
| 20 | +namespace | |
| 21 | +{ | |
| 22 | + std::atomic_bool builtin_modules_initialized; | |
| 23 | + | |
| 24 | + std::mutex builtin_modules_lock; | |
| 25 | + std::forward_list<std::shared_ptr<vm_module_t>> builtin_modules; | |
| 26 | +} | |
| 27 | + | |
| 28 | +// Declare initializers for built-in modules. | |
| 29 | +extern void Sysmodinit(void); | |
| 30 | + | |
| 31 | +void disvm::runtime::builtin::initialize_builtin_modules() | |
| 32 | +{ | |
| 33 | + auto is_false = bool{ false }; | |
| 34 | + if (!builtin_modules_initialized.compare_exchange_strong(is_false, true)) | |
| 35 | + return; | |
| 36 | + | |
| 37 | + // Add built-in module initialization calls here. | |
| 38 | + // e.g. Sysmodinit() | |
| 39 | + | |
| 40 | + Sysmodinit(); | |
| 41 | +} | |
| 42 | + | |
| 43 | +void disvm::runtime::builtin::register_module_exports(const char *name, word_t table_length, const vm_runtab_t *module_runtime_table) | |
| 44 | +{ | |
| 45 | + assert(name != nullptr && (module_runtime_table != nullptr || table_length == 0) && 0 <= table_length); | |
| 46 | + assert(name[0] == BUILTIN_MODULE_PREFIX_CHAR && "Built-in modules should have the \"" BUILTIN_MODULE_PREFIX_STR "\" prefix"); | |
| 47 | + | |
| 48 | + auto new_builtin = std::make_shared<vm_module_t>(); | |
| 49 | + | |
| 50 | + // Define the module header | |
| 51 | + auto &header = new_builtin->header; | |
| 52 | + header.magic_number = format::magic_number_constants::xmagic; | |
| 53 | + header.Signature.length = word_t{ 0 }; | |
| 54 | + header.runtime_flag = runtime_flags_t::builtin; | |
| 55 | + | |
| 56 | + header.stack_extent = 0; | |
| 57 | + header.code_size = table_length; | |
| 58 | + header.data_size = 0; | |
| 59 | + header.type_size = table_length; // 1 for each function frame type | |
| 60 | + header.export_size = table_length; | |
| 61 | + header.entry_pc = runtime_constants::invalid_program_counter; | |
| 62 | + header.entry_type = -1; | |
| 63 | + | |
| 64 | + new_builtin->module_name = std::make_unique<vm_string_t>(::strlen(name), reinterpret_cast<const uint8_t *>(name)); | |
| 65 | + | |
| 66 | + // Fill out code, type, and export sections | |
| 67 | + for (auto i = word_t{ 0 }; i < table_length; ++i) | |
| 68 | + { | |
| 69 | + const auto &ex = module_runtime_table[i]; | |
| 70 | + | |
| 71 | + // Type | |
| 72 | + auto frame_type = type_descriptor_t::create(ex.frame_size, ex.frame_pointer_count, ex.frame_pointer_map); | |
| 73 | + new_builtin->type_section.push_back(std::move(frame_type)); | |
| 74 | + | |
| 75 | + // Export | |
| 76 | + auto item = export_function_t{}; | |
| 77 | + item.pc = i; | |
| 78 | + item.frame_type = i; | |
| 79 | + item.sig = static_cast<word_t>(ex.sig); | |
| 80 | + item.name = std::make_unique<vm_string_t>(std::strlen(ex.name), reinterpret_cast<const uint8_t *>(ex.name)); | |
| 81 | + | |
| 82 | + const auto sig = item.sig; | |
| 83 | + new_builtin->export_section.emplace(sig, std::move(item)); | |
| 84 | + | |
| 85 | + // Instruction | |
| 86 | + auto vm_instr = vm_instruction_t{}; | |
| 87 | + vm_instr.native = ex.fn; | |
| 88 | + new_builtin->code_section.push_back(std::move(vm_instr)); | |
| 89 | + } | |
| 90 | + | |
| 91 | + { | |
| 92 | + std::lock_guard<std::mutex> lock{ builtin_modules_lock }; | |
| 93 | + | |
| 94 | +#ifndef NDEBUG | |
| 95 | + for (const auto &m : builtin_modules) | |
| 96 | + assert(0 != std::strcmp(m->module_name->str(), name) && "Built-in module with matching name already exists"); | |
| 97 | +#endif | |
| 98 | + | |
| 99 | + builtin_modules.push_front(std::move(new_builtin)); | |
| 100 | + } | |
| 101 | +} | |
| 102 | + | |
| 103 | +std::shared_ptr<vm_module_t> disvm::runtime::builtin::get_builtin_module(const char *name) | |
| 104 | +{ | |
| 105 | + std::lock_guard<std::mutex> lock{ builtin_modules_lock }; | |
| 106 | + for (const auto &m : builtin_modules) | |
| 107 | + { | |
| 108 | + if (0 == std::strcmp(m->module_name->str(), name)) | |
| 109 | + return m; | |
| 110 | + } | |
| 111 | + | |
| 112 | + throw vm_module_exception{ "Unknown built-in module" }; | |
| 113 | +} | |
| @@ -0,0 +1,292 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: channel.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <runtime.h> | |
| 8 | +#include <vm_memory.h> | |
| 9 | +#include <debug.h> | |
| 10 | +#include <exceptions.h> | |
| 11 | + | |
| 12 | +using namespace disvm; | |
| 13 | +using namespace disvm::runtime; | |
| 14 | + | |
| 15 | +vm_request_mutex_t::vm_request_mutex_t() | |
| 16 | + : pending_request{ false } | |
| 17 | +{ } | |
| 18 | + | |
| 19 | +vm_channel_request_t::vm_channel_request_t(uint32_t thread_id, vm_request_mutex_t &request_mutex) | |
| 20 | + : data{ nullptr } | |
| 21 | + , request_mutex{ request_mutex } | |
| 22 | + , thread_id{ thread_id } | |
| 23 | +{ } | |
| 24 | + | |
| 25 | +std::shared_ptr<const type_descriptor_t> vm_channel_t::type_desc() | |
| 26 | +{ | |
| 27 | + return intrinsic_type_desc::type<vm_channel_t>(); | |
| 28 | +} | |
| 29 | + | |
| 30 | +vm_channel_t::vm_channel_t( | |
| 31 | + std::shared_ptr<const type_descriptor_t> td, | |
| 32 | + std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer, | |
| 33 | + word_t buffer_len) | |
| 34 | + : vm_alloc_t(vm_channel_t::type_desc()) | |
| 35 | + , _data_buffer{ nullptr } | |
| 36 | + , _data_buffer_head{ 0 } | |
| 37 | + , _data_buffer_count{ 0 } | |
| 38 | + , _data_transfer{ transfer } | |
| 39 | + , _data_type{ td } | |
| 40 | +{ | |
| 41 | + if (buffer_len < 0) | |
| 42 | + throw out_of_range_memory{}; | |
| 43 | + | |
| 44 | + if (buffer_len > 0) | |
| 45 | + _data_buffer = new vm_array_t{ td, buffer_len }; | |
| 46 | + | |
| 47 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm channel\n"); | |
| 48 | +} | |
| 49 | + | |
| 50 | +vm_channel_t::~vm_channel_t() | |
| 51 | +{ | |
| 52 | + dec_ref_count_and_free(_data_buffer); | |
| 53 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm channel\n"); | |
| 54 | +} | |
| 55 | + | |
| 56 | +std::shared_ptr<const type_descriptor_t> vm_channel_t::get_data_type() const | |
| 57 | +{ | |
| 58 | + return _data_type; | |
| 59 | +} | |
| 60 | + | |
| 61 | +bool vm_channel_t::send_data(vm_channel_request_t &send_request) | |
| 62 | +{ | |
| 63 | + assert(send_request.request_handled_callback != nullptr && send_request.data != nullptr); | |
| 64 | + | |
| 65 | + std::lock(_data_lock, send_request.request_mutex.get().pending_request_lock); | |
| 66 | + std::unique_lock<std::mutex> data_lock{ _data_lock, std::adopt_lock }; | |
| 67 | + std::lock_guard<std::mutex> curr_req_lock{ send_request.request_mutex.get().pending_request_lock, std::adopt_lock }; | |
| 68 | + | |
| 69 | + if (!send_request.request_mutex.get().pending_request) | |
| 70 | + return false; | |
| 71 | + | |
| 72 | + // Assert the sender cannot also be a receiver in the channel | |
| 73 | + assert(std::end(_data_receivers) == std::find_if( | |
| 74 | + std::begin(_data_receivers), | |
| 75 | + std::end(_data_receivers), | |
| 76 | + [&send_request](const vm_channel_request_t &req) { return req.thread_id == send_request.thread_id; })); | |
| 77 | + | |
| 78 | + auto pending_req_lock = std::unique_lock<std::mutex>{}; | |
| 79 | + for (;;) | |
| 80 | + { | |
| 81 | + if (_data_receivers.empty()) | |
| 82 | + { | |
| 83 | + // Check if there is room in the channel buffer | |
| 84 | + if (_data_buffer != nullptr) | |
| 85 | + { | |
| 86 | + if (try_send_to_buffer(send_request)) | |
| 87 | + return true; | |
| 88 | + } | |
| 89 | + | |
| 90 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) | |
| 91 | + debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: send: queue: %d\n", send_request.thread_id); | |
| 92 | + | |
| 93 | + _data_senders.emplace_back(std::move(send_request)); | |
| 94 | + return false; | |
| 95 | + } | |
| 96 | + | |
| 97 | + auto &receiver_maybe = _data_receivers.front(); | |
| 98 | + | |
| 99 | + // Take lock and check the pending request status | |
| 100 | + pending_req_lock = std::unique_lock<std::mutex>{ receiver_maybe.request_mutex.get().pending_request_lock }; | |
| 101 | + if (receiver_maybe.request_mutex.get().pending_request) | |
| 102 | + { | |
| 103 | + // Indicate both request mutexes as non-pending | |
| 104 | + send_request.request_mutex.get().pending_request = false; | |
| 105 | + receiver_maybe.request_mutex.get().pending_request = false; | |
| 106 | + break; | |
| 107 | + } | |
| 108 | + | |
| 109 | + pending_req_lock.unlock(); | |
| 110 | + _data_receivers.pop_front(); | |
| 111 | + } | |
| 112 | + | |
| 113 | + // Remove the receiver from the list | |
| 114 | + auto receiver = std::move(_data_receivers.front()); | |
| 115 | + _data_receivers.pop_front(); | |
| 116 | + | |
| 117 | + // Transfer data | |
| 118 | + _data_transfer(receiver.data, send_request.data, _data_type.get()); | |
| 119 | + | |
| 120 | + // Release channel and pending request lock after data transfer | |
| 121 | + pending_req_lock.unlock(); | |
| 122 | + data_lock.unlock(); | |
| 123 | + | |
| 124 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) | |
| 125 | + debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: send: %d %d\n", send_request.thread_id, receiver.thread_id); | |
| 126 | + | |
| 127 | + receiver.request_handled_callback(*this); | |
| 128 | + | |
| 129 | + return true; | |
| 130 | +} | |
| 131 | + | |
| 132 | +bool vm_channel_t::receive_data(vm_channel_request_t &receive_request) | |
| 133 | +{ | |
| 134 | + assert(receive_request.request_handled_callback != nullptr && receive_request.data != nullptr); | |
| 135 | + | |
| 136 | + std::lock(_data_lock, receive_request.request_mutex.get().pending_request_lock); | |
| 137 | + std::unique_lock<std::mutex> data_lock{ _data_lock, std::adopt_lock }; | |
| 138 | + std::lock_guard<std::mutex> curr_req_lock{ receive_request.request_mutex.get().pending_request_lock, std::adopt_lock }; | |
| 139 | + | |
| 140 | + if (!receive_request.request_mutex.get().pending_request) | |
| 141 | + return false; | |
| 142 | + | |
| 143 | + // Assert the receiver cannot also be a sender in the channel | |
| 144 | + assert(std::end(_data_senders) == std::find_if( | |
| 145 | + std::begin(_data_senders), | |
| 146 | + std::end(_data_senders), | |
| 147 | + [&receive_request](const vm_channel_request_t &req) { return req.thread_id == receive_request.thread_id; })); | |
| 148 | + | |
| 149 | + auto pending_req_lock = std::unique_lock<std::mutex>{}; | |
| 150 | + for (;;) | |
| 151 | + { | |
| 152 | + if (_data_senders.empty()) | |
| 153 | + { | |
| 154 | + // Check if there is data in the channel buffer | |
| 155 | + if (_data_buffer != nullptr) | |
| 156 | + { | |
| 157 | + if (try_receive_from_buffer(receive_request)) | |
| 158 | + return true; | |
| 159 | + } | |
| 160 | + | |
| 161 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) | |
| 162 | + debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: receive: queue: %d\n", receive_request.thread_id); | |
| 163 | + | |
| 164 | + _data_receivers.emplace_back(std::move(receive_request)); | |
| 165 | + return false; | |
| 166 | + } | |
| 167 | + | |
| 168 | + auto &sender_maybe = _data_senders.front(); | |
| 169 | + | |
| 170 | + // Take lock and check the pending request status | |
| 171 | + pending_req_lock = std::unique_lock<std::mutex>{ sender_maybe.request_mutex.get().pending_request_lock }; | |
| 172 | + if (sender_maybe.request_mutex.get().pending_request) | |
| 173 | + { | |
| 174 | + // Indicate both request mutexes as non-pending | |
| 175 | + receive_request.request_mutex.get().pending_request = false; | |
| 176 | + sender_maybe.request_mutex.get().pending_request = false; | |
| 177 | + break; | |
| 178 | + } | |
| 179 | + | |
| 180 | + pending_req_lock.unlock(); | |
| 181 | + _data_senders.pop_front(); | |
| 182 | + } | |
| 183 | + | |
| 184 | + // Remove the sender from the list | |
| 185 | + auto sender = std::move(_data_senders.front()); | |
| 186 | + _data_senders.pop_front(); | |
| 187 | + | |
| 188 | + // Check if there is data in the channel buffer. This is being | |
| 189 | + // done to avoid starving threads that found a full buffer when | |
| 190 | + // attempting to satisfy a request. | |
| 191 | + if (_data_buffer != nullptr && try_receive_from_buffer(receive_request)) | |
| 192 | + { | |
| 193 | + // Data was taken from the buffer, so the current pending sender | |
| 194 | + // can now use the empty slot. | |
| 195 | + auto did_send = try_send_to_buffer(sender); | |
| 196 | + assert(did_send && "Should be room in buffer"); | |
| 197 | + } | |
| 198 | + else | |
| 199 | + { | |
| 200 | + // Transfer data since there wasn't anything in the buffer | |
| 201 | + _data_transfer(receive_request.data, sender.data, _data_type.get()); | |
| 202 | + | |
| 203 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) | |
| 204 | + debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: receive: %d %d\n", receive_request.thread_id, sender.thread_id); | |
| 205 | + } | |
| 206 | + | |
| 207 | + // Release channel and pending request lock after data transfer | |
| 208 | + pending_req_lock.unlock(); | |
| 209 | + data_lock.unlock(); | |
| 210 | + | |
| 211 | + sender.request_handled_callback(*this); | |
| 212 | + | |
| 213 | + return true; | |
| 214 | +} | |
| 215 | + | |
| 216 | +void vm_channel_t::cancel_request(uint32_t thread_id) | |
| 217 | +{ | |
| 218 | + std::lock_guard<std::mutex> lock{ _data_lock }; | |
| 219 | + | |
| 220 | + // Create a predicate function based on the input | |
| 221 | + auto request_predicate = [thread_id](const vm_channel_request_t &req) { return req.thread_id == thread_id; }; | |
| 222 | + | |
| 223 | + auto receiver_iter = std::find_if(std::begin(_data_receivers), std::end(_data_receivers), request_predicate); | |
| 224 | + if (receiver_iter != std::end(_data_receivers)) | |
| 225 | + { | |
| 226 | + _data_receivers.erase(receiver_iter); | |
| 227 | + } | |
| 228 | + else | |
| 229 | + { | |
| 230 | + auto sender_iter = std::find_if(std::begin(_data_senders), std::end(_data_senders), request_predicate); | |
| 231 | + if (sender_iter == std::end(_data_senders)) | |
| 232 | + return; | |
| 233 | + | |
| 234 | + _data_senders.erase(sender_iter); | |
| 235 | + } | |
| 236 | + | |
| 237 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) | |
| 238 | + debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: cancel: request: %d\n", thread_id); | |
| 239 | +} | |
| 240 | + | |
| 241 | +bool vm_channel_t::try_send_to_buffer(vm_channel_request_t &send_request) | |
| 242 | +{ | |
| 243 | + assert(_data_buffer != nullptr); | |
| 244 | + | |
| 245 | + const auto buffer_length = static_cast<uint32_t>(_data_buffer->get_length()); | |
| 246 | + if (_data_buffer_count == buffer_length) | |
| 247 | + return false; | |
| 248 | + | |
| 249 | + auto next_buffer_index = _data_buffer_head + _data_buffer_count; | |
| 250 | + if (next_buffer_index >= buffer_length) | |
| 251 | + next_buffer_index -= buffer_length; | |
| 252 | + | |
| 253 | + _data_buffer_count++; | |
| 254 | + | |
| 255 | + auto dest = _data_buffer->at(next_buffer_index); | |
| 256 | + | |
| 257 | + // Transfer data to buffer | |
| 258 | + _data_transfer(dest, send_request.data, _data_type.get()); | |
| 259 | + send_request.request_mutex.get().pending_request = false; | |
| 260 | + | |
| 261 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) | |
| 262 | + debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: send: buffer: %d\n", send_request.thread_id); | |
| 263 | + | |
| 264 | + return true; | |
| 265 | +} | |
| 266 | + | |
| 267 | +bool vm_channel_t::try_receive_from_buffer(vm_channel_request_t &receive_request) | |
| 268 | +{ | |
| 269 | + assert(_data_buffer != nullptr); | |
| 270 | + | |
| 271 | + if (_data_buffer_count == 0) | |
| 272 | + return false; | |
| 273 | + | |
| 274 | + auto src = _data_buffer->at(_data_buffer_head); | |
| 275 | + _data_buffer_head++; | |
| 276 | + | |
| 277 | + const auto buffer_length = _data_buffer->get_length(); | |
| 278 | + if (_data_buffer_head == buffer_length) | |
| 279 | + _data_buffer_head = 0; | |
| 280 | + | |
| 281 | + _data_buffer_count--; | |
| 282 | + | |
| 283 | + // Transfer data from buffer | |
| 284 | + _data_transfer(receive_request.data, src, _data_type.get()); | |
| 285 | + | |
| 286 | + receive_request.request_mutex.get().pending_request = false; | |
| 287 | + | |
| 288 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) | |
| 289 | + debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: receive: buffer: %d\n", receive_request.thread_id); | |
| 290 | + | |
| 291 | + return true; | |
| 292 | +} | |
| @@ -0,0 +1,45 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: debug.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <mutex> | |
| 8 | +#include <array> | |
| 9 | +#include <debug.h> | |
| 10 | + | |
| 11 | +namespace | |
| 12 | +{ | |
| 13 | + std::mutex log_mutex; | |
| 14 | + disvm::debug::log_callback_t log_callback = nullptr; | |
| 15 | +} | |
| 16 | + | |
| 17 | +std::array<bool, static_cast<std::size_t>(disvm::debug::component_trace_t::last) + 1> disvm::debug::trace_enabled = {}; | |
| 18 | + | |
| 19 | +disvm::debug::log_callback_t disvm::debug::set_logging_callback(log_callback_t cb) | |
| 20 | +{ | |
| 21 | + std::lock_guard<std::mutex> lock{ log_mutex }; | |
| 22 | + auto tmp = log_callback; | |
| 23 | + log_callback = cb; | |
| 24 | + return tmp; | |
| 25 | +} | |
| 26 | + | |
| 27 | +void disvm::debug::log_msg(const component_trace_t origin, const log_level_t level, const char *msg_fmt, ...) | |
| 28 | +{ | |
| 29 | + if (log_callback == nullptr) | |
| 30 | + return; | |
| 31 | + | |
| 32 | + if (!is_component_tracing_enabled(origin)) | |
| 33 | + return; | |
| 34 | + | |
| 35 | + std::va_list args; | |
| 36 | + va_start(args, msg_fmt); | |
| 37 | + | |
| 38 | + { | |
| 39 | + std::lock_guard<std::mutex> lock{ log_mutex }; | |
| 40 | + if (log_callback != nullptr) | |
| 41 | + log_callback(origin, level, msg_fmt, args); | |
| 42 | + } | |
| 43 | + | |
| 44 | + va_end(args); | |
| 45 | +} | |
| @@ -0,0 +1,83 @@ | ||
| 1 | +{ | |
| 2 | + 'targets': [ | |
| 3 | + { | |
| 4 | + 'target_name': 'disvm', | |
| 5 | + 'type': 'static_library', | |
| 6 | + 'sources': [ | |
| 7 | + 'array.cpp', | |
| 8 | + 'builtin_module.cpp', | |
| 9 | + 'channel.cpp', | |
| 10 | + 'debug.cpp', | |
| 11 | + 'execution_table.cpp', | |
| 12 | + 'garbage_collector.cpp', | |
| 13 | + 'list.cpp', | |
| 14 | + 'module_reader.cpp', | |
| 15 | + 'module_ref.cpp', | |
| 16 | + 'scheduler.cpp', | |
| 17 | + 'stack.cpp', | |
| 18 | + 'string.cpp', | |
| 19 | + 'thread.cpp', | |
| 20 | + 'utf8.cpp', | |
| 21 | + 'vm.cpp', | |
| 22 | + 'vm_memory.cpp', | |
| 23 | + 'vm_exception_handler.cpp', | |
| 24 | + 'sys/print.cpp', | |
| 25 | + 'sys/Sysmod.cpp', | |
| 26 | + ], | |
| 27 | + 'include_dirs': [ | |
| 28 | + '../include' | |
| 29 | + ], | |
| 30 | + 'conditions': [ | |
| 31 | + ['OS=="linux"', { | |
| 32 | + 'include_dirs': [ | |
| 33 | + '../include/linux' | |
| 34 | + ], | |
| 35 | + 'cflags': ['-std=c++1y'], | |
| 36 | + 'cflags_cc': ['-fexceptions'], | |
| 37 | + 'sources': [ ], | |
| 38 | + }], | |
| 39 | + ['OS=="mac"', { | |
| 40 | + 'include_dirs': [ | |
| 41 | + '../include/osx' | |
| 42 | + ], | |
| 43 | + 'xcode_settings': { | |
| 44 | + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', | |
| 45 | + 'OTHER_CPLUSPLUSFLAGS' : ['-std=c++1y', '-stdlib=libc++'], | |
| 46 | + 'MACOSX_DEPLOYMENT_TARGET': '10.10' | |
| 47 | + }, | |
| 48 | + 'sources': [ ], | |
| 49 | + }], | |
| 50 | + ['OS=="win"', { | |
| 51 | + 'include_dirs': [ | |
| 52 | + '../include/win32' | |
| 53 | + ], | |
| 54 | + 'configurations': { | |
| 55 | + 'Release': { | |
| 56 | + 'msvs_settings': { | |
| 57 | + 'VCCLCompilerTool': { | |
| 58 | + 'ExceptionHandling': 1, | |
| 59 | + } | |
| 60 | + } | |
| 61 | + }, | |
| 62 | + 'Debug': { | |
| 63 | + 'msvs_settings': { | |
| 64 | + 'VCCLCompilerTool': { | |
| 65 | + 'ExceptionHandling': 1, | |
| 66 | + } | |
| 67 | + } | |
| 68 | + } | |
| 69 | + }, | |
| 70 | + 'msvs_settings': { | |
| 71 | + 'VCCLCompilerTool': { | |
| 72 | + 'ExceptionHandling': '1', # /EHsc - This seems to only impact Debug, not Release | |
| 73 | + 'TreatWChar_tAsBuiltInType': 'false', | |
| 74 | + 'WarningLevel': '2', # Level 3 | |
| 75 | + 'DebugInformationFormat': '3', # PDB | |
| 76 | + } | |
| 77 | + }, | |
| 78 | + 'sources': [ ], | |
| 79 | + }] | |
| 80 | + ] | |
| 81 | + } | |
| 82 | + ] | |
| 83 | +} | |
| @@ -0,0 +1,1814 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: execution_table.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <type_traits> | |
| 8 | +#include <numeric> | |
| 9 | +#include <algorithm> | |
| 10 | +#include <array> | |
| 11 | +#include <cstdint> | |
| 12 | +#include <limits> | |
| 13 | +#include <random> | |
| 14 | +#include <disvm.h> | |
| 15 | +#include <vm_memory.h> | |
| 16 | +#include <opcodes.h> | |
| 17 | +#include <utils.h> | |
| 18 | +#include <exceptions.h> | |
| 19 | +#include <debug.h> | |
| 20 | +#include "execution_table.h" | |
| 21 | + | |
| 22 | +using namespace disvm; | |
| 23 | +using namespace disvm::runtime; | |
| 24 | + | |
| 25 | +namespace | |
| 26 | +{ | |
| 27 | + // | |
| 28 | + // Private VM primitive data type definitions | |
| 29 | + // | |
| 30 | + | |
| 31 | + using uword_t = uint32_t; // unsigned word_t | |
| 32 | + using ubig_t = uint64_t; // unsigned big_t | |
| 33 | + | |
| 34 | + template<typename ValueType> | |
| 35 | + ValueType& vt_ref(pointer_t vt) | |
| 36 | + { | |
| 37 | + assert(vt != nullptr); | |
| 38 | + return *reinterpret_cast<ValueType *>(vt); | |
| 39 | + } | |
| 40 | + | |
| 41 | + template<typename AllocType> | |
| 42 | + typename std::enable_if<std::is_base_of<vm_alloc_t, AllocType>::value, AllocType *>::type | |
| 43 | + at_val(pointer_t at) | |
| 44 | + { | |
| 45 | + assert(at != nullptr); | |
| 46 | + | |
| 47 | + return vm_alloc_t::from_allocation<AllocType>(*reinterpret_cast<pointer_t *>(at)); | |
| 48 | + } | |
| 49 | + | |
| 50 | + template<> | |
| 51 | + vm_alloc_t *at_val<vm_alloc_t>(pointer_t at) | |
| 52 | + { | |
| 53 | + assert(at != nullptr); | |
| 54 | + | |
| 55 | + return vm_alloc_t::from_allocation(*reinterpret_cast<pointer_t *>(at)); | |
| 56 | + } | |
| 57 | + | |
| 58 | + pointer_t &pt_ref(pointer_t pt) | |
| 59 | + { | |
| 60 | + assert(pt != nullptr); | |
| 61 | + return *reinterpret_cast<pointer_t *>(pt); | |
| 62 | + } | |
| 63 | + | |
| 64 | +#define EXEC_DECL(inst_name) void inst_name(vm_registers_t &r, vm_t& vm) | |
| 65 | + | |
| 66 | + EXEC_DECL(notimpl) { assert(false && "Instruction not implemented"); throw vm_system_exception{ "Instruction not implemented" }; } | |
| 67 | + | |
| 68 | + EXEC_DECL(invalid) { throw vm_system_exception{ "Illegal Dis VM instruction" }; } | |
| 69 | + | |
| 70 | + // [SPEC] Documented as 'Clear exception stack' and defined to be used at the discretion of the VM implementation. | |
| 71 | + // Implemented as a 'spare slot' in Inferno | |
| 72 | + EXEC_DECL(eclr) { assert(false && "The 'eclr' instruction is not expected to be used"); } | |
| 73 | + | |
| 74 | + // | |
| 75 | + // Bit-wise operations | |
| 76 | + // | |
| 77 | + | |
| 78 | + EXEC_DECL(andb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) & vt_ref<byte_t>(r.mid); } | |
| 79 | + EXEC_DECL(andw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) & vt_ref<word_t>(r.mid); } | |
| 80 | + EXEC_DECL(andl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) & vt_ref<big_t>(r.mid); } | |
| 81 | + EXEC_DECL(orb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) | vt_ref<byte_t>(r.mid); } | |
| 82 | + EXEC_DECL(orw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) | vt_ref<word_t>(r.mid); } | |
| 83 | + EXEC_DECL(orl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) | vt_ref<big_t>(r.mid); } | |
| 84 | + EXEC_DECL(xorb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) ^ vt_ref<byte_t>(r.mid); } | |
| 85 | + EXEC_DECL(xorw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) ^ vt_ref<word_t>(r.mid); } | |
| 86 | + EXEC_DECL(xorl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) ^ vt_ref<big_t>(r.mid); } | |
| 87 | + | |
| 88 | + // | |
| 89 | + // Bit-shift operations | |
| 90 | + // | |
| 91 | + | |
| 92 | + EXEC_DECL(shlb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.mid) << vt_ref<byte_t>(r.src); } | |
| 93 | + EXEC_DECL(shlw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.mid) << vt_ref<word_t>(r.src); } | |
| 94 | + EXEC_DECL(shll) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.mid) << vt_ref<big_t>(r.src); } | |
| 95 | + EXEC_DECL(shrb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.mid) >> vt_ref<byte_t>(r.src); } | |
| 96 | + EXEC_DECL(shrw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.mid) >> vt_ref<word_t>(r.src); } | |
| 97 | + EXEC_DECL(shrl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.mid) >> vt_ref<big_t>(r.src); } | |
| 98 | + EXEC_DECL(lsrw) { vt_ref<word_t>(r.dest) = vt_ref<uword_t>(r.mid) >> vt_ref<word_t>(r.src); } | |
| 99 | + EXEC_DECL(lsrl) { vt_ref<big_t>(r.dest) = vt_ref<ubig_t>(r.mid) >> vt_ref<big_t>(r.src); } | |
| 100 | + | |
| 101 | + // | |
| 102 | + // Conversion operations | |
| 103 | + // | |
| 104 | + | |
| 105 | + EXEC_DECL(cvtbw) { vt_ref<word_t>(r.dest) = std::numeric_limits<byte_t>::max() & vt_ref<byte_t>(r.src); } | |
| 106 | + EXEC_DECL(cvtwb) { vt_ref<byte_t>(r.dest) = std::numeric_limits<byte_t>::max() & vt_ref<word_t>(r.src); } | |
| 107 | + EXEC_DECL(cvtwl) { vt_ref<big_t>(r.dest) = std::numeric_limits<word_t>::max() & vt_ref<word_t>(r.src); } | |
| 108 | + EXEC_DECL(cvtlw) { vt_ref<word_t>(r.dest) = std::numeric_limits<word_t>::max() & vt_ref<big_t>(r.src); } | |
| 109 | + EXEC_DECL(cvtrf) { vt_ref<short_real_t>(r.dest) = static_cast<short_real_t>(vt_ref<real_t>(r.src)); } | |
| 110 | + EXEC_DECL(cvtfr) { vt_ref<real_t>(r.dest) = static_cast<real_t>(vt_ref<short_real_t>(r.src)); } | |
| 111 | + EXEC_DECL(cvtws) { vt_ref<short_word_t>(r.dest) = std::numeric_limits<short_word_t>::max() & vt_ref<word_t>(r.src); } | |
| 112 | + EXEC_DECL(cvtsw) { vt_ref<word_t>(r.dest) = std::numeric_limits<short_word_t>::max() & vt_ref<short_word_t>(r.src); } | |
| 113 | + EXEC_DECL(cvtlf) { vt_ref<real_t>(r.dest) = static_cast<real_t>(vt_ref<big_t>(r.src)); } | |
| 114 | + EXEC_DECL(cvtfl) | |
| 115 | + { | |
| 116 | + auto f = vt_ref<real_t>(r.src); | |
| 117 | + f = f < 0 ? (f - 0.5) : (f + 0.5); | |
| 118 | + vt_ref<big_t>(r.dest) = static_cast<big_t>(f); | |
| 119 | + } | |
| 120 | + | |
| 121 | + EXEC_DECL(cvtxf) { vt_ref<real_t>(r.dest) = static_cast<real_t>(vt_ref<word_t>(r.src)) * vt_ref<real_t>(r.mid); } | |
| 122 | + EXEC_DECL(cvtfx) | |
| 123 | + { | |
| 124 | + auto f = vt_ref<real_t>(r.src) * vt_ref<real_t>(r.mid); | |
| 125 | + f = f < 0 ? (f - 0.5) : (f + 0.5); | |
| 126 | + vt_ref<word_t>(r.dest) = static_cast<word_t>(f); | |
| 127 | + } | |
| 128 | + | |
| 129 | + EXEC_DECL(cvtwf) { vt_ref<real_t>(r.dest) = static_cast<real_t>(vt_ref<word_t>(r.src)); } | |
| 130 | + EXEC_DECL(cvtfw) | |
| 131 | + { | |
| 132 | + auto f = vt_ref<real_t>(r.src); | |
| 133 | + f = f < 0 ? (f - 0.5) : (f + 0.5); | |
| 134 | + vt_ref<word_t>(r.dest) = static_cast<word_t>(f); | |
| 135 | + } | |
| 136 | + | |
| 137 | + EXEC_DECL(cvtxx) | |
| 138 | + { | |
| 139 | + const auto p = vt_ref<word_t>(r.mid); | |
| 140 | + auto res = static_cast<big_t>(vt_ref<word_t>(r.src)); | |
| 141 | + if (p >= 0) | |
| 142 | + res <<= p; | |
| 143 | + else | |
| 144 | + res >>= (-p); | |
| 145 | + | |
| 146 | + vt_ref<word_t>(r.dest) = static_cast<word_t>(res); | |
| 147 | + } | |
| 148 | + | |
| 149 | + EXEC_DECL(cvtca) | |
| 150 | + { | |
| 151 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 152 | + | |
| 153 | + auto str = at_val<vm_string_t>(r.src); | |
| 154 | + auto new_array = new vm_array_t(str); | |
| 155 | + pt_ref(r.dest) = new_array->allocation(); | |
| 156 | + } | |
| 157 | + | |
| 158 | + EXEC_DECL(cvtac) | |
| 159 | + { | |
| 160 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 161 | + | |
| 162 | + auto str = pointer_t{}; | |
| 163 | + auto arr = at_val<vm_array_t>(r.src); | |
| 164 | + if (arr != nullptr) | |
| 165 | + { | |
| 166 | + if (arr->get_element_type() != intrinsic_type_desc::type<byte_t>()) | |
| 167 | + throw vm_user_exception{ "Invalid array element type for string conversion" }; | |
| 168 | + | |
| 169 | + auto new_string = new vm_string_t(arr->get_length(), reinterpret_cast<uint8_t *>(arr->at(0))); | |
| 170 | + str = new_string->allocation(); | |
| 171 | + } | |
| 172 | + | |
| 173 | + pt_ref(r.dest) = str; | |
| 174 | + } | |
| 175 | + | |
| 176 | + EXEC_DECL(cvtcw) | |
| 177 | + { | |
| 178 | + auto result = word_t{ 0 }; | |
| 179 | + auto str = at_val<vm_string_t>(r.src); | |
| 180 | + if (str != nullptr) | |
| 181 | + result = static_cast<word_t>(::strtol(str->str(), nullptr, 10)); | |
| 182 | + | |
| 183 | + vt_ref<word_t>(r.dest) = result; | |
| 184 | + } | |
| 185 | + | |
| 186 | + EXEC_DECL(cvtwc) | |
| 187 | + { | |
| 188 | + // String represents the largest possible string from a word. | |
| 189 | + auto buffer = std::array<char, sizeof("�2147483648")>{}; | |
| 190 | + | |
| 191 | + const auto w = vt_ref<word_t>(r.src); | |
| 192 | + const auto len = ::sprintf(buffer.data(), "%d", w); | |
| 193 | + assert(static_cast<std::size_t>(len) <= buffer.size()); | |
| 194 | + | |
| 195 | + auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); | |
| 196 | + auto str = at_val<vm_string_t>(r.dest); | |
| 197 | + dec_ref_count_and_free(str); | |
| 198 | + | |
| 199 | + pt_ref(r.dest) = new_string->allocation(); | |
| 200 | + } | |
| 201 | + | |
| 202 | + EXEC_DECL(cvtcf) | |
| 203 | + { | |
| 204 | + auto result = real_t{ 0.0 }; | |
| 205 | + auto str = at_val<vm_string_t>(r.src); | |
| 206 | + if (str != nullptr) | |
| 207 | + result = static_cast<real_t>(::strtod(str->str(), nullptr)); | |
| 208 | + | |
| 209 | + vt_ref<real_t>(r.dest) = result; | |
| 210 | + } | |
| 211 | + | |
| 212 | + EXEC_DECL(cvtfc) | |
| 213 | + { | |
| 214 | + // String represents the largest possible string from a double with scientific notation. | |
| 215 | + auto buffer = std::array<char, sizeof("-2.2250738585072014e-308")>{}; | |
| 216 | + | |
| 217 | + const auto real = vt_ref<real_t>(r.src); | |
| 218 | + const auto len = ::sprintf(buffer.data(), "%g", real); | |
| 219 | + assert(static_cast<std::size_t>(len) <= buffer.size()); | |
| 220 | + | |
| 221 | + auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); | |
| 222 | + | |
| 223 | + auto str = at_val<vm_string_t>(r.dest); | |
| 224 | + pt_ref(r.dest) = new_string->allocation(); | |
| 225 | + dec_ref_count_and_free(str); | |
| 226 | + } | |
| 227 | + | |
| 228 | + EXEC_DECL(cvtcl) | |
| 229 | + { | |
| 230 | + auto result = big_t{ 0 }; | |
| 231 | + auto str = at_val<vm_string_t>(r.src); | |
| 232 | + if (str != nullptr) | |
| 233 | + result = static_cast<big_t>(::strtoll(str->str(), nullptr, 10)); | |
| 234 | + | |
| 235 | + vt_ref<big_t>(r.dest) = result; | |
| 236 | + } | |
| 237 | + | |
| 238 | + EXEC_DECL(cvtlc) | |
| 239 | + { | |
| 240 | + // String represents the largest possible string from a 64-bit integer. | |
| 241 | + auto buffer = std::array<char, sizeof("-9223372036854775808")>{}; | |
| 242 | + | |
| 243 | + const auto b = vt_ref<big_t>(r.src); | |
| 244 | + const auto len = ::sprintf(buffer.data(), "%lld", b); | |
| 245 | + assert(static_cast<std::size_t>(len) <= buffer.size()); | |
| 246 | + | |
| 247 | + auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data())); | |
| 248 | + | |
| 249 | + auto str = at_val<vm_string_t>(r.dest); | |
| 250 | + pt_ref(r.dest) = new_string->allocation(); | |
| 251 | + dec_ref_count_and_free(str); | |
| 252 | + } | |
| 253 | + | |
| 254 | + // | |
| 255 | + // Arithmetic operations | |
| 256 | + // | |
| 257 | + | |
| 258 | + EXEC_DECL(negf) { vt_ref<real_t>(r.dest) = -vt_ref<real_t>(r.src); } | |
| 259 | + EXEC_DECL(addb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) + vt_ref<byte_t>(r.mid); } | |
| 260 | + EXEC_DECL(addw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) + vt_ref<word_t>(r.mid); } | |
| 261 | + EXEC_DECL(addl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) + vt_ref<big_t>(r.mid); } | |
| 262 | + EXEC_DECL(addf) { vt_ref<real_t>(r.dest) = vt_ref<real_t>(r.src) + vt_ref<real_t>(r.mid); } | |
| 263 | + EXEC_DECL(subb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.mid) - vt_ref<byte_t>(r.src); } | |
| 264 | + EXEC_DECL(subw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.mid) - vt_ref<word_t>(r.src); } | |
| 265 | + EXEC_DECL(subl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.mid) - vt_ref<big_t>(r.src); } | |
| 266 | + EXEC_DECL(subf) { vt_ref<real_t>(r.dest) = vt_ref<real_t>(r.mid) - vt_ref<real_t>(r.src); } | |
| 267 | + EXEC_DECL(mulb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) * vt_ref<byte_t>(r.mid); } | |
| 268 | + EXEC_DECL(mulw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) * vt_ref<word_t>(r.mid); } | |
| 269 | + EXEC_DECL(mull) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) * vt_ref<big_t>(r.mid); } | |
| 270 | + EXEC_DECL(mulf) { vt_ref<real_t>(r.dest) = vt_ref<real_t>(r.src) * vt_ref<real_t>(r.mid); } | |
| 271 | + | |
| 272 | + template<typename PrimitiveType> | |
| 273 | + void _div(vm_registers_t &r) | |
| 274 | + { | |
| 275 | + const auto denom = vt_ref<PrimitiveType>(r.src); | |
| 276 | + if (denom == PrimitiveType{ 0 }) | |
| 277 | + throw divide_by_zero{}; | |
| 278 | + | |
| 279 | + vt_ref<PrimitiveType>(r.dest) = vt_ref<PrimitiveType>(r.mid) / denom; | |
| 280 | + } | |
| 281 | + | |
| 282 | + EXEC_DECL(divb) { _div<byte_t>(r); } | |
| 283 | + EXEC_DECL(divw) { _div<word_t>(r); } | |
| 284 | + EXEC_DECL(divf) { _div<real_t>(r); } | |
| 285 | + EXEC_DECL(divl) { _div<big_t>(r); } | |
| 286 | + | |
| 287 | + template<typename PrimitiveType> | |
| 288 | + void _mod(vm_registers_t &r) | |
| 289 | + { | |
| 290 | + const auto denom = vt_ref<PrimitiveType>(r.src); | |
| 291 | + if (denom == PrimitiveType{ 0 }) | |
| 292 | + throw divide_by_zero{}; | |
| 293 | + | |
| 294 | + vt_ref<PrimitiveType>(r.dest) = vt_ref<PrimitiveType>(r.mid) % denom; | |
| 295 | + } | |
| 296 | + | |
| 297 | + EXEC_DECL(modb) { _mod<byte_t>(r); } | |
| 298 | + EXEC_DECL(modw) { _mod<word_t>(r); } | |
| 299 | + EXEC_DECL(modl) { _mod<big_t>(r); } | |
| 300 | + | |
| 301 | + // [SPEC] 'Exponent' op code | |
| 302 | + // Undocumented: (http://www.vitanuova.com/inferno/papers/dis.html) | |
| 303 | + // Limbo reference: (http://www.vitanuova.com/inferno/papers/addendum.pdf) | |
| 304 | + // | |
| 305 | + // Inferno implementation: (libinterp/xec.c) | |
| 306 | + // src1 = power | |
| 307 | + // src2 = base | |
| 308 | + // | |
| 309 | + // exp[w|l|f] src1 src2 dst | |
| 310 | + // dst = src2 'raised to' src1 | |
| 311 | + // | |
| 312 | + // Result type matches the type of base. Power should be type 'word'. | |
| 313 | + template<typename PrimitiveType> | |
| 314 | + void _exp(vm_registers_t &r) | |
| 315 | + { | |
| 316 | + auto base = vt_ref<PrimitiveType>(r.mid); | |
| 317 | + auto power = vt_ref<word_t>(r.src); | |
| 318 | + | |
| 319 | + auto inverse = false; | |
| 320 | + if (power < 0) | |
| 321 | + { | |
| 322 | + power = (-power); | |
| 323 | + inverse = true; | |
| 324 | + } | |
| 325 | + | |
| 326 | + auto result = PrimitiveType{ 1 }; | |
| 327 | + for (;;) | |
| 328 | + { | |
| 329 | + if ((power & 1) != 0) | |
| 330 | + result *= base; | |
| 331 | + | |
| 332 | + power >>= 1; | |
| 333 | + if (power == 0) | |
| 334 | + break; | |
| 335 | + | |
| 336 | + base *= base; | |
| 337 | + } | |
| 338 | + | |
| 339 | + if (inverse) | |
| 340 | + result = (1 / result); | |
| 341 | + | |
| 342 | + vt_ref<PrimitiveType>(r.dest) = result; | |
| 343 | + } | |
| 344 | + | |
| 345 | + EXEC_DECL(expw) { _exp<word_t>(r); } | |
| 346 | + EXEC_DECL(expl) { _exp<big_t>(r); } | |
| 347 | + EXEC_DECL(expf) { _exp<real_t>(r); } | |
| 348 | + | |
| 349 | + // [SPEC] 'Fixed point' op codes | |
| 350 | + // Undocumented: (http://www.vitanuova.com/inferno/papers/dis.html) | |
| 351 | + // Limbo reference: (http://www.vitanuova.com/inferno/papers/addendum.pdf) | |
| 352 | + EXEC_DECL(mulx) | |
| 353 | + { | |
| 354 | + const auto m = big_t{ vt_ref<word_t>(r.mid) }; | |
| 355 | + const auto s = big_t{ vt_ref<word_t>(r.src) }; | |
| 356 | + auto result = m * s; | |
| 357 | + | |
| 358 | + const auto p = *reinterpret_cast<word_t *>(r.stack.peek_frame()->fixed_point_register_2()); | |
| 359 | + if (p >= 0) | |
| 360 | + result <<= p; | |
| 361 | + else | |
| 362 | + result >>= (-p); | |
| 363 | + | |
| 364 | + vt_ref<word_t>(r.dest) = static_cast<word_t>(result); | |
| 365 | + } | |
| 366 | + | |
| 367 | + EXEC_DECL(divx) | |
| 368 | + { | |
| 369 | + auto denom = big_t{ vt_ref<word_t>(r.mid) }; | |
| 370 | + const auto p = *reinterpret_cast<word_t *>(r.stack.peek_frame()->fixed_point_register_2()); | |
| 371 | + if (p >= 0) | |
| 372 | + denom <<= p; | |
| 373 | + else | |
| 374 | + denom >>= (-p); | |
| 375 | + | |
| 376 | + const auto numerator = big_t{ vt_ref<word_t>(r.src) }; | |
| 377 | + auto result = denom / numerator; | |
| 378 | + | |
| 379 | + vt_ref<word_t>(r.dest) = static_cast<word_t>(result); | |
| 380 | + } | |
| 381 | + | |
| 382 | + // | |
| 383 | + // Move operations | |
| 384 | + // | |
| 385 | + | |
| 386 | + template<typename PrimitiveType> | |
| 387 | + void _mov_(pointer_t dest, pointer_t src, const type_descriptor_t *) | |
| 388 | + { | |
| 389 | + vt_ref<PrimitiveType>(dest) = vt_ref<PrimitiveType>(src); | |
| 390 | + } | |
| 391 | + | |
| 392 | + template<> | |
| 393 | + void _mov_<vm_alloc_t>(pointer_t dest, pointer_t src, const type_descriptor_t *) | |
| 394 | + { | |
| 395 | + auto alloc_prev = at_val<vm_alloc_t>(dest); | |
| 396 | + dec_ref_count_and_free(alloc_prev); | |
| 397 | + | |
| 398 | + auto alloc = at_val<vm_alloc_t>(src); | |
| 399 | + if (alloc != nullptr) | |
| 400 | + { | |
| 401 | + alloc->add_ref(); | |
| 402 | + pt_ref(dest) = alloc->allocation(); | |
| 403 | + } | |
| 404 | + else | |
| 405 | + { | |
| 406 | + pt_ref(dest) = nullptr; | |
| 407 | + } | |
| 408 | + } | |
| 409 | + | |
| 410 | + EXEC_DECL(movb) { _mov_<byte_t>(r.dest, r.src, nullptr); } | |
| 411 | + EXEC_DECL(movw) { _mov_<word_t>(r.dest, r.src, nullptr); } | |
| 412 | + EXEC_DECL(movf) { _mov_<real_t>(r.dest, r.src, nullptr); } | |
| 413 | + EXEC_DECL(movl) { _mov_<big_t>(r.dest, r.src, nullptr); } | |
| 414 | + EXEC_DECL(movp) { _mov_<vm_alloc_t>(r.dest, r.src, nullptr); } | |
| 415 | + | |
| 416 | + EXEC_DECL(movm) | |
| 417 | + { | |
| 418 | + assert(r.src != nullptr && r.dest != nullptr); | |
| 419 | + | |
| 420 | + std::memmove(r.dest, r.src, vt_ref<word_t>(r.mid)); | |
| 421 | + } | |
| 422 | + | |
| 423 | + EXEC_DECL(movmp) | |
| 424 | + { | |
| 425 | + auto type_id = vt_ref<word_t>(r.mid); | |
| 426 | + const auto &types = r.module_ref->type_section; | |
| 427 | + assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); | |
| 428 | + auto type = types[type_id]; | |
| 429 | + | |
| 430 | + disvm::runtime::inc_ref_count_in_memory(*type, r.src); | |
| 431 | + | |
| 432 | + // Decrement allocated pointers in the destination. | |
| 433 | + destroy_memory(*type, r.dest); | |
| 434 | + | |
| 435 | + assert(r.src != nullptr && r.dest != nullptr); | |
| 436 | + std::memmove(r.dest, r.src, type->size_in_bytes); | |
| 437 | + } | |
| 438 | + | |
| 439 | + // | |
| 440 | + // Compare and Branch operations | |
| 441 | + // | |
| 442 | + | |
| 443 | + EXEC_DECL(bltb) { if (vt_ref<byte_t>(r.src) < vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 444 | + EXEC_DECL(bgtb) { if (vt_ref<byte_t>(r.src) > vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 445 | + EXEC_DECL(bleb) { if (vt_ref<byte_t>(r.src) <= vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 446 | + EXEC_DECL(bgeb) { if (vt_ref<byte_t>(r.src) >= vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 447 | + EXEC_DECL(beqb) { if (vt_ref<byte_t>(r.src) == vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 448 | + EXEC_DECL(bneb) { if (vt_ref<byte_t>(r.src) != vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 449 | + | |
| 450 | + EXEC_DECL(bltw) { if (vt_ref<word_t>(r.src) < vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 451 | + EXEC_DECL(bgtw) { if (vt_ref<word_t>(r.src) > vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 452 | + EXEC_DECL(blew) { if (vt_ref<word_t>(r.src) <= vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 453 | + EXEC_DECL(bgew) { if (vt_ref<word_t>(r.src) >= vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 454 | + EXEC_DECL(beqw) { if (vt_ref<word_t>(r.src) == vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 455 | + EXEC_DECL(bnew) { if (vt_ref<word_t>(r.src) != vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 456 | + | |
| 457 | + EXEC_DECL(bltf) { if (vt_ref<real_t>(r.src) < vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 458 | + EXEC_DECL(bgtf) { if (vt_ref<real_t>(r.src) > vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 459 | + EXEC_DECL(blef) { if (vt_ref<real_t>(r.src) <= vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 460 | + EXEC_DECL(bgef) { if (vt_ref<real_t>(r.src) >= vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 461 | + EXEC_DECL(beqf) { if (vt_ref<real_t>(r.src) == vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 462 | + EXEC_DECL(bnef) { if (vt_ref<real_t>(r.src) != vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 463 | + | |
| 464 | + EXEC_DECL(bltl) { if (vt_ref<big_t>(r.src) < vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 465 | + EXEC_DECL(bgtl) { if (vt_ref<big_t>(r.src) > vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 466 | + EXEC_DECL(blel) { if (vt_ref<big_t>(r.src) <= vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 467 | + EXEC_DECL(bgel) { if (vt_ref<big_t>(r.src) >= vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 468 | + EXEC_DECL(beql) { if (vt_ref<big_t>(r.src) == vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 469 | + EXEC_DECL(bnel) { if (vt_ref<big_t>(r.src) != vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 470 | + | |
| 471 | + // Define case table constants | |
| 472 | + // [SPEC]Table format is partially defined in Dis VM specification | |
| 473 | + // [element count] ([low value (inc)][high value (exc)][program counter])* [fallback program counter] | |
| 474 | + struct case_op_constants | |
| 475 | + { | |
| 476 | + static const auto table_length_index = std::size_t{ 0 }; | |
| 477 | + static const auto first_entry_index = std::size_t{ 1 }; | |
| 478 | + static const auto entry_low_index = std::size_t{ 0 }; | |
| 479 | + static const auto entry_high_index = std::size_t{ 1 }; | |
| 480 | + static const auto entry_pc_index = std::size_t{ 2 }; | |
| 481 | + static const auto entry_length = std::size_t{ 3 }; | |
| 482 | + }; | |
| 483 | + | |
| 484 | + template<typename PrimitiveType> | |
| 485 | + void _case(vm_registers_t &r) | |
| 486 | + { | |
| 487 | + const auto value = vt_ref<PrimitiveType>(r.src); | |
| 488 | + | |
| 489 | + assert(r.dest != nullptr); | |
| 490 | + const auto table = reinterpret_cast<const PrimitiveType *>(r.dest); | |
| 491 | + auto entry_count = table[case_op_constants::table_length_index]; | |
| 492 | + assert(entry_count < std::numeric_limits<word_t>::max()); | |
| 493 | + | |
| 494 | + // Initialize the target PC with the fallback value. | |
| 495 | + // This value is located after all entries in the table. | |
| 496 | + auto target_pc = table[case_op_constants::first_entry_index + (entry_count * case_op_constants::entry_length)]; | |
| 497 | + | |
| 498 | + // Move to first entry | |
| 499 | + auto current_entry = table + case_op_constants::first_entry_index; | |
| 500 | + | |
| 501 | + // Find the matching table entry | |
| 502 | + for (; 0 < entry_count; --entry_count) | |
| 503 | + { | |
| 504 | + // Check if value is in range | |
| 505 | + if (value < current_entry[case_op_constants::entry_low_index] || current_entry[case_op_constants::entry_high_index] <= value) | |
| 506 | + { | |
| 507 | + current_entry += case_op_constants::entry_length; | |
| 508 | + continue; | |
| 509 | + } | |
| 510 | + | |
| 511 | + target_pc = current_entry[case_op_constants::entry_pc_index]; | |
| 512 | + break; | |
| 513 | + } | |
| 514 | + | |
| 515 | + assert(0 <= static_cast<std::size_t>(target_pc) && static_cast<std::size_t>(target_pc) < r.module_ref->code_section.size()); | |
| 516 | + r.pc = static_cast<vm_pc_t>(target_pc); | |
| 517 | + } | |
| 518 | + | |
| 519 | + EXEC_DECL(casew) { _case<word_t>(r); } | |
| 520 | + EXEC_DECL(casel) { _case<big_t>(r); } | |
| 521 | + | |
| 522 | + // [SPEC] The documentation for the 'casec' instruction is a bit sparse | |
| 523 | + // The full semantics are the case table should have string cases lexically | |
| 524 | + // ordered (i.e. e[n] < e[n + 1]). | |
| 525 | + EXEC_DECL(casec) | |
| 526 | + { | |
| 527 | + const auto value = at_val<vm_string_t>(r.src); | |
| 528 | + | |
| 529 | + assert(r.dest != nullptr); | |
| 530 | + const auto table = reinterpret_cast<const word_t *>(r.dest); | |
| 531 | + const auto entry_count = table[case_op_constants::table_length_index]; | |
| 532 | + | |
| 533 | + // Initialize the target PC with the fallback value. | |
| 534 | + // This value is located after all entries in the table. | |
| 535 | + auto target_pc = table[case_op_constants::first_entry_index + (entry_count * case_op_constants::entry_length)]; | |
| 536 | + | |
| 537 | + // Move to first entry | |
| 538 | + auto current_entry = table + case_op_constants::first_entry_index; | |
| 539 | + | |
| 540 | + // Find the matching table entry | |
| 541 | + for (auto entry = word_t{ 0 }; entry < entry_count; ++entry) | |
| 542 | + { | |
| 543 | + auto string_low = at_val<vm_string_t>(reinterpret_cast<pointer_t>(const_cast<word_t *>(current_entry + case_op_constants::entry_low_index))); | |
| 544 | + | |
| 545 | + // Compare the value against the low string | |
| 546 | + int result = vm_string_t::compare(value, string_low); | |
| 547 | + if (result != 0) | |
| 548 | + { | |
| 549 | + // If the value compares less than the low string then skip the high string. | |
| 550 | + if (result < 0) | |
| 551 | + { | |
| 552 | + current_entry += case_op_constants::entry_length; | |
| 553 | + continue; | |
| 554 | + } | |
| 555 | + | |
| 556 | + // Compare the value against the high string | |
| 557 | + auto string_high = at_val<vm_string_t>(reinterpret_cast<pointer_t>(const_cast<word_t *>(current_entry + case_op_constants::entry_high_index))); | |
| 558 | + if (string_high == nullptr || vm_string_t::compare(value, string_high) != 0) | |
| 559 | + { | |
| 560 | + current_entry += case_op_constants::entry_length; | |
| 561 | + continue; | |
| 562 | + } | |
| 563 | + } | |
| 564 | + | |
| 565 | + target_pc = current_entry[case_op_constants::entry_pc_index]; | |
| 566 | + break; | |
| 567 | + } | |
| 568 | + | |
| 569 | + r.pc = target_pc; | |
| 570 | + } | |
| 571 | + | |
| 572 | + // | |
| 573 | + // String operations | |
| 574 | + // | |
| 575 | + | |
| 576 | + EXEC_DECL(bltc) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) < 0) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 577 | + EXEC_DECL(bgtc) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) > 0) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 578 | + EXEC_DECL(blec) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) <= 0) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 579 | + EXEC_DECL(bgec) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) >= 0) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 580 | + EXEC_DECL(beqc) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) == 0) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 581 | + EXEC_DECL(bnec) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) != 0) r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 582 | + | |
| 583 | + EXEC_DECL(lenc) | |
| 584 | + { | |
| 585 | + auto len = word_t{ 0 }; | |
| 586 | + auto str = at_val<vm_string_t>(r.src); | |
| 587 | + if (str != nullptr) | |
| 588 | + len = str->get_length(); | |
| 589 | + | |
| 590 | + vt_ref<word_t>(r.dest) = len; | |
| 591 | + } | |
| 592 | + | |
| 593 | + EXEC_DECL(indc) | |
| 594 | + { | |
| 595 | + auto str = at_val<vm_string_t>(r.src); | |
| 596 | + if (str == nullptr) | |
| 597 | + throw dereference_nil{ "Index string" }; | |
| 598 | + | |
| 599 | + const auto index = vt_ref<word_t>(r.mid); | |
| 600 | + const auto rune = str->get_rune(index); | |
| 601 | + | |
| 602 | + vt_ref<rune_t>(r.dest) = rune; | |
| 603 | + } | |
| 604 | + | |
| 605 | + // [SPEC] 'Insert Character' op code | |
| 606 | + // Documented: (http://www.vitanuova.com/inferno/papers/dis.html) | |
| 607 | + // insc src1 src2 dst | |
| 608 | + // src1[src2] = dst | |
| 609 | + // | |
| 610 | + // Inferno implementation: (libinterp/string.c) | |
| 611 | + // insc src1 src2 dst | |
| 612 | + // dst[src2] = src1 | |
| 613 | + EXEC_DECL(insc) | |
| 614 | + { | |
| 615 | + auto rune = vt_ref<rune_t>(r.src); | |
| 616 | + auto index = vt_ref<word_t>(r.mid); | |
| 617 | + auto str = at_val<vm_string_t>(r.dest); | |
| 618 | + if (str == nullptr) | |
| 619 | + str = new vm_string_t(); | |
| 620 | + else if (str->get_ref_count() > 1) | |
| 621 | + str = new vm_string_t(*str, 0, str->get_length()); | |
| 622 | + | |
| 623 | + // Set the rune at the supplied index. | |
| 624 | + str->set_rune(index, rune); | |
| 625 | + | |
| 626 | + auto old_str = at_val<vm_string_t>(r.dest); | |
| 627 | + if (str != old_str) | |
| 628 | + { | |
| 629 | + pt_ref(r.dest) = str->allocation(); | |
| 630 | + dec_ref_count_and_free(old_str); | |
| 631 | + } | |
| 632 | + } | |
| 633 | + | |
| 634 | + vm_string_t *_addc(vm_string_t *s1, vm_string_t *s2, /* in out */ bool &append_to_s1) | |
| 635 | + { | |
| 636 | + if (s1 == nullptr) | |
| 637 | + { | |
| 638 | + if (s2 == nullptr) | |
| 639 | + return new vm_string_t(); | |
| 640 | + | |
| 641 | + return new vm_string_t(*s2, 0, s2->get_length()); | |
| 642 | + } | |
| 643 | + | |
| 644 | + // There are other references to this string so we will not alter it. | |
| 645 | + if (s1->get_ref_count() > 1) | |
| 646 | + append_to_s1 = false; | |
| 647 | + | |
| 648 | + if (s2 == nullptr) | |
| 649 | + { | |
| 650 | + if (append_to_s1) | |
| 651 | + return s1; | |
| 652 | + | |
| 653 | + return new vm_string_t(*s1, 0, s1->get_length()); | |
| 654 | + } | |
| 655 | + | |
| 656 | + return (append_to_s1) ? &(s1->append(*s2)) : new vm_string_t(*s1, *s2); | |
| 657 | + } | |
| 658 | + | |
| 659 | + // [SPEC] 'Concat strings' op code | |
| 660 | + // Documented: (http://www.vitanuova.com/inferno/papers/dis.html) | |
| 661 | + // addc src1 src2 dst | |
| 662 | + // dst = src1 + src2 | |
| 663 | + // | |
| 664 | + // Inferno implementation: (libinterp/string.c) | |
| 665 | + // addc src1 src2 dst | |
| 666 | + // dst = src2 + src1 | |
| 667 | + EXEC_DECL(addc) | |
| 668 | + { | |
| 669 | + auto s1 = at_val<vm_string_t>(r.mid); | |
| 670 | + auto s2 = at_val<vm_string_t>(r.src); | |
| 671 | + auto dest_maybe = at_val<vm_string_t>(r.dest); | |
| 672 | + | |
| 673 | + // If the source is the same as the destination and the | |
| 674 | + // destination is not null, append the string. This can | |
| 675 | + // change during the concat process. | |
| 676 | + auto append_to_source = dest_maybe != nullptr && s1 == dest_maybe; | |
| 677 | + auto str = _addc(s1, s2, append_to_source); | |
| 678 | + | |
| 679 | + if (!append_to_source) | |
| 680 | + { | |
| 681 | + pt_ref(r.dest) = str->allocation(); | |
| 682 | + dec_ref_count_and_free(dest_maybe); | |
| 683 | + } | |
| 684 | + } | |
| 685 | + | |
| 686 | + EXEC_DECL(slicec) | |
| 687 | + { | |
| 688 | + const auto start = vt_ref<word_t>(r.src); | |
| 689 | + const auto end = vt_ref<word_t>(r.mid); | |
| 690 | + auto str = at_val<vm_string_t>(r.dest); | |
| 691 | + | |
| 692 | + if (str != nullptr) | |
| 693 | + { | |
| 694 | + auto new_string = new vm_string_t(*str, start, end); | |
| 695 | + pt_ref(r.dest) = new_string->allocation(); | |
| 696 | + } | |
| 697 | + else if (start == 0 && end == 0) | |
| 698 | + { | |
| 699 | + pt_ref(r.dest) = nullptr; | |
| 700 | + } | |
| 701 | + else | |
| 702 | + { | |
| 703 | + throw dereference_nil{ "Slice a nil string" }; | |
| 704 | + } | |
| 705 | + | |
| 706 | + dec_ref_count_and_free(str); | |
| 707 | + } | |
| 708 | + | |
| 709 | + // | |
| 710 | + // Array operations | |
| 711 | + // | |
| 712 | + | |
| 713 | + EXEC_DECL(lena) | |
| 714 | + { | |
| 715 | + auto len = word_t{ 0 }; | |
| 716 | + auto arr = at_val<vm_array_t>(r.src); | |
| 717 | + if (arr != nullptr) | |
| 718 | + len = arr->get_length(); | |
| 719 | + | |
| 720 | + vt_ref<word_t>(r.dest) = len; | |
| 721 | + } | |
| 722 | + | |
| 723 | + EXEC_DECL(slicea) | |
| 724 | + { | |
| 725 | + const auto begin_index = vt_ref<word_t>(r.src); | |
| 726 | + const auto end_index = vt_ref<word_t>(r.mid); | |
| 727 | + const auto length = end_index - begin_index; | |
| 728 | + | |
| 729 | + auto arr = at_val<vm_array_t>(r.dest); | |
| 730 | + if (arr == nullptr) | |
| 731 | + { | |
| 732 | + if (length == 0) | |
| 733 | + return; | |
| 734 | + | |
| 735 | + throw vm_user_exception{ "Slice of empty array is invalid" }; | |
| 736 | + } | |
| 737 | + else if (begin_index < 0 || arr->get_length() < end_index || length < 0) | |
| 738 | + { | |
| 739 | + throw out_of_range_memory{}; | |
| 740 | + } | |
| 741 | + | |
| 742 | + auto new_array = new vm_array_t(arr, begin_index, length); | |
| 743 | + | |
| 744 | + if (arr->alloc_type->map_in_bytes > 0) | |
| 745 | + vm.get_garbage_collector().track_allocation(new_array); | |
| 746 | + | |
| 747 | + pt_ref(r.dest) = new_array->allocation(); | |
| 748 | + } | |
| 749 | + | |
| 750 | + EXEC_DECL(slicela) | |
| 751 | + { | |
| 752 | + auto src_arr = at_val<vm_array_t>(r.src); | |
| 753 | + if (src_arr == nullptr) | |
| 754 | + return; | |
| 755 | + | |
| 756 | + auto dest_arr = at_val<vm_array_t>(r.dest); | |
| 757 | + if (dest_arr == nullptr) | |
| 758 | + throw dereference_nil{ "Slice assignment" }; | |
| 759 | + | |
| 760 | + const auto begin_index = vt_ref<word_t>(r.mid); | |
| 761 | + dest_arr->copy_from(*src_arr, begin_index); | |
| 762 | + } | |
| 763 | + | |
| 764 | + void _index_in(vm_registers_t &r) | |
| 765 | + { | |
| 766 | + auto arr = at_val<vm_array_t>(r.src); | |
| 767 | + if (arr == nullptr) | |
| 768 | + throw dereference_nil{ "Indexing into array" }; | |
| 769 | + | |
| 770 | + auto index = vt_ref<word_t>(r.dest); | |
| 771 | + if (index < 0 || arr->get_length() <= index) | |
| 772 | + throw out_of_range_memory{ 0, arr->get_length(), index }; | |
| 773 | + | |
| 774 | + // Return the address, not the value | |
| 775 | + pt_ref(r.mid) = arr->at(index); | |
| 776 | + } | |
| 777 | + | |
| 778 | + EXEC_DECL(indb) { _index_in(r); } | |
| 779 | + EXEC_DECL(indw) { _index_in(r); } | |
| 780 | + EXEC_DECL(indf) { _index_in(r); } | |
| 781 | + EXEC_DECL(indl) { _index_in(r); } | |
| 782 | + EXEC_DECL(indx) { _index_in(r); } | |
| 783 | + | |
| 784 | + // | |
| 785 | + // List operations | |
| 786 | + // | |
| 787 | + | |
| 788 | + EXEC_DECL(lenl) | |
| 789 | + { | |
| 790 | + auto len = word_t{ 0 }; | |
| 791 | + auto list = at_val<vm_list_t>(r.src); | |
| 792 | + if (list != nullptr) | |
| 793 | + { | |
| 794 | + assert(list->alloc_type == vm_list_t::type_desc()); | |
| 795 | + len = list->get_length(); | |
| 796 | + } | |
| 797 | + | |
| 798 | + vt_ref<word_t>(r.dest) = len; | |
| 799 | + } | |
| 800 | + | |
| 801 | + template<typename PrimitiveType> | |
| 802 | + void _cons(vm_registers_t &r) | |
| 803 | + { | |
| 804 | + auto tail_maybe = at_val<vm_list_t>(r.dest); | |
| 805 | + if (tail_maybe != nullptr) | |
| 806 | + tail_maybe->release(); | |
| 807 | + | |
| 808 | + auto new_list = new vm_list_t(intrinsic_type_desc::type<PrimitiveType>(), tail_maybe); | |
| 809 | + | |
| 810 | + auto value = vt_ref<PrimitiveType>(r.src); | |
| 811 | + vt_ref<PrimitiveType>(new_list->value()) = value; | |
| 812 | + | |
| 813 | + pt_ref(r.dest) = new_list->allocation(); | |
| 814 | + } | |
| 815 | + | |
| 816 | + EXEC_DECL(consb) { _cons<byte_t>(r); } | |
| 817 | + EXEC_DECL(consw) { _cons<word_t>(r); } | |
| 818 | + EXEC_DECL(consf) { _cons<real_t>(r); } | |
| 819 | + EXEC_DECL(consl) { _cons<big_t>(r); } | |
| 820 | + | |
| 821 | + EXEC_DECL(consp) | |
| 822 | + { | |
| 823 | + auto tail_maybe = at_val<vm_list_t>(r.dest); | |
| 824 | + if (tail_maybe != nullptr) | |
| 825 | + tail_maybe->release(); | |
| 826 | + | |
| 827 | + // Create a new list | |
| 828 | + auto new_list = new vm_list_t(intrinsic_type_desc::type<pointer_t>(), tail_maybe); | |
| 829 | + auto alloc = at_val<vm_alloc_t>(r.src); | |
| 830 | + if (alloc != nullptr) | |
| 831 | + { | |
| 832 | + alloc->add_ref(); | |
| 833 | + pt_ref(new_list->value()) = alloc->allocation(); | |
| 834 | + | |
| 835 | + vm.get_garbage_collector().track_allocation(new_list); | |
| 836 | + } | |
| 837 | + else | |
| 838 | + { | |
| 839 | + pt_ref(new_list->value()) = nullptr; | |
| 840 | + } | |
| 841 | + | |
| 842 | + // Return the list | |
| 843 | + pt_ref(r.dest) = new_list->allocation(); | |
| 844 | + } | |
| 845 | + | |
| 846 | + EXEC_DECL(consmp) | |
| 847 | + { | |
| 848 | + auto type_id = vt_ref<word_t>(r.mid); | |
| 849 | + const auto &types = r.module_ref->type_section; | |
| 850 | + assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); | |
| 851 | + auto type = types[type_id]; | |
| 852 | + | |
| 853 | + // Increment the ref count for element content | |
| 854 | + inc_ref_count_in_memory(*type, r.src); | |
| 855 | + | |
| 856 | + auto tail_maybe = at_val<vm_list_t>(r.dest); | |
| 857 | + if (tail_maybe != nullptr) | |
| 858 | + tail_maybe->release(); | |
| 859 | + | |
| 860 | + // Create a new list | |
| 861 | + auto new_list = new vm_list_t(type, tail_maybe); | |
| 862 | + auto destination = new_list->value(); | |
| 863 | + std::memmove(destination, r.src, type->size_in_bytes); | |
| 864 | + | |
| 865 | + if (type->map_in_bytes > 0) | |
| 866 | + vm.get_garbage_collector().track_allocation(new_list); | |
| 867 | + | |
| 868 | + // Return the list | |
| 869 | + pt_ref(r.dest) = new_list->allocation(); | |
| 870 | + } | |
| 871 | + | |
| 872 | + template<typename PrimitiveType> | |
| 873 | + void _head(vm_registers_t &r) | |
| 874 | + { | |
| 875 | + auto head = at_val<vm_list_t>(r.src); | |
| 876 | + if (head == nullptr) | |
| 877 | + throw dereference_nil{ "Head of list" }; | |
| 878 | + | |
| 879 | + assert(head->get_element_type() == intrinsic_type_desc::type<PrimitiveType>()); | |
| 880 | + auto head_value = head->value(); | |
| 881 | + vt_ref<PrimitiveType>(r.dest) = vt_ref<PrimitiveType>(head_value); | |
| 882 | + } | |
| 883 | + | |
| 884 | + EXEC_DECL(headb) { _head<byte_t>(r); } | |
| 885 | + EXEC_DECL(headw) { _head<word_t>(r); } | |
| 886 | + EXEC_DECL(headf) { _head<real_t>(r); } | |
| 887 | + EXEC_DECL(headl) { _head<big_t>(r); } | |
| 888 | + | |
| 889 | + EXEC_DECL(headp) | |
| 890 | + { | |
| 891 | + auto list = at_val<vm_list_t>(r.src); | |
| 892 | + if (list == nullptr) | |
| 893 | + throw dereference_nil{ "Head of list" }; | |
| 894 | + | |
| 895 | + r.src = list->value(); | |
| 896 | + movp(r, vm); | |
| 897 | + } | |
| 898 | + | |
| 899 | + EXEC_DECL(headmp) | |
| 900 | + { | |
| 901 | + auto list = at_val<vm_list_t>(r.src); | |
| 902 | + if (list == nullptr) | |
| 903 | + throw dereference_nil{ "Head of list" }; | |
| 904 | + | |
| 905 | + r.src = list->value(); | |
| 906 | + movmp(r, vm); | |
| 907 | + } | |
| 908 | + | |
| 909 | + EXEC_DECL(tail) | |
| 910 | + { | |
| 911 | + auto list = at_val<vm_list_t>(r.src); | |
| 912 | + if (list == nullptr) | |
| 913 | + throw dereference_nil{ "Tail of list" }; | |
| 914 | + | |
| 915 | + assert(list->alloc_type == vm_list_t::type_desc()); | |
| 916 | + auto tail = pointer_t{}; | |
| 917 | + | |
| 918 | + auto tail_maybe = list->get_tail(); | |
| 919 | + if (tail_maybe != nullptr) | |
| 920 | + tail = tail_maybe->allocation(); | |
| 921 | + | |
| 922 | + // Use the address of the local variable. | |
| 923 | + r.src = reinterpret_cast<pointer_t>(&tail); | |
| 924 | + movp(r, vm); | |
| 925 | + } | |
| 926 | + | |
| 927 | + // | |
| 928 | + // Channel operations | |
| 929 | + // | |
| 930 | + | |
| 931 | + namespace | |
| 932 | + { | |
| 933 | + void _channel_movm(pointer_t dest, pointer_t src, const type_descriptor_t *type) | |
| 934 | + { | |
| 935 | + assert(type != nullptr); | |
| 936 | + std::memmove(dest, src, type->size_in_bytes); | |
| 937 | + } | |
| 938 | + | |
| 939 | + void _channel_movmp(pointer_t dest, pointer_t src, const type_descriptor_t *type) | |
| 940 | + { | |
| 941 | + assert(type != nullptr); | |
| 942 | + destroy_memory(*type, dest); | |
| 943 | + | |
| 944 | + inc_ref_count_in_memory(*type, src); | |
| 945 | + std::memmove(dest, src, type->size_in_bytes); | |
| 946 | + } | |
| 947 | + | |
| 948 | + // [SPEC] 'Create new channel' op codes | |
| 949 | + // Documented: (http://www.vitanuova.com/inferno/papers/dis.html) | |
| 950 | + // As documented the src2 register is not used, however the Inferno | |
| 951 | + // implementation does use the src2 register. It is checked against | |
| 952 | + // the dst register and if they are not equal the src2 register | |
| 953 | + // contains the internal buffer size of the channel. | |
| 954 | + // | |
| 955 | + // See Inferno implementation: (libinterp/xec.c) | |
| 956 | + void _newc_( | |
| 957 | + vm_registers_t &r, | |
| 958 | + const vm_t &vm, | |
| 959 | + std::shared_ptr<const type_descriptor_t> type_desc, | |
| 960 | + std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer) | |
| 961 | + { | |
| 962 | + auto buffer_len = word_t{ 0 }; | |
| 963 | + if (r.mid != r.dest) | |
| 964 | + { | |
| 965 | + buffer_len = vt_ref<word_t>(r.mid); | |
| 966 | + if (buffer_len < 0) | |
| 967 | + throw out_of_range_memory{}; | |
| 968 | + } | |
| 969 | + | |
| 970 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 971 | + auto new_channel = new vm_channel_t{ type_desc, transfer, buffer_len }; | |
| 972 | + | |
| 973 | + if (type_desc->map_in_bytes > 0) | |
| 974 | + vm.get_garbage_collector().track_allocation(new_channel); | |
| 975 | + | |
| 976 | + pt_ref(r.dest) = new_channel->allocation(); | |
| 977 | + } | |
| 978 | + } | |
| 979 | + | |
| 980 | + EXEC_DECL(newcb) { _newc_(r, vm, intrinsic_type_desc::type<byte_t>(), _mov_<byte_t>); } | |
| 981 | + EXEC_DECL(newcw) { _newc_(r, vm, intrinsic_type_desc::type<word_t>(), _mov_<word_t>); } | |
| 982 | + EXEC_DECL(newcf) { _newc_(r, vm, intrinsic_type_desc::type<real_t>(), _mov_<real_t>); } | |
| 983 | + EXEC_DECL(newcl) { _newc_(r, vm, intrinsic_type_desc::type<big_t>(), _mov_<big_t>); } | |
| 984 | + EXEC_DECL(newcp) { _newc_(r, vm, intrinsic_type_desc::type<pointer_t>(), _mov_<vm_alloc_t>); } | |
| 985 | + | |
| 986 | + EXEC_DECL(newcm) | |
| 987 | + { | |
| 988 | + auto memory_size = vt_ref<word_t>(r.src); | |
| 989 | + auto channel_data_type = type_descriptor_t::create(memory_size); | |
| 990 | + | |
| 991 | + _newc_(r, vm, channel_data_type, _channel_movm); | |
| 992 | + } | |
| 993 | + | |
| 994 | + EXEC_DECL(newcmp) | |
| 995 | + { | |
| 996 | + auto type_id = vt_ref<word_t>(r.src); | |
| 997 | + const auto &types = r.module_ref->type_section; | |
| 998 | + assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); | |
| 999 | + auto type = types[type_id]; | |
| 1000 | + | |
| 1001 | + _newc_(r, vm, type, _channel_movmp); | |
| 1002 | + } | |
| 1003 | + | |
| 1004 | + EXEC_DECL(send) | |
| 1005 | + { | |
| 1006 | + auto channel = at_val<vm_channel_t>(r.dest); | |
| 1007 | + if (channel == nullptr) | |
| 1008 | + throw dereference_nil{ "Send to channel" }; | |
| 1009 | + | |
| 1010 | + const auto current_thread_id = r.thread.get_thread_id(); | |
| 1011 | + | |
| 1012 | + assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); | |
| 1013 | + r.request_mutex.pending_request = true; | |
| 1014 | + | |
| 1015 | + assert(r.current_thread_state == vm_thread_state_t::running); | |
| 1016 | + r.current_thread_state = vm_thread_state_t::blocked_sending; | |
| 1017 | + | |
| 1018 | + // Setup the request | |
| 1019 | + auto request = vm_channel_request_t{ current_thread_id, r.request_mutex }; | |
| 1020 | + request.data = r.src; | |
| 1021 | + request.request_handled_callback = [&vm, &r, current_thread_id](const vm_channel_t &) | |
| 1022 | + { | |
| 1023 | + assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_sending); | |
| 1024 | + vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); | |
| 1025 | + }; | |
| 1026 | + | |
| 1027 | + if (channel->send_data(request)) | |
| 1028 | + r.current_thread_state = vm_thread_state_t::running; | |
| 1029 | + } | |
| 1030 | + | |
| 1031 | + EXEC_DECL(recv) | |
| 1032 | + { | |
| 1033 | + auto channel = at_val<vm_channel_t>(r.src); | |
| 1034 | + if (channel == nullptr) | |
| 1035 | + throw dereference_nil{ "Receive from channel" }; | |
| 1036 | + | |
| 1037 | + const auto current_thread_id = r.thread.get_thread_id(); | |
| 1038 | + | |
| 1039 | + assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); | |
| 1040 | + r.request_mutex.pending_request = true; | |
| 1041 | + | |
| 1042 | + assert(r.current_thread_state == vm_thread_state_t::running); | |
| 1043 | + r.current_thread_state = vm_thread_state_t::blocked_receiving; | |
| 1044 | + | |
| 1045 | + // Setup the request | |
| 1046 | + auto request = vm_channel_request_t { current_thread_id, r.request_mutex }; | |
| 1047 | + request.data = r.dest; | |
| 1048 | + request.request_handled_callback = [&vm, &r, current_thread_id](const vm_channel_t &) | |
| 1049 | + { | |
| 1050 | + assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_receiving); | |
| 1051 | + vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); | |
| 1052 | + }; | |
| 1053 | + | |
| 1054 | + if (channel->receive_data(request)) | |
| 1055 | + r.current_thread_state = vm_thread_state_t::running; | |
| 1056 | + } | |
| 1057 | + | |
| 1058 | + namespace | |
| 1059 | + { | |
| 1060 | + std::minstd_rand alt_channel_selector; | |
| 1061 | + | |
| 1062 | + word_t cancel_outstanding_channel_requests(uint32_t request_thread_id, vm_alt_stack_layout_t *alt, const vm_channel_t &handled_channel) | |
| 1063 | + { | |
| 1064 | + auto alloc = handled_channel.allocation(); | |
| 1065 | + | |
| 1066 | + auto handled_index = word_t{ -1 }; | |
| 1067 | + const auto channel_count = (alt->send_count + alt->receive_count); | |
| 1068 | + for (auto i = word_t{ 0 }; i < channel_count; ++i) | |
| 1069 | + { | |
| 1070 | + auto ch_pointer = reinterpret_cast<pointer_t>(alt->channels[i].alloc); | |
| 1071 | + if (ch_pointer == alloc) | |
| 1072 | + { | |
| 1073 | + assert(handled_index == -1 && "Index should only be set once"); | |
| 1074 | + handled_index = i; | |
| 1075 | + continue; | |
| 1076 | + } | |
| 1077 | + | |
| 1078 | + auto ch = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&ch_pointer)); | |
| 1079 | + ch->cancel_request(request_thread_id); | |
| 1080 | + } | |
| 1081 | + | |
| 1082 | + assert(handled_index != -1 && "Index should be set"); | |
| 1083 | + return handled_index; | |
| 1084 | + } | |
| 1085 | + | |
| 1086 | + void exec_alt(vm_registers_t &r, const vm_t &vm, const bool blocking) | |
| 1087 | + { | |
| 1088 | + auto alt = reinterpret_cast<vm_alt_stack_layout_t *>(r.src); | |
| 1089 | + assert(alt != nullptr); | |
| 1090 | + | |
| 1091 | + const auto current_thread_id = r.thread.get_thread_id(); | |
| 1092 | + | |
| 1093 | + assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); | |
| 1094 | + r.request_mutex.pending_request = true; | |
| 1095 | + | |
| 1096 | + assert(r.current_thread_state == vm_thread_state_t::running); | |
| 1097 | + r.current_thread_state = vm_thread_state_t::blocked_in_alt; | |
| 1098 | + | |
| 1099 | + // Create request callback | |
| 1100 | + auto alt_request_handled_callback = [&vm, &r, alt, current_thread_id](const vm_channel_t &handled_channel) | |
| 1101 | + { | |
| 1102 | + // Cancel all non-handled requests and determine the | |
| 1103 | + // index of the channel that handled this request. | |
| 1104 | + auto handled_index = cancel_outstanding_channel_requests(current_thread_id, alt, handled_channel); | |
| 1105 | + | |
| 1106 | + // Set the index of the channel that handled the request | |
| 1107 | + vt_ref<word_t>(r.dest) = handled_index; | |
| 1108 | + | |
| 1109 | + assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_in_alt); | |
| 1110 | + vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); | |
| 1111 | + }; | |
| 1112 | + | |
| 1113 | + const auto channel_count = (alt->send_count + alt->receive_count); | |
| 1114 | + assert(channel_count > 0); | |
| 1115 | + | |
| 1116 | + // Generate a random channel index sequence. | |
| 1117 | + auto random_idx_sequence = std::vector<word_t>(channel_count); | |
| 1118 | + std::iota(std::begin(random_idx_sequence), std::end(random_idx_sequence), 0); | |
| 1119 | + std::shuffle(std::begin(random_idx_sequence), std::end(random_idx_sequence), alt_channel_selector); | |
| 1120 | + | |
| 1121 | + auto requests_queued = uint32_t{ 0 }; | |
| 1122 | + auto handled = bool{}; | |
| 1123 | + for (auto rnd_idx : random_idx_sequence) | |
| 1124 | + { | |
| 1125 | + auto &channel_stack_layout = alt->channels[rnd_idx]; | |
| 1126 | + if (channel_stack_layout.alloc == runtime_constants::nil) | |
| 1127 | + throw dereference_nil{ "Alt channel" }; | |
| 1128 | + | |
| 1129 | + auto channel = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&channel_stack_layout.alloc)); | |
| 1130 | + | |
| 1131 | + auto request = vm_channel_request_t{ current_thread_id, r.request_mutex }; | |
| 1132 | + request.data = reinterpret_cast<pointer_t>(channel_stack_layout.data); | |
| 1133 | + request.request_handled_callback = alt_request_handled_callback; | |
| 1134 | + | |
| 1135 | + if (rnd_idx < alt->send_count) | |
| 1136 | + handled = channel->send_data(request); | |
| 1137 | + else | |
| 1138 | + handled = channel->receive_data(request); | |
| 1139 | + | |
| 1140 | + if (handled) | |
| 1141 | + { | |
| 1142 | + // Check if requests need to be handled | |
| 1143 | + if (requests_queued > 0) | |
| 1144 | + { | |
| 1145 | + auto handled_index = cancel_outstanding_channel_requests(current_thread_id, alt, *channel); | |
| 1146 | + assert(handled_index == rnd_idx); | |
| 1147 | + } | |
| 1148 | + | |
| 1149 | + vt_ref<word_t>(r.dest) = rnd_idx; | |
| 1150 | + r.current_thread_state = vm_thread_state_t::running; | |
| 1151 | + return; | |
| 1152 | + } | |
| 1153 | + else | |
| 1154 | + { | |
| 1155 | + requests_queued++; | |
| 1156 | + } | |
| 1157 | + } | |
| 1158 | + | |
| 1159 | + // If this is a non-blocking alt request and we are here no channels | |
| 1160 | + // were immediately able to handle the request. Try to lock the request | |
| 1161 | + // so we can cancel it in all channels. | |
| 1162 | + if (!blocking && r.request_mutex.pending_request_lock.try_lock()) | |
| 1163 | + { | |
| 1164 | + std::lock_guard<std::mutex> lock{ r.request_mutex.pending_request_lock, std::adopt_lock }; | |
| 1165 | + // Check if a channel handled the request between the initial queueing and now. | |
| 1166 | + if (r.request_mutex.pending_request) | |
| 1167 | + { | |
| 1168 | + r.request_mutex.pending_request = false; | |
| 1169 | + | |
| 1170 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) | |
| 1171 | + debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "alt: request: non-blocking\n"); | |
| 1172 | + | |
| 1173 | + // Cancel request in all channels. | |
| 1174 | + for (auto i = word_t{ 0 }; i < channel_count; ++i) | |
| 1175 | + { | |
| 1176 | + auto ch_pointer = reinterpret_cast<pointer_t>(alt->channels[i].alloc); | |
| 1177 | + auto ch = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&ch_pointer)); | |
| 1178 | + ch->cancel_request(current_thread_id); | |
| 1179 | + } | |
| 1180 | + | |
| 1181 | + // Index of a non-blocking request is the final index of the alt. | |
| 1182 | + vt_ref<word_t>(r.dest) = channel_count; | |
| 1183 | + r.current_thread_state = vm_thread_state_t::running; | |
| 1184 | + } | |
| 1185 | + } | |
| 1186 | + } | |
| 1187 | + } | |
| 1188 | + | |
| 1189 | + EXEC_DECL(alt) | |
| 1190 | + { | |
| 1191 | + exec_alt(r, vm, true /* blocking */); | |
| 1192 | + } | |
| 1193 | + | |
| 1194 | + EXEC_DECL(nbalt) | |
| 1195 | + { | |
| 1196 | + exec_alt(r, vm, false /* blocking */); | |
| 1197 | + } | |
| 1198 | + | |
| 1199 | + // | |
| 1200 | + // Dynamic allocation operations | |
| 1201 | + // | |
| 1202 | + | |
| 1203 | + namespace | |
| 1204 | + { | |
| 1205 | + void new_instance(vm_registers_t &r, const vm_t &vm, const bool zero_memory) | |
| 1206 | + { | |
| 1207 | + const auto type_id = vt_ref<word_t>(r.src); | |
| 1208 | + const auto &types = r.module_ref->type_section; | |
| 1209 | + assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); | |
| 1210 | + | |
| 1211 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 1212 | + | |
| 1213 | + auto type_desc = types[type_id]; | |
| 1214 | + auto new_alloc = !zero_memory ? vm_alloc_t::allocate(type_desc) : vm_alloc_t::allocate(type_desc, vm_alloc_t::zero_memory); | |
| 1215 | + | |
| 1216 | + if (type_desc->map_in_bytes > 0) | |
| 1217 | + vm.get_garbage_collector().track_allocation(new_alloc); | |
| 1218 | + | |
| 1219 | + pt_ref(r.dest) = new_alloc->allocation(); | |
| 1220 | + } | |
| 1221 | + } | |
| 1222 | + | |
| 1223 | + EXEC_DECL(new_) | |
| 1224 | + { | |
| 1225 | + new_instance(r, vm, false /* zero memory */); | |
| 1226 | + } | |
| 1227 | + | |
| 1228 | + EXEC_DECL(newz) | |
| 1229 | + { | |
| 1230 | + new_instance(r, vm, true /* zero memory */); | |
| 1231 | + } | |
| 1232 | + | |
| 1233 | + EXEC_DECL(mnewz) | |
| 1234 | + { | |
| 1235 | + auto target_module = at_val<vm_module_ref_t>(r.src); | |
| 1236 | + if (target_module == nullptr) | |
| 1237 | + throw vm_user_exception{ "Module not loaded" }; | |
| 1238 | + | |
| 1239 | + const auto type_id = vt_ref<word_t>(r.mid); | |
| 1240 | + const auto &types = target_module->type_section; | |
| 1241 | + assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); | |
| 1242 | + | |
| 1243 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 1244 | + | |
| 1245 | + auto type_desc = types[type_id]; | |
| 1246 | + auto new_alloc = vm_alloc_t::allocate(type_desc, vm_alloc_t::zero_memory); | |
| 1247 | + | |
| 1248 | + if (type_desc->map_in_bytes > 0) | |
| 1249 | + vm.get_garbage_collector().track_allocation(new_alloc); | |
| 1250 | + | |
| 1251 | + pt_ref(r.dest) = new_alloc->allocation(); | |
| 1252 | + } | |
| 1253 | + | |
| 1254 | + namespace | |
| 1255 | + { | |
| 1256 | + void new_array(vm_registers_t &r, const vm_t &vm, const bool zero_memory) | |
| 1257 | + { | |
| 1258 | + assert(r.module_ref != nullptr); | |
| 1259 | + const auto &types = r.module_ref->type_section; | |
| 1260 | + | |
| 1261 | + const auto type_id = vt_ref<word_t>(r.mid); | |
| 1262 | + assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); | |
| 1263 | + auto type_desc = types[type_id]; | |
| 1264 | + | |
| 1265 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 1266 | + | |
| 1267 | + const auto array_size = vt_ref<word_t>(r.src); | |
| 1268 | + auto new_array = !zero_memory ? new vm_array_t(type_desc, array_size) : new vm_array_t(type_desc, array_size, vm_alloc_t::zero_memory); | |
| 1269 | + | |
| 1270 | + if (type_desc->map_in_bytes > 0) | |
| 1271 | + vm.get_garbage_collector().track_allocation(new_array); | |
| 1272 | + | |
| 1273 | + pt_ref(r.dest) = new_array->allocation(); | |
| 1274 | + } | |
| 1275 | + } | |
| 1276 | + | |
| 1277 | + EXEC_DECL(newa) | |
| 1278 | + { | |
| 1279 | + new_array(r, vm, false /* zero memory */); | |
| 1280 | + } | |
| 1281 | + | |
| 1282 | + EXEC_DECL(newaz) | |
| 1283 | + { | |
| 1284 | + new_array(r, vm, true /* zero memory */); | |
| 1285 | + } | |
| 1286 | + | |
| 1287 | + EXEC_DECL(tcmp) | |
| 1288 | + { | |
| 1289 | + auto s = at_val<vm_alloc_t>(r.src); | |
| 1290 | + auto d = at_val<vm_alloc_t>(r.dest); | |
| 1291 | + | |
| 1292 | + if (s == nullptr) | |
| 1293 | + return; | |
| 1294 | + | |
| 1295 | + if (d == nullptr || s->alloc_type != d->alloc_type) | |
| 1296 | + throw type_violation{}; | |
| 1297 | + } | |
| 1298 | + | |
| 1299 | + // | |
| 1300 | + // Stack operations | |
| 1301 | + // | |
| 1302 | + | |
| 1303 | + // [SPEC] 'runt' op code | |
| 1304 | + // Undocumented: (http://www.vitanuova.com/inferno/papers/dis.html) | |
| 1305 | + // | |
| 1306 | + // Inferno implementation: (libinterp/xec.c) | |
| 1307 | + // No operands and appears to be a type of NOP instruction. | |
| 1308 | + EXEC_DECL(runt) { /* NOP */ } | |
| 1309 | + | |
| 1310 | + EXEC_DECL(lea) { pt_ref(r.dest) = r.src; } | |
| 1311 | + | |
| 1312 | + EXEC_DECL(frame) | |
| 1313 | + { | |
| 1314 | + const auto &types = r.module_ref->type_section; | |
| 1315 | + | |
| 1316 | + const auto frame_type_id = vt_ref<word_t>(r.src); | |
| 1317 | + assert(0 <= frame_type_id && frame_type_id < static_cast<word_t>(types.size())); | |
| 1318 | + const auto frame_type = types[frame_type_id]; | |
| 1319 | + | |
| 1320 | + pt_ref(r.dest) = r.stack.alloc_frame(frame_type)->base(); | |
| 1321 | + } | |
| 1322 | + | |
| 1323 | + EXEC_DECL(mframe) | |
| 1324 | + { | |
| 1325 | + const auto target_module = at_val<vm_module_ref_t>(r.src); | |
| 1326 | + if (target_module == nullptr) | |
| 1327 | + throw vm_user_exception{ "Module not loaded" }; | |
| 1328 | + | |
| 1329 | + const auto &types = target_module->type_section; | |
| 1330 | + | |
| 1331 | + // Get the function ref index for this module. | |
| 1332 | + const auto function_id = vt_ref<word_t>(r.mid); | |
| 1333 | + const auto &function_ref = target_module->get_function_ref(function_id); | |
| 1334 | + | |
| 1335 | + const auto frame_type_id = function_ref.frame_type; | |
| 1336 | + assert(0 <= frame_type_id && frame_type_id < static_cast<word_t>(types.size())); | |
| 1337 | + const auto frame_type = types[frame_type_id]; | |
| 1338 | + | |
| 1339 | + pt_ref(r.dest) = r.stack.alloc_frame(frame_type)->base(); | |
| 1340 | + } | |
| 1341 | + | |
| 1342 | + EXEC_DECL(ret) | |
| 1343 | + { | |
| 1344 | + const auto current_top_frame = r.stack.peek_frame(); | |
| 1345 | + assert(current_top_frame != nullptr); | |
| 1346 | + r.pc = current_top_frame->prev_pc(); | |
| 1347 | + | |
| 1348 | + if (current_top_frame->prev_module_ref() != nullptr) | |
| 1349 | + { | |
| 1350 | + dec_ref_count_and_free(r.module_ref); | |
| 1351 | + r.module_ref = current_top_frame->prev_module_ref(); | |
| 1352 | + current_top_frame->prev_module_ref() = nullptr; | |
| 1353 | + | |
| 1354 | + dec_ref_count_and_free(r.mp_base); | |
| 1355 | + r.mp_base = r.module_ref->mp_base; | |
| 1356 | + } | |
| 1357 | + | |
| 1358 | + r.stack.pop_frame(); | |
| 1359 | + | |
| 1360 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>()) | |
| 1361 | + debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "exit: function\n"); | |
| 1362 | + } | |
| 1363 | + | |
| 1364 | + EXEC_DECL(call) | |
| 1365 | + { | |
| 1366 | + auto top_frame = r.stack.push_frame(); | |
| 1367 | + top_frame->prev_pc() = r.pc; | |
| 1368 | + | |
| 1369 | +#ifndef NDEBUG | |
| 1370 | + // Validate the stack state | |
| 1371 | + const auto frame_base_pointer = pt_ref(r.src); | |
| 1372 | + assert(frame_base_pointer != nullptr); | |
| 1373 | + assert(top_frame->base() == frame_base_pointer); | |
| 1374 | +#endif | |
| 1375 | + | |
| 1376 | + r.pc = vt_ref<vm_pc_t>(r.dest); | |
| 1377 | + | |
| 1378 | + debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "enter: function\n"); | |
| 1379 | + } | |
| 1380 | + | |
| 1381 | + EXEC_DECL(mcall) | |
| 1382 | + { | |
| 1383 | + auto target_module = at_val<vm_module_ref_t>(r.dest); | |
| 1384 | + if (target_module == nullptr) | |
| 1385 | + throw vm_user_exception{ "Module not loaded" }; | |
| 1386 | + | |
| 1387 | + // Get the pc for the function call into this module | |
| 1388 | + const auto export_id = vt_ref<word_t>(r.mid); | |
| 1389 | + const auto &function_ref = target_module->get_function_ref(export_id); | |
| 1390 | + const auto function_pc = function_ref.entry_pc; | |
| 1391 | + assert(static_cast<std::size_t>(function_pc) < target_module->code_section.size()); | |
| 1392 | + | |
| 1393 | + // Push the next frame | |
| 1394 | + auto top_frame = r.stack.push_frame(); | |
| 1395 | + | |
| 1396 | +#ifndef NDEBUG | |
| 1397 | + // Validate the stack state | |
| 1398 | + const auto frame_base_pointer = pt_ref(r.src); | |
| 1399 | + assert(frame_base_pointer != nullptr); | |
| 1400 | + assert(top_frame->base() == frame_base_pointer); | |
| 1401 | +#endif | |
| 1402 | + | |
| 1403 | + // Store current state | |
| 1404 | + top_frame->prev_pc() = r.pc; | |
| 1405 | + top_frame->prev_module_ref() = r.module_ref; | |
| 1406 | + | |
| 1407 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>()) | |
| 1408 | + { | |
| 1409 | + debug::log_msg( | |
| 1410 | + debug::component_trace_t::stack, | |
| 1411 | + debug::log_level_t::debug, | |
| 1412 | + "enter: function: inter-module: %d >>%s<< %d %s\n", | |
| 1413 | + export_id, | |
| 1414 | + target_module->module->module_name->str(), | |
| 1415 | + function_pc, | |
| 1416 | + (target_module->is_builtin_module() ? "true" : "false")); | |
| 1417 | + } | |
| 1418 | + | |
| 1419 | + // Set registers for execution | |
| 1420 | + r.module_ref = target_module; | |
| 1421 | + r.module_ref->add_ref(); | |
| 1422 | + r.mp_base = r.module_ref->mp_base; | |
| 1423 | + r.pc = function_pc; | |
| 1424 | + | |
| 1425 | + if (r.module_ref->is_builtin_module()) | |
| 1426 | + { | |
| 1427 | + // Execute the built-in function, then return | |
| 1428 | + const auto &inst = r.module_ref->code_section[function_pc]; | |
| 1429 | + assert(r.mp_base == nullptr && "Built-in modules shouldn't have module data (MP register)"); | |
| 1430 | + | |
| 1431 | + r.current_thread_state = vm_thread_state_t::release; | |
| 1432 | + inst.native(r, vm); | |
| 1433 | + ret(r, vm); | |
| 1434 | + r.current_thread_state = vm_thread_state_t::running; | |
| 1435 | + } | |
| 1436 | + else | |
| 1437 | + { | |
| 1438 | + r.mp_base->add_ref(); | |
| 1439 | + } | |
| 1440 | + } | |
| 1441 | + | |
| 1442 | + EXEC_DECL(jmp) { r.pc = vt_ref<vm_pc_t>(r.dest); } | |
| 1443 | + | |
| 1444 | + EXEC_DECL(goto_) | |
| 1445 | + { | |
| 1446 | + auto pc_index = vt_ref<word_t>(r.src); | |
| 1447 | + | |
| 1448 | + assert(r.dest != nullptr); | |
| 1449 | + auto pc_table = reinterpret_cast<vm_pc_t *>(r.dest); | |
| 1450 | + | |
| 1451 | + r.pc = pc_table[pc_index]; | |
| 1452 | + } | |
| 1453 | + | |
| 1454 | + // | |
| 1455 | + // Thread operations | |
| 1456 | + // | |
| 1457 | + | |
| 1458 | + EXEC_DECL(spawn) | |
| 1459 | + { | |
| 1460 | + // Arguments to the spawned thread are passed on the current | |
| 1461 | + // thread's stack in a new frame. | |
| 1462 | + auto spawned_frame = r.stack.push_frame(); | |
| 1463 | + | |
| 1464 | +#ifndef NDEBUG | |
| 1465 | + // Validate the stack state | |
| 1466 | + const auto frame_base_pointer = pt_ref(r.src); | |
| 1467 | + assert(frame_base_pointer != nullptr); | |
| 1468 | + assert(spawned_frame->base() == frame_base_pointer); | |
| 1469 | +#endif | |
| 1470 | + | |
| 1471 | + const auto starting_pc = vt_ref<vm_pc_t>(r.dest); | |
| 1472 | + | |
| 1473 | + assert(r.module_ref != nullptr); | |
| 1474 | + vm.fork(r.thread, *r.module_ref, *spawned_frame, starting_pc); | |
| 1475 | + | |
| 1476 | + // After the new thread is forked pop off the argument passing frame. | |
| 1477 | + r.stack.pop_frame(); | |
| 1478 | + } | |
| 1479 | + | |
| 1480 | + EXEC_DECL(mspawn) | |
| 1481 | + { | |
| 1482 | + // Arguments to the spawned thread are passed on the current | |
| 1483 | + // thread's stack in a new frame. | |
| 1484 | + auto spawned_frame = r.stack.push_frame(); | |
| 1485 | + | |
| 1486 | +#ifndef NDEBUG | |
| 1487 | + // Validate the stack state | |
| 1488 | + const auto frame_base_pointer = pt_ref(r.src); | |
| 1489 | + assert(frame_base_pointer != nullptr); | |
| 1490 | + assert(spawned_frame->base() == frame_base_pointer); | |
| 1491 | +#endif | |
| 1492 | + | |
| 1493 | + const auto import_function_id = vt_ref<word_t>(r.mid); | |
| 1494 | + auto target_module_ref = at_val<vm_module_ref_t>(r.dest); | |
| 1495 | + assert(target_module_ref != nullptr); | |
| 1496 | + | |
| 1497 | + const auto &function_ref = target_module_ref->get_function_ref(import_function_id); | |
| 1498 | + | |
| 1499 | + vm.fork(r.thread, *target_module_ref, *spawned_frame, function_ref.entry_pc); | |
| 1500 | + | |
| 1501 | + // After the new thread is forked pop off the argument passing frame. | |
| 1502 | + r.stack.pop_frame(); | |
| 1503 | + } | |
| 1504 | + | |
| 1505 | + EXEC_DECL(exit) | |
| 1506 | + { | |
| 1507 | + assert(false && "test failure"); | |
| 1508 | + r.current_thread_state = vm_thread_state_t::exiting; | |
| 1509 | + } | |
| 1510 | + | |
| 1511 | + // | |
| 1512 | + // Exception operations | |
| 1513 | + // | |
| 1514 | + | |
| 1515 | + EXEC_DECL(raise) | |
| 1516 | + { | |
| 1517 | + auto e = at_val<vm_alloc_t>(r.src); | |
| 1518 | + if (e == nullptr) | |
| 1519 | + throw dereference_nil{ "Raise exception" }; | |
| 1520 | + | |
| 1521 | + const vm_string_t *exception_id; | |
| 1522 | + | |
| 1523 | + // Determine if a 'string' or 'exception' type was raised. | |
| 1524 | + if (e->alloc_type == vm_string_t::type_desc()) | |
| 1525 | + { | |
| 1526 | + // String | |
| 1527 | + exception_id = dynamic_cast<vm_string_t *>(e); | |
| 1528 | + | |
| 1529 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::exception>()) | |
| 1530 | + debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "raise: string: >>%s<<\n", exception_id->str()); | |
| 1531 | + } | |
| 1532 | + else | |
| 1533 | + { | |
| 1534 | + // [SPEC] The exception format is not formally specified. | |
| 1535 | + // The first pointer in the exception is a string type | |
| 1536 | + // that is used to uniquely identify the exception type. | |
| 1537 | + // The remainder of the type is user-defined. | |
| 1538 | + auto content = e->allocation(); | |
| 1539 | + exception_id = at_val<vm_string_t>(content); | |
| 1540 | + | |
| 1541 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::exception>()) | |
| 1542 | + debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "raise: exception: >>%s<<\n", exception_id->str()); | |
| 1543 | + } | |
| 1544 | + | |
| 1545 | + const handler_t *handler; | |
| 1546 | + vm_frame_t *target_frame; | |
| 1547 | + vm_pc_t handler_pc; | |
| 1548 | + | |
| 1549 | + // The faulting pc is minus one since the VM has already incremented the counter. | |
| 1550 | + const auto faulting_pc = r.pc - 1; | |
| 1551 | + | |
| 1552 | + assert(r.module_ref != nullptr); | |
| 1553 | + std::tie(handler, target_frame, handler_pc) = find_exception_handler(r.stack, *r.module_ref, faulting_pc, *exception_id); | |
| 1554 | + assert(target_frame != nullptr); | |
| 1555 | + | |
| 1556 | + // Now that we know the exception has a handler, take a reference. | |
| 1557 | + e->add_ref(); | |
| 1558 | + | |
| 1559 | + if (target_frame != r.stack.peek_frame()) | |
| 1560 | + { | |
| 1561 | + while (target_frame != r.stack.pop_frame()) | |
| 1562 | + { | |
| 1563 | + // Remove frames from the stack until the target frame is found. | |
| 1564 | + } | |
| 1565 | + } | |
| 1566 | + | |
| 1567 | + // Re-initialize the current frame | |
| 1568 | + if (handler->type_desc != nullptr) | |
| 1569 | + { | |
| 1570 | + destroy_memory(*handler->type_desc, target_frame->base()); | |
| 1571 | + init_memory(*handler->type_desc, target_frame->base()); | |
| 1572 | + } | |
| 1573 | + | |
| 1574 | + // Set the exception at the appropriate frame offset | |
| 1575 | + auto frame_pointer = reinterpret_cast<uint8_t *>(target_frame->base()); | |
| 1576 | + auto exception_destination = reinterpret_cast<pointer_t>(frame_pointer + handler->exception_offset); | |
| 1577 | + | |
| 1578 | + // Destroy any data at this location. | |
| 1579 | + auto current_value = at_val<vm_alloc_t>(exception_destination); | |
| 1580 | + dec_ref_count_and_free(current_value); | |
| 1581 | + | |
| 1582 | + // Set the exception destination. | |
| 1583 | + pt_ref(exception_destination) = e->allocation(); | |
| 1584 | + | |
| 1585 | + r.pc = handler_pc; | |
| 1586 | + } | |
| 1587 | + | |
| 1588 | + // | |
| 1589 | + // Module operations | |
| 1590 | + // | |
| 1591 | + | |
| 1592 | + EXEC_DECL(load) | |
| 1593 | + { | |
| 1594 | + // Validate module loading module. | |
| 1595 | + auto importing_module = r.module_ref->module; | |
| 1596 | + if (!util::has_flag(importing_module->header.runtime_flag, runtime_flags_t::has_import)) | |
| 1597 | + throw vm_user_exception{ "Invalid importing module" }; | |
| 1598 | + | |
| 1599 | + auto str = at_val<vm_string_t>(r.src); | |
| 1600 | + assert(str != nullptr); | |
| 1601 | + | |
| 1602 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 1603 | + pt_ref(r.dest) = nullptr; | |
| 1604 | + | |
| 1605 | + auto imported_module = std::shared_ptr<const vm_module_t>{}; | |
| 1606 | + | |
| 1607 | + // Handle self loading module optimization. | |
| 1608 | + if (::strcmp(str->str(), "$self") == 0) | |
| 1609 | + { | |
| 1610 | + imported_module = r.module_ref->module; | |
| 1611 | + } | |
| 1612 | + else | |
| 1613 | + { | |
| 1614 | + try | |
| 1615 | + { | |
| 1616 | + imported_module = vm.load_module(str->str()); | |
| 1617 | + } | |
| 1618 | + catch (const vm_module_exception &e) | |
| 1619 | + { | |
| 1620 | + debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "exception: load: >>%s<<\n", e.what()); | |
| 1621 | + assert(false && "Failure reading module"); | |
| 1622 | + return; | |
| 1623 | + } | |
| 1624 | + } | |
| 1625 | + | |
| 1626 | + auto module_import_index = vt_ref<uint32_t>(r.mid); | |
| 1627 | + const auto &imports = importing_module->import_section; | |
| 1628 | + | |
| 1629 | + assert(module_import_index < imports.size()); | |
| 1630 | + const auto &function_imports = imports[module_import_index]; | |
| 1631 | + | |
| 1632 | + auto entry_module_ref = new vm_module_ref_t{ imported_module, function_imports }; | |
| 1633 | + pt_ref(r.dest) = entry_module_ref->allocation(); | |
| 1634 | + } | |
| 1635 | +} | |
| 1636 | + | |
| 1637 | +const vm_exec_t disvm::runtime::vm_exec_table[static_cast<std::size_t>(opcode_t::last_opcode) + 1] = | |
| 1638 | +{ | |
| 1639 | + invalid, | |
| 1640 | + alt, | |
| 1641 | + nbalt, | |
| 1642 | + goto_, | |
| 1643 | + call, | |
| 1644 | + frame, | |
| 1645 | + spawn, | |
| 1646 | + runt, | |
| 1647 | + load, | |
| 1648 | + mcall, | |
| 1649 | + mspawn, | |
| 1650 | + mframe, | |
| 1651 | + ret, | |
| 1652 | + jmp, | |
| 1653 | + casew, | |
| 1654 | + exit, | |
| 1655 | + new_, | |
| 1656 | + newa, | |
| 1657 | + newcb, | |
| 1658 | + newcw, | |
| 1659 | + newcf, | |
| 1660 | + newcp, | |
| 1661 | + newcm, | |
| 1662 | + newcmp, | |
| 1663 | + send, | |
| 1664 | + recv, | |
| 1665 | + consb, | |
| 1666 | + consw, | |
| 1667 | + consp, | |
| 1668 | + consf, | |
| 1669 | + notimpl, // consm, | |
| 1670 | + consmp, | |
| 1671 | + headb, | |
| 1672 | + headw, | |
| 1673 | + headp, | |
| 1674 | + headf, | |
| 1675 | + notimpl, // headm, | |
| 1676 | + headmp, | |
| 1677 | + tail, | |
| 1678 | + lea, | |
| 1679 | + indx, | |
| 1680 | + movp, | |
| 1681 | + movm, | |
| 1682 | + movmp, | |
| 1683 | + movb, | |
| 1684 | + movw, | |
| 1685 | + movf, | |
| 1686 | + cvtbw, | |
| 1687 | + cvtwb, | |
| 1688 | + cvtfw, | |
| 1689 | + cvtwf, | |
| 1690 | + cvtca, | |
| 1691 | + cvtac, | |
| 1692 | + cvtwc, | |
| 1693 | + cvtcw, | |
| 1694 | + cvtfc, | |
| 1695 | + cvtcf, | |
| 1696 | + addb, | |
| 1697 | + addw, | |
| 1698 | + addf, | |
| 1699 | + subb, | |
| 1700 | + subw, | |
| 1701 | + subf, | |
| 1702 | + mulb, | |
| 1703 | + mulw, | |
| 1704 | + mulf, | |
| 1705 | + divb, | |
| 1706 | + divw, | |
| 1707 | + divf, | |
| 1708 | + modw, | |
| 1709 | + modb, | |
| 1710 | + andb, | |
| 1711 | + andw, | |
| 1712 | + orb, | |
| 1713 | + orw, | |
| 1714 | + xorb, | |
| 1715 | + xorw, | |
| 1716 | + shlb, | |
| 1717 | + shlw, | |
| 1718 | + shrb, | |
| 1719 | + shrw, | |
| 1720 | + insc, | |
| 1721 | + indc, | |
| 1722 | + addc, | |
| 1723 | + lenc, | |
| 1724 | + lena, | |
| 1725 | + lenl, | |
| 1726 | + beqb, | |
| 1727 | + bneb, | |
| 1728 | + bltb, | |
| 1729 | + bleb, | |
| 1730 | + bgtb, | |
| 1731 | + bgeb, | |
| 1732 | + beqw, | |
| 1733 | + bnew, | |
| 1734 | + bltw, | |
| 1735 | + blew, | |
| 1736 | + bgtw, | |
| 1737 | + bgew, | |
| 1738 | + beqf, | |
| 1739 | + bnef, | |
| 1740 | + bltf, | |
| 1741 | + blef, | |
| 1742 | + bgtf, | |
| 1743 | + bgef, | |
| 1744 | + beqc, | |
| 1745 | + bnec, | |
| 1746 | + bltc, | |
| 1747 | + blec, | |
| 1748 | + bgtc, | |
| 1749 | + bgec, | |
| 1750 | + slicea, | |
| 1751 | + slicela, | |
| 1752 | + slicec, | |
| 1753 | + indw, | |
| 1754 | + indf, | |
| 1755 | + indb, | |
| 1756 | + negf, | |
| 1757 | + movl, | |
| 1758 | + addl, | |
| 1759 | + subl, | |
| 1760 | + divl, | |
| 1761 | + modl, | |
| 1762 | + mull, | |
| 1763 | + andl, | |
| 1764 | + orl, | |
| 1765 | + xorl, | |
| 1766 | + shll, | |
| 1767 | + shrl, | |
| 1768 | + bnel, | |
| 1769 | + bltl, | |
| 1770 | + blel, | |
| 1771 | + bgtl, | |
| 1772 | + bgel, | |
| 1773 | + beql, | |
| 1774 | + cvtlf, | |
| 1775 | + cvtfl, | |
| 1776 | + cvtlw, | |
| 1777 | + cvtwl, | |
| 1778 | + cvtlc, | |
| 1779 | + cvtcl, | |
| 1780 | + headl, | |
| 1781 | + consl, | |
| 1782 | + newcl, | |
| 1783 | + casec, | |
| 1784 | + indl, | |
| 1785 | + notimpl, // movpc, | |
| 1786 | + tcmp, | |
| 1787 | + mnewz, | |
| 1788 | + cvtrf, | |
| 1789 | + cvtfr, | |
| 1790 | + cvtws, | |
| 1791 | + cvtsw, | |
| 1792 | + lsrw, | |
| 1793 | + lsrl, | |
| 1794 | + eclr, | |
| 1795 | + newz, | |
| 1796 | + newaz, | |
| 1797 | + raise, | |
| 1798 | + casel, | |
| 1799 | + mulx, | |
| 1800 | + divx, | |
| 1801 | + cvtxx, | |
| 1802 | + notimpl, // mulx0, | |
| 1803 | + notimpl, // divx0, | |
| 1804 | + notimpl, // cvtxx0, | |
| 1805 | + notimpl, // mulx1, | |
| 1806 | + notimpl, // divx1, | |
| 1807 | + notimpl, // cvtxx1, | |
| 1808 | + cvtfx, | |
| 1809 | + cvtxf, | |
| 1810 | + expw, | |
| 1811 | + expl, | |
| 1812 | + expf, | |
| 1813 | + notimpl, // self, | |
| 1814 | +}; | |
| @@ -0,0 +1,21 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: execution_table.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_VM_EXECUTION_TABLE_H_ | |
| 8 | +#define _DISVM_SRC_VM_EXECUTION_TABLE_H_ | |
| 9 | + | |
| 10 | +#include "runtime.h" | |
| 11 | + | |
| 12 | +namespace disvm | |
| 13 | +{ | |
| 14 | + namespace runtime | |
| 15 | + { | |
| 16 | + // VM instruction execution table | |
| 17 | + extern const vm_exec_t vm_exec_table[]; | |
| 18 | + } | |
| 19 | +} | |
| 20 | + | |
| 21 | +#endif // _DISVM_SRC_VM_EXECUTION_TABLE_H_ | |
| @@ -0,0 +1,207 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: garbage_collector.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cinttypes> | |
| 8 | +#include <array> | |
| 9 | +#include <queue> | |
| 10 | +#include <debug.h> | |
| 11 | +#include <vm_memory.h> | |
| 12 | +#include <exceptions.h> | |
| 13 | +#include "garbage_collector.h" | |
| 14 | + | |
| 15 | +using namespace disvm; | |
| 16 | +using namespace disvm::runtime; | |
| 17 | + | |
| 18 | +// Empty destructors for vm garbage collector 'interfaces' | |
| 19 | +vm_garbage_collector_t::~vm_garbage_collector_t() | |
| 20 | +{ | |
| 21 | +} | |
| 22 | + | |
| 23 | +default_garbage_collector_t::default_garbage_collector_t(vm_t &vm) | |
| 24 | + : _collection_epoch{ 0 } | |
| 25 | + , _vm{ vm } | |
| 26 | +{ | |
| 27 | +} | |
| 28 | + | |
| 29 | +default_garbage_collector_t::~default_garbage_collector_t() | |
| 30 | +{ | |
| 31 | + std::lock_guard<std::mutex> lock{ _tracking_allocs_lock }; | |
| 32 | + for (auto a : _tracking_allocs) | |
| 33 | + dec_ref_count_and_free(a); | |
| 34 | +} | |
| 35 | + | |
| 36 | +namespace | |
| 37 | +{ | |
| 38 | + enum class gc_colour_t | |
| 39 | + { | |
| 40 | + white, | |
| 41 | + grey, | |
| 42 | + black, | |
| 43 | + }; | |
| 44 | + | |
| 45 | + const auto current_colours = std::array<gc_colour_t, 3>{ gc_colour_t::white, gc_colour_t::grey, gc_colour_t::black }; | |
| 46 | + const auto sweeper_colours = std::array<gc_colour_t, 3>{ gc_colour_t::grey, gc_colour_t::black, gc_colour_t::white }; | |
| 47 | + | |
| 48 | + gc_colour_t get_current_colour(const std::size_t epoch) | |
| 49 | + { | |
| 50 | + return current_colours[epoch % current_colours.size()]; | |
| 51 | + } | |
| 52 | + | |
| 53 | + gc_colour_t get_sweeper_colour(const std::size_t epoch) | |
| 54 | + { | |
| 55 | + return sweeper_colours[epoch % sweeper_colours.size()]; | |
| 56 | + } | |
| 57 | + | |
| 58 | + gc_colour_t get_gc_colour(const vm_alloc_t *a) | |
| 59 | + { | |
| 60 | + return *reinterpret_cast<const gc_colour_t *>(&a->gc_reserved); | |
| 61 | + } | |
| 62 | + | |
| 63 | + void set_gc_colour(vm_alloc_t *a, const gc_colour_t c) | |
| 64 | + { | |
| 65 | + a->gc_reserved = reinterpret_cast<pointer_t>(c); | |
| 66 | + } | |
| 67 | +} | |
| 68 | + | |
| 69 | +void default_garbage_collector_t::track_allocation(vm_alloc_t *alloc) | |
| 70 | +{ | |
| 71 | + assert(alloc != nullptr); | |
| 72 | + | |
| 73 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>()) | |
| 74 | + debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: track: %#" PRIxPTR "\n", alloc); | |
| 75 | + | |
| 76 | + alloc->add_ref(); | |
| 77 | + set_gc_colour(alloc, get_current_colour(_collection_epoch)); | |
| 78 | + | |
| 79 | + std::lock_guard<std::mutex> lock{ _tracking_allocs_lock }; | |
| 80 | + _tracking_allocs.push_back(alloc); | |
| 81 | +} | |
| 82 | + | |
| 83 | +void default_garbage_collector_t::enum_tracked_allocations(vm_alloc_callback_t callback) const | |
| 84 | +{ | |
| 85 | + if (callback == nullptr) | |
| 86 | + throw vm_system_exception{ "Callback should not be null" }; | |
| 87 | + | |
| 88 | + std::lock_guard<std::mutex> lock{ _tracking_allocs_lock }; | |
| 89 | + for (auto a : _tracking_allocs) | |
| 90 | + callback(a); | |
| 91 | +} | |
| 92 | + | |
| 93 | +namespace | |
| 94 | +{ | |
| 95 | + void mark(const std::vector<std::shared_ptr<const vm_thread_t>> &threads, const gc_colour_t curr_colour) | |
| 96 | + { | |
| 97 | + std::queue<vm_alloc_t *> allocs; | |
| 98 | + | |
| 99 | + auto add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs](pointer_t p) -> void | |
| 100 | + { | |
| 101 | + auto a = vm_alloc_t::from_allocation(p); | |
| 102 | + allocs.push(a); | |
| 103 | + } }; | |
| 104 | + | |
| 105 | + // Get roots from threads | |
| 106 | + for (const auto &t : threads) | |
| 107 | + { | |
| 108 | + const auto &r = t->get_registers(); | |
| 109 | + | |
| 110 | + // Current MP | |
| 111 | + allocs.push(r.mp_base); | |
| 112 | + | |
| 113 | + // Traverse the stack for roots | |
| 114 | + for (auto frame = r.stack.peek_frame(); frame != nullptr; frame = frame->prev_frame()) | |
| 115 | + { | |
| 116 | + // Previous MP | |
| 117 | + if (frame->prev_module_ref() != nullptr) | |
| 118 | + { | |
| 119 | + auto prev_mp = frame->prev_module_ref()->mp_base; | |
| 120 | + allocs.push(prev_mp); | |
| 121 | + } | |
| 122 | + | |
| 123 | + if (frame->frame_type->map_in_bytes == 0) | |
| 124 | + continue; | |
| 125 | + | |
| 126 | + enum_pointer_fields(*frame->frame_type, frame->base(), add_pointer_to_queue); | |
| 127 | + } | |
| 128 | + } | |
| 129 | + | |
| 130 | + const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>(); | |
| 131 | + if (log_enabled) | |
| 132 | + { | |
| 133 | + debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: roots found: %" PRIuPTR "\n", allocs.size()); | |
| 134 | + | |
| 135 | + // If logging is enabled update the callback with logging | |
| 136 | + add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs](pointer_t p) -> void | |
| 137 | + { | |
| 138 | + auto a = vm_alloc_t::from_allocation(p); | |
| 139 | + debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "\tref: %#" PRIxPTR "\n", a); | |
| 140 | + allocs.push(a); | |
| 141 | + } }; | |
| 142 | + } | |
| 143 | + | |
| 144 | + // Traverse the graph | |
| 145 | + while (!allocs.empty()) | |
| 146 | + { | |
| 147 | + auto curr = allocs.front(); | |
| 148 | + allocs.pop(); | |
| 149 | + | |
| 150 | + if (log_enabled) | |
| 151 | + debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: mark: alloc: %#" PRIxPTR "\n", curr); | |
| 152 | + | |
| 153 | + if (curr->alloc_type->map_in_bytes > 0) | |
| 154 | + enum_pointer_fields(*curr->alloc_type, curr->allocation(), add_pointer_to_queue); | |
| 155 | + | |
| 156 | + set_gc_colour(curr, curr_colour); | |
| 157 | + } | |
| 158 | + } | |
| 159 | + | |
| 160 | + void sweep(std::deque<vm_alloc_t *> &allocs, const gc_colour_t sweeper_colour) | |
| 161 | + { | |
| 162 | + auto allocs_local = std::move(allocs); | |
| 163 | + | |
| 164 | + auto freed = uint32_t{ 0 }; | |
| 165 | + for (auto a : allocs_local) | |
| 166 | + { | |
| 167 | + const auto colour = get_gc_colour(a); | |
| 168 | + if (colour == sweeper_colour) | |
| 169 | + { | |
| 170 | + dec_ref_count_and_free(a); | |
| 171 | + freed++; | |
| 172 | + continue; | |
| 173 | + } | |
| 174 | + | |
| 175 | + allocs.push_back(a); | |
| 176 | + } | |
| 177 | + | |
| 178 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>()) | |
| 179 | + debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: sweep: %u\n", freed); | |
| 180 | + } | |
| 181 | +} | |
| 182 | + | |
| 183 | +bool default_garbage_collector_t::collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) | |
| 184 | +{ | |
| 185 | + // No need to lock the actual memory allocator since the contract | |
| 186 | + // for this function call indicates all VM threads should be blocked. | |
| 187 | + std::lock_guard<std::mutex> lock{ _tracking_allocs_lock }; | |
| 188 | + | |
| 189 | + if (threads.empty() || _tracking_allocs.empty()) | |
| 190 | + return false; | |
| 191 | + | |
| 192 | + const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>(); | |
| 193 | + if (log_enabled) | |
| 194 | + debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: begin: collect\n"); | |
| 195 | + | |
| 196 | + const auto curr_colour = get_current_colour(_collection_epoch); | |
| 197 | + mark(threads, curr_colour); | |
| 198 | + | |
| 199 | + const auto sweeper_colour = get_sweeper_colour(_collection_epoch); | |
| 200 | + sweep(_tracking_allocs, sweeper_colour); | |
| 201 | + | |
| 202 | + if (log_enabled) | |
| 203 | + debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: end: collect\n"); | |
| 204 | + | |
| 205 | + ++_collection_epoch; | |
| 206 | + return true; | |
| 207 | +} | |
| @@ -0,0 +1,45 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: garbage_collector.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_VM_GARBAGE_COLLECTOR_H_ | |
| 8 | +#define _DISVM_SRC_VM_GARBAGE_COLLECTOR_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include <mutex> | |
| 12 | +#include <deque> | |
| 13 | +#include <vector> | |
| 14 | +#include <disvm.h> | |
| 15 | + | |
| 16 | +namespace disvm | |
| 17 | +{ | |
| 18 | + namespace runtime | |
| 19 | + { | |
| 20 | + // Default garbage collector | |
| 21 | + class default_garbage_collector_t final : public vm_garbage_collector_t | |
| 22 | + { | |
| 23 | + public: | |
| 24 | + default_garbage_collector_t(vm_t &vm); | |
| 25 | + | |
| 26 | + ~default_garbage_collector_t(); | |
| 27 | + | |
| 28 | + public: // vm_garbage_collector_t | |
| 29 | + void track_allocation(vm_alloc_t *alloc) override final; | |
| 30 | + | |
| 31 | + void enum_tracked_allocations(vm_alloc_callback_t callback) const override final; | |
| 32 | + | |
| 33 | + bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) override final; | |
| 34 | + | |
| 35 | + private: | |
| 36 | + vm_t &_vm; | |
| 37 | + | |
| 38 | + std::size_t _collection_epoch; | |
| 39 | + mutable std::mutex _tracking_allocs_lock; | |
| 40 | + std::deque<vm_alloc_t *> _tracking_allocs; | |
| 41 | + }; | |
| 42 | + } | |
| 43 | +} | |
| 44 | + | |
| 45 | +#endif // _DISVM_SRC_VM_GARBAGE_COLLECTOR_H_ | |
| @@ -0,0 +1,105 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: list.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <runtime.h> | |
| 8 | +#include <vm_memory.h> | |
| 9 | +#include <debug.h> | |
| 10 | + | |
| 11 | +using namespace disvm; | |
| 12 | +using namespace disvm::runtime; | |
| 13 | + | |
| 14 | +std::shared_ptr<const type_descriptor_t> vm_list_t::type_desc() | |
| 15 | +{ | |
| 16 | + return intrinsic_type_desc::type<vm_list_t>(); | |
| 17 | +} | |
| 18 | + | |
| 19 | +vm_list_t::vm_list_t(std::shared_ptr<const type_descriptor_t> td, vm_list_t *tail) | |
| 20 | + : vm_alloc_t(vm_list_t::type_desc()) | |
| 21 | + , _element_type{ td } | |
| 22 | + , _is_alloc{ sizeof(_mem) < td->size_in_bytes } | |
| 23 | + , _mem{} | |
| 24 | + , _tail{ tail } | |
| 25 | +{ | |
| 26 | + assert(_element_type != nullptr); | |
| 27 | + if (_tail != nullptr) | |
| 28 | + { | |
| 29 | + assert(tail->get_element_type() == _element_type); | |
| 30 | + _tail->add_ref(); | |
| 31 | + } | |
| 32 | + | |
| 33 | + // If the default list element storage is not enough, allocate more memory. | |
| 34 | + if (_is_alloc) | |
| 35 | + { | |
| 36 | + _mem.alloc = static_cast<pointer_t>(alloc_memory(_element_type->size_in_bytes)); | |
| 37 | + init_memory(*_element_type, _mem.alloc); | |
| 38 | + } | |
| 39 | + else | |
| 40 | + { | |
| 41 | + init_memory(*_element_type, &_mem.local); | |
| 42 | + } | |
| 43 | + | |
| 44 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm list: new\n"); | |
| 45 | +} | |
| 46 | + | |
| 47 | +vm_list_t::~vm_list_t() | |
| 48 | +{ | |
| 49 | + // Remove tail | |
| 50 | + dec_ref_count_and_free(_tail); | |
| 51 | + _tail = nullptr; | |
| 52 | + | |
| 53 | + // Destroy and free the list element, if default element size was surpassed. | |
| 54 | + if (_is_alloc) | |
| 55 | + { | |
| 56 | + destroy_memory(*_element_type, _mem.alloc); | |
| 57 | + free_memory(_mem.alloc); | |
| 58 | + } | |
| 59 | + else | |
| 60 | + { | |
| 61 | + destroy_memory(*_element_type, &_mem.local); | |
| 62 | + } | |
| 63 | + | |
| 64 | + debug::assign_debug_pointer(&_mem.alloc); | |
| 65 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm list\n"); | |
| 66 | +} | |
| 67 | + | |
| 68 | +std::shared_ptr<const type_descriptor_t> vm_list_t::get_element_type() const | |
| 69 | +{ | |
| 70 | + return _element_type; | |
| 71 | +} | |
| 72 | + | |
| 73 | +word_t vm_list_t::get_length() const | |
| 74 | +{ | |
| 75 | + auto len = word_t{ 1 }; | |
| 76 | + auto list = _tail; | |
| 77 | + while (list != nullptr) | |
| 78 | + { | |
| 79 | + len++; | |
| 80 | + list = list->get_tail(); | |
| 81 | + } | |
| 82 | + | |
| 83 | + return len; | |
| 84 | +} | |
| 85 | + | |
| 86 | +vm_list_t *vm_list_t::get_tail() const | |
| 87 | +{ | |
| 88 | + return _tail; | |
| 89 | +} | |
| 90 | + | |
| 91 | +vm_list_t *vm_list_t::drop_tail() | |
| 92 | +{ | |
| 93 | + auto tail = _tail; | |
| 94 | + _tail = nullptr; | |
| 95 | + tail->release(); | |
| 96 | + return tail; | |
| 97 | +} | |
| 98 | + | |
| 99 | +pointer_t vm_list_t::value() const | |
| 100 | +{ | |
| 101 | + if (_is_alloc) | |
| 102 | + return _mem.alloc; | |
| 103 | + else | |
| 104 | + return reinterpret_cast<pointer_t>(&_mem.local); | |
| 105 | +} | |
| @@ -0,0 +1,800 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: loader.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cassert> | |
| 8 | +#include <limits> | |
| 9 | +#include <memory> | |
| 10 | +#include <iostream> | |
| 11 | +#include <fstream> | |
| 12 | +#include <array> | |
| 13 | +#include <tuple> | |
| 14 | +#include <disvm.h> | |
| 15 | +#include <exceptions.h> | |
| 16 | +#include <vm_memory.h> | |
| 17 | +#include <utils.h> | |
| 18 | +#include <module_reader.h> | |
| 19 | +#include "buffered_reader.h" | |
| 20 | + | |
| 21 | +using namespace disvm; | |
| 22 | +using namespace disvm::runtime; | |
| 23 | +using namespace disvm::format; | |
| 24 | + | |
| 25 | +namespace | |
| 26 | +{ | |
| 27 | + // constants used in module processing | |
| 28 | + struct module_constants | |
| 29 | + { | |
| 30 | + static const auto max_type_pointer_count = operand_t{ 128 * 1024 }; | |
| 31 | + static const auto no_error_handle_type_desc = operand_t{ -1 }; | |
| 32 | + static const auto no_entry_pc = vm_pc_t{ -1 }; | |
| 33 | + static const auto vm_module_type_desc_number = std::size_t{ 0 }; | |
| 34 | + static const auto max_module_name_bytes = std::size_t{ 128 }; // Including null | |
| 35 | + static const auto array_address_stack_size = std::size_t{ 4 }; | |
| 36 | + }; | |
| 37 | + | |
| 38 | + // enumeration for types in the data section | |
| 39 | + enum class datum_type_t | |
| 40 | + { | |
| 41 | + value_bit8 = 1, | |
| 42 | + value_bit32 = 2, | |
| 43 | + utf_string = 3, | |
| 44 | + value_real64 = 4, | |
| 45 | + array = 5, | |
| 46 | + set_array = 6, | |
| 47 | + restore_load_address = 7, | |
| 48 | + value_bit64 = 8 | |
| 49 | + }; | |
| 50 | + | |
| 51 | + // Read the next operand as defined in the Dis VM specification. | |
| 52 | + std::tuple<bool, operand_t> read_next_operand(util::buffered_reader_t &reader) | |
| 53 | + { | |
| 54 | + auto result = operand_t{ 0 }; | |
| 55 | + | |
| 56 | + // Converting stack allocation to buffer since max size of operand is a machine word | |
| 57 | + byte_t buffer[sizeof(result)]; | |
| 58 | + | |
| 59 | + if (!reader.get_next_bytes(1, buffer)) | |
| 60 | + return std::make_tuple(false, 0); | |
| 61 | + | |
| 62 | + switch (buffer[0] & 0xc0) | |
| 63 | + { | |
| 64 | + case 0x00: | |
| 65 | + // 1 byte operand | |
| 66 | + result = buffer[0]; | |
| 67 | + break; | |
| 68 | + | |
| 69 | + case 0x40: | |
| 70 | + // 1 byte operand - Preserve sign | |
| 71 | + result = buffer[0] | ~0x7f; | |
| 72 | + break; | |
| 73 | + | |
| 74 | + case 0x80: | |
| 75 | + // 2 byte operand | |
| 76 | + if (!reader.get_next_bytes(1, buffer + 1)) | |
| 77 | + return std::make_tuple(false, 0); | |
| 78 | + | |
| 79 | + // Preserve sign | |
| 80 | + if ((buffer[0] & 0x20) != 0) | |
| 81 | + buffer[0] |= ~0x3f; | |
| 82 | + else | |
| 83 | + buffer[0] &= 0x3f; | |
| 84 | + | |
| 85 | + result = (buffer[0] << 8); | |
| 86 | + result |= buffer[1]; | |
| 87 | + break; | |
| 88 | + | |
| 89 | + case 0xc0: | |
| 90 | + // 4 byte operand | |
| 91 | + if (!reader.get_next_bytes(3, buffer + 1)) | |
| 92 | + return std::make_tuple(false, 0); | |
| 93 | + | |
| 94 | + // Preserve sign | |
| 95 | + if ((buffer[0] & 0x20) != 0) | |
| 96 | + buffer[0] |= ~0x3f; | |
| 97 | + else | |
| 98 | + buffer[0] &= 0x3f; | |
| 99 | + | |
| 100 | + result = (buffer[0] << 24); | |
| 101 | + result |= (buffer[1] << 16); | |
| 102 | + result |= (buffer[2] << 8); | |
| 103 | + result |= buffer[3]; | |
| 104 | + break; | |
| 105 | + | |
| 106 | + default: | |
| 107 | + assert(false && "Should not be possible"); | |
| 108 | + } | |
| 109 | + | |
| 110 | + return std::make_tuple(true, result); | |
| 111 | + } | |
| 112 | + | |
| 113 | + // Read the next word as defined in the Dis VM specification. | |
| 114 | + std::tuple<bool, word_t> read_next_word(util::buffered_reader_t &reader) | |
| 115 | + { | |
| 116 | + auto result = word_t{}; | |
| 117 | + | |
| 118 | + // Converting stack allocation to buffer since max size of operand is a machine word | |
| 119 | + byte_t buffer[sizeof(result)]; | |
| 120 | + | |
| 121 | + if (!reader.get_next_bytes(sizeof(result), buffer)) | |
| 122 | + return std::make_tuple(false, 0); | |
| 123 | + | |
| 124 | + result = (buffer[0] << 24); | |
| 125 | + result |= (buffer[1] << 16); | |
| 126 | + result |= (buffer[2] << 8); | |
| 127 | + result |= buffer[3]; | |
| 128 | + | |
| 129 | + return std::make_tuple(true, result); | |
| 130 | + } | |
| 131 | + | |
| 132 | + real_t convert_to_real(const operand_t f, const operand_t s) | |
| 133 | + { | |
| 134 | + auto u64 = static_cast<uint64_t>(f); | |
| 135 | + u64 = (u64 << 32) | (static_cast<uint64_t>(s) & 0xffffffff); | |
| 136 | + | |
| 137 | + auto r = real_t{}; | |
| 138 | + std::memcpy(&r, &u64, sizeof(u64)); | |
| 139 | + | |
| 140 | + static_assert(sizeof(u64) == sizeof(r), "Real should be 64-bits"); | |
| 141 | + return r; | |
| 142 | + } | |
| 143 | + | |
| 144 | + bool read_next_string(util::buffered_reader_t &reader, std::vector<uint8_t> &char_buffer) | |
| 145 | + { | |
| 146 | + assert(char_buffer.size() == 0); | |
| 147 | + char_buffer.reserve(1024); | |
| 148 | + | |
| 149 | + auto success = bool{}; | |
| 150 | + auto value = uint8_t{}; | |
| 151 | + std::tie(success, value) = reader.get_next_byte(); | |
| 152 | + while (success && value != 0) | |
| 153 | + { | |
| 154 | + char_buffer.push_back(value); | |
| 155 | + std::tie(success, value) = reader.get_next_byte(); | |
| 156 | + } | |
| 157 | + | |
| 158 | + return success; | |
| 159 | + } | |
| 160 | + | |
| 161 | + byte_t *read_8byte_segments(util::buffered_reader_t &reader, datum_type_t type, uint32_t segment_count, byte_t *dest) | |
| 162 | + { | |
| 163 | + assert(type == datum_type_t::value_real64 || type == datum_type_t::value_bit64); | |
| 164 | + | |
| 165 | + auto success = bool{}; | |
| 166 | + auto first = word_t{}; | |
| 167 | + auto second = word_t{}; | |
| 168 | + for (auto idx = uint32_t{ 0 }; idx < segment_count; ++idx) | |
| 169 | + { | |
| 170 | + std::tie(success, first) = read_next_word(reader); | |
| 171 | + if (!success) throw module_reader_exception{ "Failed to read first word" }; | |
| 172 | + | |
| 173 | + std::tie(success, second) = read_next_word(reader); | |
| 174 | + if (!success) throw module_reader_exception{ "Failed to read second word" }; | |
| 175 | + | |
| 176 | + if (type == datum_type_t::value_real64) | |
| 177 | + { | |
| 178 | + auto r = convert_to_real(first, second); | |
| 179 | + *reinterpret_cast<real_t *>(dest) = r; | |
| 180 | + dest += sizeof(r); | |
| 181 | + } | |
| 182 | + else | |
| 183 | + { | |
| 184 | + auto b = static_cast<big_t>(first); | |
| 185 | + b = (b << 32) | (static_cast<big_t>(second) & 0xffffffff); | |
| 186 | + *reinterpret_cast<big_t *>(dest) = b; | |
| 187 | + dest += sizeof(b); | |
| 188 | + } | |
| 189 | + } | |
| 190 | + | |
| 191 | + return dest; | |
| 192 | + } | |
| 193 | + | |
| 194 | + std::tuple<address_mode_middle_t, address_mode_t, address_mode_t> convert_to_address_mode(uint8_t instr_addr_mode) | |
| 195 | + { | |
| 196 | + // Taken from Dis VM specification | |
| 197 | + // bit 7 6 5 4 3 2 1 0 | |
| 198 | + // m1 m0 s2 s1 s0 d2 d1 d0 | |
| 199 | + | |
| 200 | + static const auto mid_mask = uint8_t{ 0xc0 }; | |
| 201 | + static const auto src_mask = uint8_t{ 0x38 }; | |
| 202 | + static const auto dest_mask = uint8_t{ 0x07 }; | |
| 203 | + assert((mid_mask | src_mask | dest_mask) == std::numeric_limits<uint8_t>::max()); | |
| 204 | + | |
| 205 | + const auto mid = static_cast<address_mode_middle_t>((instr_addr_mode & mid_mask) >> 6); | |
| 206 | + const auto src = static_cast<address_mode_t>((instr_addr_mode & src_mask) >> 3); | |
| 207 | + const auto dest = static_cast<address_mode_t>(instr_addr_mode & dest_mask); | |
| 208 | + | |
| 209 | + return std::make_tuple(mid, src, dest); | |
| 210 | + } | |
| 211 | + | |
| 212 | + // See header format definition in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html) | |
| 213 | + void read_header(util::buffered_reader_t &reader, vm_module_t &modobj) | |
| 214 | + { | |
| 215 | + auto success = bool{}; | |
| 216 | + auto &header = modobj.header; | |
| 217 | + | |
| 218 | + std::tie(success, header.magic_number) = read_next_operand(reader); | |
| 219 | + if (!success) | |
| 220 | + throw module_reader_exception{ "Failed to read magic number" }; | |
| 221 | + | |
| 222 | + assert(header.magic_number == magic_number_constants::xmagic || header.magic_number == magic_number_constants::smagic); | |
| 223 | + | |
| 224 | + // If the object file is signed, read in the signature. | |
| 225 | + if (header.magic_number == magic_number_constants::smagic) | |
| 226 | + { | |
| 227 | + std::tie(success, header.Signature.length) = read_next_operand(reader); | |
| 228 | + if (!success) throw module_reader_exception{ "Failed to read signature length" }; | |
| 229 | + | |
| 230 | + header.Signature.signature.reset(new byte_t[header.Signature.length]); | |
| 231 | + const auto bytesRead = reader.get_next_bytes(header.Signature.length, header.Signature.signature.get()); | |
| 232 | + if (bytesRead != header.Signature.length) throw module_reader_exception{ "Failed to read full signature" }; | |
| 233 | + } | |
| 234 | + else | |
| 235 | + { | |
| 236 | + header.Signature.length = word_t{ 0 }; | |
| 237 | + } | |
| 238 | + | |
| 239 | + auto runtime_flags = operand_t{}; | |
| 240 | + std::tie(success, runtime_flags) = read_next_operand(reader); | |
| 241 | + if (!success) throw module_reader_exception{ "Failed to read runtime flag" }; | |
| 242 | + header.runtime_flag = static_cast<runtime_flags_t>(runtime_flags); | |
| 243 | + | |
| 244 | + std::tie(success, header.stack_extent) = read_next_operand(reader); | |
| 245 | + if (!success) throw module_reader_exception{ "Failed to read stack extent" }; | |
| 246 | + | |
| 247 | + std::tie(success, header.code_size) = read_next_operand(reader); | |
| 248 | + if (!success) throw module_reader_exception{ "Failed to read code size" }; | |
| 249 | + | |
| 250 | + std::tie(success, header.data_size) = read_next_operand(reader); | |
| 251 | + if (!success) throw module_reader_exception{ "Failed to read data size" }; | |
| 252 | + | |
| 253 | + std::tie(success, header.type_size) = read_next_operand(reader); | |
| 254 | + if (!success) throw module_reader_exception{ "Failed to read type size" }; | |
| 255 | + | |
| 256 | + std::tie(success, header.export_size) = read_next_operand(reader); | |
| 257 | + if (!success) throw module_reader_exception{ "Failed to read export size" }; | |
| 258 | + | |
| 259 | + // [SPEC] If a module does not have an entry program counter it is set to '-1'. | |
| 260 | + std::tie(success, header.entry_pc) = read_next_operand(reader); | |
| 261 | + if (!success) throw module_reader_exception{ "Failed to entry pc" }; | |
| 262 | + | |
| 263 | + // [SPEC] If a module does not have an entry program counter the entry type index is set to '-1'. | |
| 264 | + std::tie(success, header.entry_type) = read_next_operand(reader); | |
| 265 | + if (!success) throw module_reader_exception{ "Failed to read entry type" }; | |
| 266 | + } | |
| 267 | + | |
| 268 | + // See code section layout in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html) | |
| 269 | + void read_code_section(util::buffered_reader_t &reader, vm_module_t &modobj) | |
| 270 | + { | |
| 271 | + auto success = bool{}; | |
| 272 | + const auto instruction_count = modobj.header.code_size; | |
| 273 | + | |
| 274 | + // Allocate memory for the instructions | |
| 275 | + modobj.code_section.resize(instruction_count); | |
| 276 | + | |
| 277 | + for (auto c = word_t{ 0 }; c < instruction_count; ++c) | |
| 278 | + { | |
| 279 | + // Read in a single instruction | |
| 280 | + byte_t op_and_addrmode[2] = {}; | |
| 281 | + const auto bytesRead = reader.get_next_bytes(sizeof(op_and_addrmode), op_and_addrmode); | |
| 282 | + if (bytesRead != sizeof(op_and_addrmode)) throw module_reader_exception{ "Failed to read op code and address mode" }; | |
| 283 | + | |
| 284 | + auto vm_instr = vm_instruction_t{}; | |
| 285 | + auto &instr = vm_instr.op; | |
| 286 | + | |
| 287 | + instr.opcode = static_cast<opcode_t>(op_and_addrmode[0]); | |
| 288 | + assert(opcode_t::first_opcode <= instr.opcode && instr.opcode <= opcode_t::last_opcode); | |
| 289 | + | |
| 290 | + std::tie(instr.middle.mode, instr.source.mode, instr.destination.mode) = convert_to_address_mode(op_and_addrmode[1]); | |
| 291 | + | |
| 292 | + if (instr.middle.mode != address_mode_middle_t::none) | |
| 293 | + { | |
| 294 | + std::tie(success, instr.middle.register1) = read_next_operand(reader); | |
| 295 | + if (!success) throw module_reader_exception{ "Failed to read middle register" }; | |
| 296 | + } | |
| 297 | + | |
| 298 | + if (instr.source.mode != address_mode_t::none) | |
| 299 | + { | |
| 300 | + std::tie(success, instr.source.register1) = read_next_operand(reader); | |
| 301 | + if (!success) throw module_reader_exception{ "Failed to read source register 1" }; | |
| 302 | + | |
| 303 | + if (instr.source.mode == address_mode_t::offset_double_indirect_fp || | |
| 304 | + instr.source.mode == address_mode_t::offset_double_indirect_mp) | |
| 305 | + { | |
| 306 | + std::tie(success, instr.source.register2) = read_next_operand(reader); | |
| 307 | + if (!success) throw module_reader_exception{ "Failed to read source register 2" }; | |
| 308 | + | |
| 309 | + // Double indirect offsets need to be < 2^16-1. | |
| 310 | + assert(instr.source.register1 <= std::numeric_limits<uint16_t>::max()); | |
| 311 | + assert(instr.source.register2 <= std::numeric_limits<uint16_t>::max()); | |
| 312 | + } | |
| 313 | + } | |
| 314 | + | |
| 315 | + if (instr.destination.mode != address_mode_t::none) | |
| 316 | + { | |
| 317 | + std::tie(success, instr.destination.register1) = read_next_operand(reader); | |
| 318 | + if (!success) throw module_reader_exception{ "Failed to read destination register 1" }; | |
| 319 | + | |
| 320 | + if (instr.destination.mode == address_mode_t::offset_double_indirect_fp || | |
| 321 | + instr.destination.mode == address_mode_t::offset_double_indirect_mp) | |
| 322 | + { | |
| 323 | + std::tie(success, instr.destination.register2) = read_next_operand(reader); | |
| 324 | + if (!success) throw module_reader_exception{ "Failed to destination register 2" }; | |
| 325 | + | |
| 326 | + // Double indirect offsets need to be < 2^16-1. | |
| 327 | + assert(instr.destination.register1 <= std::numeric_limits<uint16_t>::max()); | |
| 328 | + assert(instr.destination.register2 <= std::numeric_limits<uint16_t>::max()); | |
| 329 | + } | |
| 330 | + } | |
| 331 | + | |
| 332 | + modobj.code_section[c] = std::move(vm_instr); | |
| 333 | + } | |
| 334 | + | |
| 335 | + if (modobj.header.entry_pc != module_constants::no_entry_pc && static_cast<std::size_t>(modobj.header.entry_pc) >= modobj.code_section.size()) | |
| 336 | + throw module_reader_exception{ "Invalid initial program counter value for code section" }; | |
| 337 | + } | |
| 338 | + | |
| 339 | + // See type section layout in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html) | |
| 340 | + void read_type_section(util::buffered_reader_t &reader, vm_module_t &modobj) | |
| 341 | + { | |
| 342 | + auto success = bool{}; | |
| 343 | + const auto type_count = modobj.header.type_size; | |
| 344 | + | |
| 345 | + // Allocate memory for the types | |
| 346 | + modobj.type_section.resize(type_count); | |
| 347 | + | |
| 348 | + for (auto c = word_t{ 0 }; c < type_count; ++c) | |
| 349 | + { | |
| 350 | + // Read in single type | |
| 351 | + operand_t desc_number; | |
| 352 | + std::tie(success, desc_number) = read_next_operand(reader); | |
| 353 | + if (!success) throw module_reader_exception{ "Failed to read type descriptor number" }; | |
| 354 | + | |
| 355 | + operand_t size; | |
| 356 | + std::tie(success, size) = read_next_operand(reader); | |
| 357 | + if (!success) throw module_reader_exception{ "Failed to read type size" }; | |
| 358 | + | |
| 359 | + operand_t map_in_bytes; | |
| 360 | + std::tie(success, map_in_bytes) = read_next_operand(reader); | |
| 361 | + if (!success) throw module_reader_exception{ "Failed to read type pointer count" }; | |
| 362 | + if (map_in_bytes > module_constants::max_type_pointer_count) throw module_reader_exception{ "Invalid Limbo type" }; | |
| 363 | + | |
| 364 | + auto pointer_map = std::vector<uint8_t>(map_in_bytes); | |
| 365 | + if (map_in_bytes != 0) | |
| 366 | + { | |
| 367 | + const auto bytesRead = reader.get_next_bytes(map_in_bytes, pointer_map.data()); | |
| 368 | + if (bytesRead != map_in_bytes) throw module_reader_exception{ "Failed to read type pointer map" }; | |
| 369 | + } | |
| 370 | + | |
| 371 | + modobj.type_section[desc_number] = std::move(type_descriptor_t::create(size, pointer_map)); | |
| 372 | + } | |
| 373 | + } | |
| 374 | + | |
| 375 | + // See data section layout in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html) | |
| 376 | + void read_data_section(util::buffered_reader_t &reader, vm_module_t &modobj) | |
| 377 | + { | |
| 378 | + if (modobj.header.data_size != 0) | |
| 379 | + { | |
| 380 | + auto vm_module_type = modobj.type_section[module_constants::vm_module_type_desc_number]; | |
| 381 | + if (vm_module_type->size_in_bytes != modobj.header.data_size) throw module_reader_exception{ "Invalid type desc for MP" }; | |
| 382 | + | |
| 383 | + // The Dis VM spec does not guarantee the MP segment (i.e. data segment) of the loaded | |
| 384 | + // module will be initialized to zero, but it is the C/C++ approach so defer to that. | |
| 385 | + modobj.original_mp.reset(vm_alloc_t::allocate(vm_module_type, vm_alloc_t::zero_memory)); | |
| 386 | + } | |
| 387 | + | |
| 388 | + auto array_stack = std::array<byte_t *, module_constants::array_address_stack_size>{}; | |
| 389 | + auto array_stack_pointer = std::size_t{ 0 }; | |
| 390 | + auto success = bool{}; | |
| 391 | + auto code = byte_t{}; | |
| 392 | + auto base = reinterpret_cast<byte_t *>(modobj.original_mp->allocation()); | |
| 393 | + for (;;) | |
| 394 | + { | |
| 395 | + auto bytesRead = reader.get_next_bytes(sizeof(code), &code); | |
| 396 | + if (bytesRead != sizeof(code)) throw module_reader_exception{ "Failed to read data item code" }; | |
| 397 | + if (code == 0) | |
| 398 | + break; | |
| 399 | + | |
| 400 | + const auto type = static_cast<datum_type_t>((code & 0xf0) >> 4); | |
| 401 | + auto count = static_cast<uint32_t>(code & 0x0f); | |
| 402 | + if (count == 0) | |
| 403 | + { | |
| 404 | + std::tie(success, count) = read_next_operand(reader); | |
| 405 | + if (!success) throw module_reader_exception{ "Failed to read long item datum count" }; | |
| 406 | + } | |
| 407 | + | |
| 408 | + auto byte_offset = operand_t{}; | |
| 409 | + std::tie(success, byte_offset) = read_next_operand(reader); | |
| 410 | + if (!success) throw module_reader_exception{ "Failed to read offset" }; | |
| 411 | + | |
| 412 | + // Compute the next data destination. | |
| 413 | + auto data_dest = base + byte_offset; | |
| 414 | + | |
| 415 | + switch (type) | |
| 416 | + { | |
| 417 | + case datum_type_t::value_bit8: | |
| 418 | + { | |
| 419 | + auto byte = uint8_t{}; | |
| 420 | + for (auto idx = uint32_t{ 0 }; idx < count; ++idx) | |
| 421 | + { | |
| 422 | + std::tie(success, byte) = reader.get_next_byte(); | |
| 423 | + if (!success) throw module_reader_exception{ "Failed to read 1 byte" }; | |
| 424 | + | |
| 425 | + *reinterpret_cast<byte_t *>(data_dest) = byte; | |
| 426 | + data_dest += sizeof(byte); | |
| 427 | + } | |
| 428 | + break; | |
| 429 | + } | |
| 430 | + case datum_type_t::value_bit32: | |
| 431 | + { | |
| 432 | + auto word = word_t{}; | |
| 433 | + for (auto idx = uint32_t{ 0 }; idx < count; ++idx) | |
| 434 | + { | |
| 435 | + word = word_t{ 0 }; | |
| 436 | + std::tie(success, word) = read_next_word(reader); | |
| 437 | + if (!success) throw module_reader_exception{ "Failed to read word" }; | |
| 438 | + | |
| 439 | + *reinterpret_cast<word_t *>(data_dest) = word; | |
| 440 | + data_dest += sizeof(word); | |
| 441 | + } | |
| 442 | + break; | |
| 443 | + } | |
| 444 | + case datum_type_t::utf_string: | |
| 445 | + { | |
| 446 | + auto str_buffer = std::vector<uint8_t>(count); | |
| 447 | + auto bytesRead = reader.get_next_bytes(count, str_buffer.data()); | |
| 448 | + if (bytesRead != count) | |
| 449 | + throw module_reader_exception{ "Failed to read string data" }; | |
| 450 | + | |
| 451 | + auto new_string = new vm_string_t(str_buffer.size(), str_buffer.data()); | |
| 452 | + *reinterpret_cast<pointer_t *>(data_dest) = new_string->allocation(); | |
| 453 | + break; | |
| 454 | + } | |
| 455 | + case datum_type_t::value_real64: | |
| 456 | + data_dest = read_8byte_segments(reader, datum_type_t::value_real64, count, data_dest); | |
| 457 | + break; | |
| 458 | + case datum_type_t::array: | |
| 459 | + { | |
| 460 | + auto element_type = word_t{}; | |
| 461 | + std::tie(success, element_type) = read_next_word(reader); | |
| 462 | + if (!success) throw module_reader_exception{ "Failed to read word" }; | |
| 463 | + | |
| 464 | + if (element_type < 0 || modobj.header.type_size < element_type) | |
| 465 | + if (!success) throw module_reader_exception{ "Invalid array element type" }; | |
| 466 | + | |
| 467 | + auto element_count = word_t{}; | |
| 468 | + std::tie(success, element_count) = read_next_word(reader); | |
| 469 | + if (!success) throw module_reader_exception{ "Failed to read word" }; | |
| 470 | + | |
| 471 | + auto new_array = new vm_array_t(modobj.type_section[element_type], element_count); | |
| 472 | + *reinterpret_cast<pointer_t *>(data_dest) = new_array->allocation(); | |
| 473 | + break; | |
| 474 | + } | |
| 475 | + case datum_type_t::set_array: | |
| 476 | + { | |
| 477 | + if (data_dest == nullptr) throw module_reader_exception{ "Invalid data type processing" }; | |
| 478 | + auto arr_maybe = vm_alloc_t::from_allocation(*reinterpret_cast<pointer_t *>(data_dest)); | |
| 479 | + assert(arr_maybe != nullptr); | |
| 480 | + | |
| 481 | + if (arr_maybe->alloc_type != vm_array_t::type_desc()) | |
| 482 | + throw module_reader_exception{ "Data index not an array type" }; | |
| 483 | + | |
| 484 | + auto arr = dynamic_cast<vm_array_t *>(arr_maybe); | |
| 485 | + | |
| 486 | + auto array_index = word_t{}; | |
| 487 | + std::tie(success, array_index) = read_next_word(reader); | |
| 488 | + if (!success) throw module_reader_exception{ "Failed to read word" }; | |
| 489 | + | |
| 490 | + if (array_index < 0 || arr->get_length() <= array_index || array_stack_pointer >= module_constants::array_address_stack_size) | |
| 491 | + throw module_reader_exception{ "Invalid array stack pointer" }; | |
| 492 | + | |
| 493 | + array_stack[array_stack_pointer++] = base; | |
| 494 | + base = reinterpret_cast<byte_t *>(arr->at(0)) + (array_index * arr->alloc_type->size_in_bytes); | |
| 495 | + | |
| 496 | + break; | |
| 497 | + } | |
| 498 | + case datum_type_t::restore_load_address: | |
| 499 | + if (array_stack_pointer == 0) throw module_reader_exception{ "Invalid array stack pointer" }; | |
| 500 | + | |
| 501 | + base = array_stack[--array_stack_pointer]; | |
| 502 | + assert(base != nullptr); | |
| 503 | + | |
| 504 | +#ifndef NDEBUG | |
| 505 | + array_stack[array_stack_pointer] = nullptr; | |
| 506 | +#endif | |
| 507 | + | |
| 508 | + break; | |
| 509 | + case datum_type_t::value_bit64: | |
| 510 | + data_dest = read_8byte_segments(reader, datum_type_t::value_bit64, count, data_dest); | |
| 511 | + break; | |
| 512 | + default: | |
| 513 | + throw module_reader_exception{ "Unknown item data type" }; | |
| 514 | + } | |
| 515 | + } | |
| 516 | + } | |
| 517 | + | |
| 518 | + // See module name in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html) | |
| 519 | + void read_module_name(util::buffered_reader_t &reader, vm_module_t &modobj) | |
| 520 | + { | |
| 521 | + auto string_buffer = std::vector<uint8_t>{}; | |
| 522 | + if (!read_next_string(reader, string_buffer)) | |
| 523 | + throw module_reader_exception{ "Failed to read module name" }; | |
| 524 | + | |
| 525 | + modobj.module_name = std::make_unique<vm_string_t>(string_buffer.size(), string_buffer.data()); | |
| 526 | + assert(modobj.module_name->get_length() <= module_constants::max_module_name_bytes); | |
| 527 | + } | |
| 528 | + | |
| 529 | + // See link section in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html) | |
| 530 | + void read_link_section(util::buffered_reader_t &reader, vm_module_t &modobj) | |
| 531 | + { | |
| 532 | + auto success = bool{}; | |
| 533 | + const auto export_count = modobj.header.export_size; | |
| 534 | + | |
| 535 | + // Allocate memory for the export entries | |
| 536 | + modobj.export_section.reserve(export_count); | |
| 537 | + | |
| 538 | + for (auto c = word_t{ 0 }; c < export_count; ++c) | |
| 539 | + { | |
| 540 | + // Read in single export item | |
| 541 | + auto item = export_function_t{}; | |
| 542 | + | |
| 543 | + std::tie(success, item.pc) = read_next_operand(reader); | |
| 544 | + if (!success) throw module_reader_exception{ "Failed to read linkage pc" }; | |
| 545 | + | |
| 546 | + std::tie(success, item.frame_type) = read_next_operand(reader); | |
| 547 | + if (!success) throw module_reader_exception{ "Failed to read linkage frame type index" }; | |
| 548 | + | |
| 549 | + std::tie(success, item.sig) = read_next_word(reader); | |
| 550 | + if (!success) throw module_reader_exception{ "Failed to read linkage signature" }; | |
| 551 | + | |
| 552 | + auto string_buffer = std::vector<uint8_t>{}; | |
| 553 | + if (!read_next_string(reader, string_buffer)) | |
| 554 | + throw module_reader_exception{ "Failed to read linkage name" }; | |
| 555 | + | |
| 556 | + item.name = std::make_unique<vm_string_t>(string_buffer.size(), string_buffer.data()); | |
| 557 | + | |
| 558 | + const auto sig = item.sig; | |
| 559 | + modobj.export_section.emplace(sig, std::move(item)); | |
| 560 | + } | |
| 561 | + } | |
| 562 | + | |
| 563 | + // See import section in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html) | |
| 564 | + // [SPEC] The specification does not seem to match what is observed in an actual module. | |
| 565 | + // The function is modeled after the disassembler (dis.b and libinterp/load.c) instead. | |
| 566 | + void read_import_section(util::buffered_reader_t &reader, vm_module_t &modobj) | |
| 567 | + { | |
| 568 | + auto success = bool{}; | |
| 569 | + auto module_import_count = operand_t{}; | |
| 570 | + std::tie(success, module_import_count) = read_next_operand(reader); | |
| 571 | + if (!success) throw module_reader_exception{ "Failed to read module import count" }; | |
| 572 | + | |
| 573 | + // Allocate memory for module imports | |
| 574 | + modobj.import_section.reserve(module_import_count); | |
| 575 | + | |
| 576 | + for (auto c = operand_t{ 0 }; c < module_import_count; ++c) | |
| 577 | + { | |
| 578 | + // Read in single module import | |
| 579 | + import_vm_module_t module{}; | |
| 580 | + | |
| 581 | + auto func_import_count = operand_t{}; | |
| 582 | + std::tie(success, func_import_count) = read_next_operand(reader); | |
| 583 | + if (!success) throw module_reader_exception{ "Failed to read function import count" }; | |
| 584 | + | |
| 585 | + // Allocate memory for function imports | |
| 586 | + module.functions.reserve(func_import_count); | |
| 587 | + | |
| 588 | + for (auto k = operand_t{ 0 }; k < func_import_count; ++k) | |
| 589 | + { | |
| 590 | + // Read in single function import | |
| 591 | + import_function_t func{}; | |
| 592 | + | |
| 593 | + std::tie(success, func.sig) = read_next_word(reader); | |
| 594 | + if (!success) throw module_reader_exception{ "Failed to read import function signature" }; | |
| 595 | + | |
| 596 | + auto string_buffer = std::vector<uint8_t>{}; | |
| 597 | + if (!read_next_string(reader, string_buffer)) | |
| 598 | + throw module_reader_exception{ "Failed to read import function name" }; | |
| 599 | + | |
| 600 | + func.name = std::make_unique<vm_string_t>(string_buffer.size(), string_buffer.data()); | |
| 601 | + | |
| 602 | + module.functions.push_back(std::move(func)); | |
| 603 | + } | |
| 604 | + | |
| 605 | + modobj.import_section.push_back(std::move(module)); | |
| 606 | + } | |
| 607 | + | |
| 608 | + byte_t value; | |
| 609 | + std::tie(success, value) = reader.get_next_byte(); | |
| 610 | + if (!success || value != 0) | |
| 611 | + throw module_reader_exception{ "Failed to read final byte from import section" }; | |
| 612 | + } | |
| 613 | + | |
| 614 | + // See handler section in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html) | |
| 615 | + void read_handler_section(util::buffered_reader_t &reader, vm_module_t &modobj) | |
| 616 | + { | |
| 617 | + auto success = bool{}; | |
| 618 | + auto handler_count = operand_t{}; | |
| 619 | + std::tie(success, handler_count) = read_next_operand(reader); | |
| 620 | + | |
| 621 | + // Allocate memory for handlers | |
| 622 | + modobj.handler_section.reserve(handler_count); | |
| 623 | + | |
| 624 | + for (auto c = operand_t{ 0 }; c < handler_count; ++c) | |
| 625 | + { | |
| 626 | + // Read in single handler | |
| 627 | + handler_t handler{}; | |
| 628 | + | |
| 629 | + std::tie(success, handler.exception_offset) = read_next_operand(reader); | |
| 630 | + if (!success) throw module_reader_exception{ "Failed to read exception offset" }; | |
| 631 | + | |
| 632 | + std::tie(success, handler.begin_pc) = read_next_operand(reader); | |
| 633 | + if (!success) throw module_reader_exception{ "Failed to read handler begin program counter" }; | |
| 634 | + | |
| 635 | + std::tie(success, handler.end_pc) = read_next_operand(reader); | |
| 636 | + if (!success) throw module_reader_exception{ "Failed to read handler end program counter" }; | |
| 637 | + | |
| 638 | + auto type_desc_number = operand_t{}; | |
| 639 | + std::tie(success, type_desc_number) = read_next_operand(reader); | |
| 640 | + if (!success) throw module_reader_exception{ "Failed to read handler type description ID" }; | |
| 641 | + | |
| 642 | + if (type_desc_number != module_constants::no_error_handle_type_desc) | |
| 643 | + { | |
| 644 | + assert(0 <= type_desc_number && type_desc_number < static_cast<operand_t>(modobj.type_section.size())); | |
| 645 | + handler.type_desc = modobj.type_section[type_desc_number]; | |
| 646 | + assert(handler.type_desc != nullptr); | |
| 647 | + } | |
| 648 | + | |
| 649 | + auto handler_cases = operand_t{}; | |
| 650 | + std::tie(success, handler_cases) = read_next_operand(reader); | |
| 651 | + | |
| 652 | + // [SPEC] The operand read above contains two pieces of data. | |
| 653 | + // The higher 16-bits represent the number of exception cases that catch exception types. | |
| 654 | + // The lower 16-bits represent the total number of handled cases. | |
| 655 | + // In the limbo language strings and exceptions can be caught in an exception handler. | |
| 656 | + // Based on Inferno implementation libinterp/load.c. | |
| 657 | + handler.exception_type_count = word_t{ handler_cases >> 16 }; | |
| 658 | + const auto total_count = (handler_cases & 0xffff); | |
| 659 | + | |
| 660 | + // Allocate memory for exceptions plus the wild card. | |
| 661 | + handler.exception_table.reserve(total_count + 1); | |
| 662 | + | |
| 663 | + for (auto k = operand_t{ 0 }; k < total_count; ++k) | |
| 664 | + { | |
| 665 | + // Read in single exception | |
| 666 | + exception_t except{}; | |
| 667 | + | |
| 668 | + auto string_buffer = std::vector<uint8_t>{}; | |
| 669 | + if (!read_next_string(reader, string_buffer)) | |
| 670 | + throw module_reader_exception{ "Failed to read exception name" }; | |
| 671 | + | |
| 672 | + except.name = std::make_unique<vm_string_t>(string_buffer.size(), string_buffer.data()); | |
| 673 | + | |
| 674 | + std::tie(success, except.pc) = read_next_operand(reader); | |
| 675 | + if (!success) throw module_reader_exception{ "Failed to read exception pc" }; | |
| 676 | + | |
| 677 | + handler.exception_table.push_back(std::move(except)); | |
| 678 | + } | |
| 679 | + | |
| 680 | + // Read in wildcard exception | |
| 681 | + exception_t wildcard_exception{}; | |
| 682 | + std::tie(success, wildcard_exception.pc) = read_next_operand(reader); | |
| 683 | + if (!success) throw module_reader_exception{ "Failed to read wildcard exception pc" }; | |
| 684 | + | |
| 685 | + handler.exception_table.push_back(std::move(wildcard_exception)); | |
| 686 | + | |
| 687 | + modobj.handler_section.push_back(std::move(handler)); | |
| 688 | + } | |
| 689 | + | |
| 690 | + byte_t value; | |
| 691 | + std::tie(success, value) = reader.get_next_byte(); | |
| 692 | + if (!success || value != 0) | |
| 693 | + throw module_reader_exception{ "Failed to read final byte from handler section" }; | |
| 694 | + } | |
| 695 | +} | |
| 696 | + | |
| 697 | +export_function_t::export_function_t(export_function_t &&other) | |
| 698 | + : pc{ other.pc } | |
| 699 | + , frame_type{ other.frame_type } | |
| 700 | + , sig{ other.sig } | |
| 701 | + , name{ std::move(other.name) } | |
| 702 | +{ | |
| 703 | +} | |
| 704 | + | |
| 705 | +export_function_t::~export_function_t() | |
| 706 | +{ | |
| 707 | + if (name != nullptr) | |
| 708 | + name->release(); | |
| 709 | +} | |
| 710 | + | |
| 711 | +import_function_t::import_function_t(import_function_t &&other) | |
| 712 | + : sig{ other.sig } | |
| 713 | + , name{ std::move(other.name) } | |
| 714 | +{ | |
| 715 | +} | |
| 716 | + | |
| 717 | +import_function_t::~import_function_t() | |
| 718 | +{ | |
| 719 | + if (name != nullptr) | |
| 720 | + name->release(); | |
| 721 | +} | |
| 722 | + | |
| 723 | +import_vm_module_t::import_vm_module_t(import_vm_module_t &&other) | |
| 724 | + : functions{ std::move(other.functions) } | |
| 725 | +{ | |
| 726 | +} | |
| 727 | + | |
| 728 | +exception_t::exception_t(exception_t &&other) | |
| 729 | + : pc{ other.pc } | |
| 730 | + , name{ std::move(other.name) } | |
| 731 | +{ | |
| 732 | +} | |
| 733 | + | |
| 734 | +exception_t::~exception_t() | |
| 735 | +{ | |
| 736 | + if (name != nullptr) | |
| 737 | + name->release(); | |
| 738 | +} | |
| 739 | + | |
| 740 | +handler_t::handler_t(handler_t &&other) | |
| 741 | + : exception_offset{ other.exception_offset } | |
| 742 | + , begin_pc{ other.begin_pc } | |
| 743 | + , end_pc{ other.end_pc } | |
| 744 | + , type_desc{ std::move(other.type_desc) } | |
| 745 | + , exception_table{ std::move(other.exception_table) } | |
| 746 | + , exception_type_count{ other.exception_type_count } | |
| 747 | +{ | |
| 748 | +} | |
| 749 | + | |
| 750 | +vm_module_t::~vm_module_t() | |
| 751 | +{ | |
| 752 | + if (module_name != nullptr) | |
| 753 | + module_name->release(); | |
| 754 | + | |
| 755 | + if (original_mp != nullptr) | |
| 756 | + original_mp->release(); | |
| 757 | +} | |
| 758 | + | |
| 759 | +std::unique_ptr<vm_module_t> disvm::read_module(const char *path) | |
| 760 | +{ | |
| 761 | + assert(path != nullptr); | |
| 762 | + | |
| 763 | + auto module_file = std::ifstream{ path, std::ifstream::binary }; | |
| 764 | + if (!module_file.is_open()) | |
| 765 | + { | |
| 766 | + assert(false && "Failed to open module"); | |
| 767 | + throw vm_user_exception{ "Failed to open module" }; | |
| 768 | + } | |
| 769 | + | |
| 770 | + return read_module(module_file); | |
| 771 | +} | |
| 772 | + | |
| 773 | +std::unique_ptr<runtime::vm_module_t> disvm::read_module(std::istream &data) | |
| 774 | +{ | |
| 775 | + util::buffered_reader_t reader{ data }; | |
| 776 | + | |
| 777 | + auto modobj = std::make_unique<vm_module_t>(); | |
| 778 | + | |
| 779 | + read_header(reader, *modobj); | |
| 780 | + | |
| 781 | + if (util::has_flag(modobj->header.runtime_flag, runtime_flags_t::has_import_deprecated)) | |
| 782 | + throw module_reader_exception{ "Obsolete module" }; | |
| 783 | + | |
| 784 | + read_code_section(reader, *modobj); | |
| 785 | + read_type_section(reader, *modobj); | |
| 786 | + read_data_section(reader, *modobj); | |
| 787 | + read_module_name(reader, *modobj); | |
| 788 | + read_link_section(reader, *modobj); | |
| 789 | + | |
| 790 | + if (util::has_flag(modobj->header.runtime_flag, runtime_flags_t::has_import)) | |
| 791 | + read_import_section(reader, *modobj); | |
| 792 | + | |
| 793 | + if (util::has_flag(modobj->header.runtime_flag, runtime_flags_t::has_handler)) | |
| 794 | + read_handler_section(reader, *modobj); | |
| 795 | + | |
| 796 | + // [TODO] Native code generation - runtime_flags_t::must_compile | |
| 797 | + assert(!util::has_flag(modobj->header.runtime_flag, runtime_flags_t::must_compile)); | |
| 798 | + | |
| 799 | + return modobj; | |
| 800 | +} | |
| @@ -0,0 +1,128 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: module_ref.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <runtime.h> | |
| 8 | +#include <exceptions.h> | |
| 9 | +#include <vm_memory.h> | |
| 10 | +#include <utils.h> | |
| 11 | +#include <debug.h> | |
| 12 | + | |
| 13 | +using namespace disvm; | |
| 14 | +using namespace disvm::runtime; | |
| 15 | + | |
| 16 | +namespace | |
| 17 | +{ | |
| 18 | + std::unique_ptr<vm_array_t> resolve_imports(const export_section_t &exports, const import_vm_module_t &imports) | |
| 19 | + { | |
| 20 | + static auto function_ref_type_desc = type_descriptor_t::create(sizeof(vm_module_function_ref_t), std::vector<byte_t>{}); | |
| 21 | + auto refs = std::make_unique<vm_array_t>(function_ref_type_desc, imports.functions.size()); | |
| 22 | + | |
| 23 | + for (auto index = std::size_t{ 0 }; index < imports.functions.size(); ++index) | |
| 24 | + { | |
| 25 | + const auto &import = imports.functions[index]; | |
| 26 | + | |
| 27 | + const auto exports_with_sig = exports.equal_range(import.sig); | |
| 28 | + if (exports_with_sig.first == exports.cend() && exports_with_sig.second == exports.cend()) | |
| 29 | + throw vm_user_exception{ "Module does not export a function with expected signature" }; | |
| 30 | + | |
| 31 | + auto export_match = exports_with_sig.first; | |
| 32 | + while (export_match != exports_with_sig.second) | |
| 33 | + { | |
| 34 | + // Look for the matching function name in those with matching signature | |
| 35 | + if (0 == vm_string_t::compare(export_match->second.name.get(), import.name.get())) | |
| 36 | + break; | |
| 37 | + | |
| 38 | + ++export_match; | |
| 39 | + } | |
| 40 | + | |
| 41 | + if (export_match == exports_with_sig.second) | |
| 42 | + throw vm_user_exception{ "Type check failure for exported function" }; | |
| 43 | + | |
| 44 | + const auto &export_entry = export_match->second; | |
| 45 | + | |
| 46 | + auto &ref = refs->at<vm_module_function_ref_t>(index); | |
| 47 | + ref.entry_pc = export_entry.pc; | |
| 48 | + ref.frame_type = export_entry.frame_type; | |
| 49 | + | |
| 50 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::module>()) | |
| 51 | + { | |
| 52 | + debug::log_msg( | |
| 53 | + debug::component_trace_t::module, | |
| 54 | + debug::log_level_t::debug, | |
| 55 | + "export: %d %#08x >>%s<<\n", | |
| 56 | + index, | |
| 57 | + export_entry.sig, | |
| 58 | + export_entry.name->str()); | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + return refs; | |
| 63 | + } | |
| 64 | +} | |
| 65 | + | |
| 66 | +std::shared_ptr<const type_descriptor_t> vm_module_ref_t::type_desc() | |
| 67 | +{ | |
| 68 | + return intrinsic_type_desc::type<vm_module_ref_t>(); | |
| 69 | +} | |
| 70 | + | |
| 71 | +vm_module_ref_t::vm_module_ref_t(std::shared_ptr<const vm_module_t> module) | |
| 72 | + : vm_alloc_t(vm_module_ref_t::type_desc()) | |
| 73 | + , code_section{ module->code_section } | |
| 74 | + , module{ module } | |
| 75 | + , mp_base{ nullptr } | |
| 76 | + , type_section{ module->type_section } | |
| 77 | + , _builtin_module{ util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin) } | |
| 78 | +{ | |
| 79 | + if (module->original_mp != nullptr) | |
| 80 | + mp_base = vm_alloc_t::copy(*module->original_mp); | |
| 81 | + | |
| 82 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 83 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm module ref\n"); | |
| 84 | +} | |
| 85 | + | |
| 86 | +vm_module_ref_t::vm_module_ref_t(std::shared_ptr<const vm_module_t> module, const import_vm_module_t &imports) | |
| 87 | + : vm_alloc_t(vm_module_ref_t::type_desc()) | |
| 88 | + , code_section{ module->code_section } | |
| 89 | + , module{ module } | |
| 90 | + , type_section{ module->type_section } | |
| 91 | + , _function_refs{ resolve_imports(module->export_section, imports) } | |
| 92 | + , _builtin_module{ util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin) } | |
| 93 | +{ | |
| 94 | + assert(module->header.data_size == 0 || module->original_mp != nullptr); | |
| 95 | + | |
| 96 | + if (module->original_mp != nullptr) | |
| 97 | + mp_base = vm_alloc_t::copy(*module->original_mp); | |
| 98 | + | |
| 99 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 100 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm module ref: exported %d\n", _function_refs->get_length()); | |
| 101 | +} | |
| 102 | + | |
| 103 | +vm_module_ref_t::~vm_module_ref_t() | |
| 104 | +{ | |
| 105 | + dec_ref_count_and_free(mp_base); | |
| 106 | + | |
| 107 | + if (_function_refs != nullptr) | |
| 108 | + _function_refs->release(); | |
| 109 | + | |
| 110 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 111 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm module ref\n"); | |
| 112 | +} | |
| 113 | + | |
| 114 | +bool vm_module_ref_t::is_builtin_module() const | |
| 115 | +{ | |
| 116 | + return _builtin_module; | |
| 117 | +} | |
| 118 | + | |
| 119 | +const vm_module_function_ref_t& vm_module_ref_t::get_function_ref(word_t index) const | |
| 120 | +{ | |
| 121 | + assert(_function_refs != nullptr); | |
| 122 | + | |
| 123 | + if (index < 0 || _function_refs->get_length() <= index) | |
| 124 | + throw vm_system_exception{ "Invalid function reference index into module reference functions" }; | |
| 125 | + | |
| 126 | + return _function_refs->at<vm_module_function_ref_t>(index); | |
| 127 | +} | |
| 128 | + | |
| @@ -0,0 +1,332 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: scheduler.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <debug.h> | |
| 8 | +#include <exceptions.h> | |
| 9 | +#include "scheduler.h" | |
| 10 | + | |
| 11 | +using namespace disvm; | |
| 12 | +using namespace disvm::runtime; | |
| 13 | + | |
| 14 | +// Empty destructors for vm scheduler 'interfaces' | |
| 15 | +vm_scheduler_t::~vm_scheduler_t() | |
| 16 | +{ | |
| 17 | +} | |
| 18 | + | |
| 19 | +vm_scheduler_control_t::~vm_scheduler_control_t() | |
| 20 | +{ | |
| 21 | +} | |
| 22 | + | |
| 23 | +void default_scheduler_t::worker_main(default_scheduler_t &instance) | |
| 24 | +{ | |
| 25 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: worker: start\n"); | |
| 26 | + | |
| 27 | + // [TODO] Handle cases of when a vm_system_exception is thrown | |
| 28 | + | |
| 29 | + auto current_thread = std::shared_ptr<thread_instance_t>{}; | |
| 30 | + for (;;) | |
| 31 | + { | |
| 32 | + current_thread = instance.next_thread(std::move(current_thread)); | |
| 33 | + if (current_thread == nullptr) | |
| 34 | + { | |
| 35 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: worker: stop\n"); | |
| 36 | + break; | |
| 37 | + } | |
| 38 | + | |
| 39 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>()) | |
| 40 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: worker: execute: %d\n", current_thread->vm_thread->get_thread_id()); | |
| 41 | + | |
| 42 | + current_thread->vm_thread->execute(instance._vm, instance._vm_thread_quanta); | |
| 43 | + } | |
| 44 | +} | |
| 45 | + | |
| 46 | +default_scheduler_t::default_scheduler_t(vm_t &vm, uint32_t system_thread_count, uint32_t thread_quanta) | |
| 47 | + : _gc_complete{ true } | |
| 48 | + , _gc_counter{ 1 } | |
| 49 | + , _worker_thread_count{ system_thread_count } | |
| 50 | + , _vm{ vm } | |
| 51 | + , _vm_thread_quanta{ thread_quanta } | |
| 52 | +{ | |
| 53 | +} | |
| 54 | + | |
| 55 | +default_scheduler_t::~default_scheduler_t() | |
| 56 | +{ | |
| 57 | + // Drain the runnable queue and clear out all the threads | |
| 58 | + { | |
| 59 | + std::lock_guard<std::mutex> lock{ _vm_threads_lock }; | |
| 60 | + while (!_runnable_vm_threads.empty()) | |
| 61 | + _runnable_vm_threads.pop(); | |
| 62 | + | |
| 63 | + _all_vm_threads.clear(); | |
| 64 | + } | |
| 65 | + | |
| 66 | + // Set terminating flag | |
| 67 | + { | |
| 68 | + std::unique_lock<std::mutex> worker_event_lock{ _worker_event_lock }; | |
| 69 | + _terminating = true; | |
| 70 | + } | |
| 71 | + | |
| 72 | + // Notify all workers | |
| 73 | + _worker_event.notify_all(); | |
| 74 | + | |
| 75 | + // Wait for all worker threads to finish | |
| 76 | + for (auto &w : _worker_pool) | |
| 77 | + w.join(); | |
| 78 | + | |
| 79 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: shutdown\n"); | |
| 80 | +} | |
| 81 | + | |
| 82 | +bool default_scheduler_t::is_idle() const | |
| 83 | +{ | |
| 84 | + std::lock_guard<std::mutex> lock{ _vm_threads_lock }; | |
| 85 | + return _running_vm_thread_count == 0 && _runnable_vm_threads.empty() && _blocked_vm_thread_ids.empty(); | |
| 86 | +} | |
| 87 | + | |
| 88 | +vm_scheduler_control_t &default_scheduler_t::get_controller() const | |
| 89 | +{ | |
| 90 | + return const_cast<default_scheduler_t&>(*this); | |
| 91 | +} | |
| 92 | + | |
| 93 | +const vm_thread_t& default_scheduler_t::schedule_thread(std::unique_ptr<runtime::vm_thread_t> thread) | |
| 94 | +{ | |
| 95 | + assert(thread != nullptr); | |
| 96 | + if (thread->get_state() != vm_thread_state_t::ready) | |
| 97 | + throw vm_system_exception{ "Scheduled thread in invalid state" }; | |
| 98 | + | |
| 99 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>()) | |
| 100 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: scheduled: %d\n", thread->get_thread_id()); | |
| 101 | + | |
| 102 | + std::lock_guard<std::mutex> lock{ _vm_threads_lock }; | |
| 103 | + | |
| 104 | + // Create workers on first scheduled thread | |
| 105 | + if (_worker_pool.empty()) | |
| 106 | + { | |
| 107 | + for (auto i = uint32_t{ 0 }; i < _worker_thread_count; ++i) | |
| 108 | + _worker_pool.push_back(std::thread{ default_scheduler_t::worker_main, std::ref(*this) }); | |
| 109 | + } | |
| 110 | + | |
| 111 | + const auto new_thread_id = thread->get_thread_id(); | |
| 112 | + assert(_all_vm_threads.find(new_thread_id) == _all_vm_threads.cend()); | |
| 113 | + | |
| 114 | + auto new_thread = std::make_shared<thread_instance_t>(std::move(thread)); | |
| 115 | + | |
| 116 | + // Allocate a new container and set the thread entry | |
| 117 | + _all_vm_threads[new_thread_id] = new_thread; | |
| 118 | + | |
| 119 | + _runnable_vm_threads.push(new_thread); | |
| 120 | + | |
| 121 | + // Notify the worker a thread has been enqueued. | |
| 122 | + _worker_event.notify_one(); | |
| 123 | + | |
| 124 | + return *(new_thread->vm_thread); | |
| 125 | +} | |
| 126 | + | |
| 127 | +void default_scheduler_t::enqueue_blocked_thread(uint32_t thread_id) | |
| 128 | +{ | |
| 129 | + all_thread_map_t::iterator thread_to_enqueue; | |
| 130 | + { | |
| 131 | + std::lock_guard<std::mutex> lock{ _vm_threads_lock }; | |
| 132 | + thread_to_enqueue = _all_vm_threads.find(thread_id); | |
| 133 | + if (thread_to_enqueue == _all_vm_threads.cend()) | |
| 134 | + { | |
| 135 | + assert(false && "Unknown thread to enqueue"); | |
| 136 | + return; | |
| 137 | + } | |
| 138 | + } | |
| 139 | + | |
| 140 | + auto thread_container = thread_to_enqueue->second; | |
| 141 | + | |
| 142 | + // Lock the vm thread collections and this vm thread | |
| 143 | + std::lock(_vm_threads_lock, thread_container->system_thread_ownership); | |
| 144 | + std::lock_guard<std::mutex> lock_all{ _vm_threads_lock, std::adopt_lock }; | |
| 145 | + std::lock_guard<std::mutex> lock_thread{ thread_container->system_thread_ownership, std::adopt_lock }; | |
| 146 | + | |
| 147 | + // If the thread is not blocked, just return. | |
| 148 | + auto blocked_iter = _blocked_vm_thread_ids.find(thread_id); | |
| 149 | + if (blocked_iter == _blocked_vm_thread_ids.cend()) | |
| 150 | + { | |
| 151 | + assert(false && "Thread to enqueue not blocked"); | |
| 152 | + return; | |
| 153 | + } | |
| 154 | + | |
| 155 | + _blocked_vm_thread_ids.erase(blocked_iter); | |
| 156 | + | |
| 157 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>()) | |
| 158 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: enqueue blocked: %d\n", thread_id); | |
| 159 | + | |
| 160 | + enqueue_thread_unsafe(thread_container, vm_thread_state_t::ready); | |
| 161 | +} | |
| 162 | + | |
| 163 | +std::vector<std::shared_ptr<const vm_thread_t>> default_scheduler_t::get_all_threads() const | |
| 164 | +{ | |
| 165 | + auto result = std::vector<std::shared_ptr<const vm_thread_t>>{}; | |
| 166 | + | |
| 167 | + { | |
| 168 | + std::lock_guard<std::mutex> lock{ _vm_threads_lock }; | |
| 169 | + for (auto &p : _all_vm_threads) | |
| 170 | + result.push_back(p.second->vm_thread); | |
| 171 | + } | |
| 172 | + | |
| 173 | + return result; | |
| 174 | +} | |
| 175 | + | |
| 176 | +std::shared_ptr<default_scheduler_t::thread_instance_t> default_scheduler_t::next_thread(std::shared_ptr<thread_instance_t> prev_thread) | |
| 177 | +{ | |
| 178 | + auto next_thread = std::shared_ptr<thread_instance_t>{}; | |
| 179 | + | |
| 180 | + // Check if the passed in thread should be re-enqueued | |
| 181 | + if (prev_thread != nullptr) | |
| 182 | + { | |
| 183 | + std::lock_guard<std::mutex> lock{ _vm_threads_lock }; | |
| 184 | + --_running_vm_thread_count; | |
| 185 | + auto current_state = prev_thread->vm_thread->get_state(); | |
| 186 | + | |
| 187 | + // This system thread releases ownership of the vm thread | |
| 188 | + prev_thread->system_thread_ownership.unlock(); | |
| 189 | + enqueue_thread_unsafe(std::move(prev_thread), current_state); | |
| 190 | + } | |
| 191 | + | |
| 192 | + for (;;) | |
| 193 | + { | |
| 194 | + // Check for a vm thread in the queue | |
| 195 | + { | |
| 196 | + std::unique_lock<std::mutex> lock{ _vm_threads_lock }; | |
| 197 | + | |
| 198 | + // Check if a GC should be performed. | |
| 199 | + // Matching part of the logic in Inferno (emu/port/dis.c). | |
| 200 | + if ((_gc_counter & 0xff) == 0) | |
| 201 | + { | |
| 202 | + const auto running_thread_count_local = _running_vm_thread_count; | |
| 203 | + const auto is_gc_thread = running_thread_count_local == 0; | |
| 204 | + perform_gc(is_gc_thread, lock); | |
| 205 | + } | |
| 206 | + | |
| 207 | + if (!_runnable_vm_threads.empty()) | |
| 208 | + { | |
| 209 | + next_thread = _runnable_vm_threads.front(); | |
| 210 | + _runnable_vm_threads.pop(); | |
| 211 | + ++_running_vm_thread_count; | |
| 212 | + ++_gc_counter; | |
| 213 | + | |
| 214 | + // This system thread now takes ownership of the vm thread | |
| 215 | + next_thread->system_thread_ownership.lock(); | |
| 216 | + return next_thread; | |
| 217 | + } | |
| 218 | + | |
| 219 | + if (!_all_vm_threads.empty() && _blocked_vm_thread_ids.size() == _all_vm_threads.size()) | |
| 220 | + { | |
| 221 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::warning, "scheduler: deadlock detected\n"); | |
| 222 | + assert(false && "VM thread deadlock detected"); | |
| 223 | + } | |
| 224 | + } | |
| 225 | + | |
| 226 | + // If the queue is empty, wait for a notification | |
| 227 | + { | |
| 228 | + std::unique_lock<std::mutex> worker_event_lock{ _worker_event_lock }; | |
| 229 | + if (_terminating) | |
| 230 | + return{}; | |
| 231 | + | |
| 232 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: worker: waiting\n"); | |
| 233 | + _worker_event.wait(worker_event_lock); | |
| 234 | + | |
| 235 | + if (_terminating) | |
| 236 | + return{}; | |
| 237 | + } | |
| 238 | + } | |
| 239 | +} | |
| 240 | + | |
| 241 | +void default_scheduler_t::perform_gc(bool is_gc_thread, std::unique_lock<std::mutex> &all_vm_threads_lock) | |
| 242 | +{ | |
| 243 | + if (!is_gc_thread) | |
| 244 | + { | |
| 245 | + _gc_complete = false; | |
| 246 | + all_vm_threads_lock.unlock(); | |
| 247 | + std::unique_lock<std::mutex> lock{ _gc_wait }; | |
| 248 | + _gc_done_event.wait(lock, [&]{ return _gc_complete.load(); }); | |
| 249 | + | |
| 250 | + // After the gc completes, take back the lock. | |
| 251 | + all_vm_threads_lock.lock(); | |
| 252 | + } | |
| 253 | + else | |
| 254 | + { | |
| 255 | + auto result = std::vector<std::shared_ptr<const vm_thread_t>>{}; | |
| 256 | + for (auto &p : _all_vm_threads) | |
| 257 | + { | |
| 258 | + auto &vm_thread = p.second->vm_thread; | |
| 259 | + if (vm_thread->get_state() != vm_thread_state_t::broken) | |
| 260 | + result.push_back(vm_thread); | |
| 261 | + } | |
| 262 | + | |
| 263 | + _vm.get_garbage_collector().collect(result); | |
| 264 | + _gc_complete = true; | |
| 265 | + _gc_done_event.notify_all(); | |
| 266 | + } | |
| 267 | +} | |
| 268 | + | |
| 269 | +void default_scheduler_t::enqueue_thread_unsafe(std::shared_ptr<thread_instance_t> thread_instance, runtime::vm_thread_state_t current_state) | |
| 270 | +{ | |
| 271 | + assert(thread_instance != nullptr); | |
| 272 | + | |
| 273 | + const auto thread_id = thread_instance->vm_thread->get_thread_id(); | |
| 274 | + switch (current_state) | |
| 275 | + { | |
| 276 | + case vm_thread_state_t::ready: | |
| 277 | + case vm_thread_state_t::debug: | |
| 278 | + { | |
| 279 | + _runnable_vm_threads.push(thread_instance); | |
| 280 | + | |
| 281 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>()) | |
| 282 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: enqueue: %d\n", thread_id); | |
| 283 | + | |
| 284 | + // Notify a worker a thread has been enqueued. | |
| 285 | + _worker_event.notify_one(); | |
| 286 | + break; | |
| 287 | + } | |
| 288 | + | |
| 289 | + case vm_thread_state_t::blocked_in_alt: | |
| 290 | + case vm_thread_state_t::blocked_sending: | |
| 291 | + case vm_thread_state_t::blocked_receiving: | |
| 292 | + { | |
| 293 | + _blocked_vm_thread_ids.insert(thread_id); | |
| 294 | + | |
| 295 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>()) | |
| 296 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: blocked: %d\n", thread_id); | |
| 297 | + | |
| 298 | + break; | |
| 299 | + } | |
| 300 | + | |
| 301 | + case vm_thread_state_t::empty_stack: | |
| 302 | + case vm_thread_state_t::exiting: | |
| 303 | + { | |
| 304 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>()) | |
| 305 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: exiting: %d\n", thread_id); | |
| 306 | + | |
| 307 | + // Remove the thread | |
| 308 | + auto thread_to_remove = _all_vm_threads.find(thread_id); | |
| 309 | + assert(thread_to_remove != _all_vm_threads.cend()); | |
| 310 | + _all_vm_threads.erase(thread_to_remove); | |
| 311 | + | |
| 312 | + break; | |
| 313 | + } | |
| 314 | + | |
| 315 | + case vm_thread_state_t::broken: | |
| 316 | + { | |
| 317 | + // [TODO] What happens when a thread enters this state but no debugger is attached? | |
| 318 | + _non_runnable_vm_thread_ids.push_front(thread_id); | |
| 319 | + | |
| 320 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>()) | |
| 321 | + debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: broken: %d\n", thread_id); | |
| 322 | + | |
| 323 | + break; | |
| 324 | + } | |
| 325 | + | |
| 326 | + case vm_thread_state_t::release: | |
| 327 | + case vm_thread_state_t::running: | |
| 328 | + default: | |
| 329 | + assert(false && "Unexpected thread state"); | |
| 330 | + throw vm_system_exception{ "Unexpected thread state" }; | |
| 331 | + } | |
| 332 | +} | |
| @@ -0,0 +1,102 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: scheduler.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_VM_SCHEDULER_H_ | |
| 8 | +#define _DISVM_SRC_VM_SCHEDULER_H_ | |
| 9 | + | |
| 10 | +#include <cstdint> | |
| 11 | +#include <thread> | |
| 12 | +#include <mutex> | |
| 13 | +#include <condition_variable> | |
| 14 | +#include <queue> | |
| 15 | +#include <forward_list> | |
| 16 | +#include <unordered_set> | |
| 17 | +#include <unordered_map> | |
| 18 | +#include <atomic> | |
| 19 | +#include <disvm.h> | |
| 20 | + | |
| 21 | +namespace disvm | |
| 22 | +{ | |
| 23 | + namespace runtime | |
| 24 | + { | |
| 25 | + // Default multi-threaded scheduler | |
| 26 | + class default_scheduler_t final : public vm_scheduler_t, public vm_scheduler_control_t | |
| 27 | + { | |
| 28 | + private: // static | |
| 29 | + static void worker_main(default_scheduler_t &instance); | |
| 30 | + | |
| 31 | + public: | |
| 32 | + default_scheduler_t(vm_t &vm, uint32_t system_thread_count, uint32_t thread_quanta); | |
| 33 | + | |
| 34 | + ~default_scheduler_t(); | |
| 35 | + | |
| 36 | + public: // vm_scheduler_t | |
| 37 | + bool is_idle() const override final; | |
| 38 | + | |
| 39 | + vm_scheduler_control_t &get_controller() const override final; | |
| 40 | + | |
| 41 | + const vm_thread_t& schedule_thread(std::unique_ptr<vm_thread_t> thread) override final; | |
| 42 | + | |
| 43 | + public: // vm_scheduler_control_t | |
| 44 | + void enqueue_blocked_thread(uint32_t thread_id) override final; | |
| 45 | + | |
| 46 | + std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override final; | |
| 47 | + | |
| 48 | + private: | |
| 49 | + struct thread_instance_t | |
| 50 | + { | |
| 51 | + thread_instance_t(std::unique_ptr<vm_thread_t> t) | |
| 52 | + : vm_thread{ std::move(t) } | |
| 53 | + { | |
| 54 | + } | |
| 55 | + | |
| 56 | + ~thread_instance_t() | |
| 57 | + { | |
| 58 | + if (vm_thread != nullptr) | |
| 59 | + vm_thread->release(); | |
| 60 | + } | |
| 61 | + | |
| 62 | + std::shared_ptr<vm_thread_t> vm_thread; | |
| 63 | + std::mutex system_thread_ownership; | |
| 64 | + }; | |
| 65 | + | |
| 66 | + std::shared_ptr<thread_instance_t> next_thread(std::shared_ptr<thread_instance_t> prev_thread); | |
| 67 | + | |
| 68 | + void perform_gc(bool is_gc_thread, std::unique_lock<std::mutex> &all_vm_threads_lock); | |
| 69 | + | |
| 70 | + // Add the thread to the queue in a non-thread safe manner. | |
| 71 | + void enqueue_thread_unsafe(std::shared_ptr<thread_instance_t> thread, runtime::vm_thread_state_t current_state); | |
| 72 | + | |
| 73 | + private: | |
| 74 | + vm_t &_vm; | |
| 75 | + | |
| 76 | + const uint32_t _worker_thread_count; | |
| 77 | + std::vector<std::thread> _worker_pool; | |
| 78 | + | |
| 79 | + std::mutex _worker_event_lock; | |
| 80 | + std::condition_variable _worker_event; | |
| 81 | + std::atomic_bool _terminating; | |
| 82 | + | |
| 83 | + const uint32_t _vm_thread_quanta; | |
| 84 | + mutable std::mutex _vm_threads_lock; | |
| 85 | + std::queue<std::shared_ptr<thread_instance_t>> _runnable_vm_threads; | |
| 86 | + std::size_t _running_vm_thread_count; | |
| 87 | + | |
| 88 | + std::mutex _gc_wait; | |
| 89 | + std::condition_variable _gc_done_event; | |
| 90 | + std::atomic_bool _gc_complete; // Used to avoid spurious wakeups | |
| 91 | + std::size_t _gc_counter; | |
| 92 | + | |
| 93 | + std::unordered_set<uint32_t> _blocked_vm_thread_ids; | |
| 94 | + std::forward_list<uint32_t> _non_runnable_vm_thread_ids; | |
| 95 | + | |
| 96 | + using all_thread_map_t = std::unordered_map<uint32_t, std::shared_ptr<thread_instance_t>>; | |
| 97 | + all_thread_map_t _all_vm_threads; | |
| 98 | + }; | |
| 99 | + } | |
| 100 | +} | |
| 101 | + | |
| 102 | +#endif // _DISVM_SRC_VM_SCHEDULER_H_ | |
| @@ -0,0 +1,314 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: stack.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cinttypes> | |
| 8 | +#include <runtime.h> | |
| 9 | +#include <builtin_module.h> | |
| 10 | +#include <vm_memory.h> | |
| 11 | +#include <debug.h> | |
| 12 | +#include <exceptions.h> | |
| 13 | + | |
| 14 | +using namespace disvm; | |
| 15 | +using namespace disvm::runtime; | |
| 16 | + | |
| 17 | +namespace | |
| 18 | +{ | |
| 19 | + struct vm_stack_page | |
| 20 | + { | |
| 21 | + vm_stack_page *prev_page; | |
| 22 | + uintptr_t page_limit_addr; | |
| 23 | + vm_frame_t *page_top_frame; // Do not free. This is used as a tracking pointer. | |
| 24 | + | |
| 25 | + void *stack_data() const | |
| 26 | + { | |
| 27 | + // The beginning of the stack data is at the end of the struct. | |
| 28 | + return reinterpret_cast<uint8_t *>(const_cast<vm_stack_page *>(this)) + sizeof(vm_stack_page); | |
| 29 | + } | |
| 30 | + | |
| 31 | + bool contains_frame(const vm_frame_t *frame) const | |
| 32 | + { | |
| 33 | + return reinterpret_cast<uintptr_t>(stack_data()) <= reinterpret_cast<uintptr_t>(frame) && reinterpret_cast<uintptr_t>(frame) < page_limit_addr; | |
| 34 | + } | |
| 35 | + }; | |
| 36 | + | |
| 37 | + struct vm_stack_layout final : public vm_alloc_t | |
| 38 | + { | |
| 39 | + static std::shared_ptr<const type_descriptor_t> type_desc() | |
| 40 | + { | |
| 41 | + static auto td = type_descriptor_t::create(0); | |
| 42 | + return td; | |
| 43 | + } | |
| 44 | + | |
| 45 | + vm_stack_layout(std::size_t stack_page_size) | |
| 46 | + : vm_alloc_t(type_desc()) | |
| 47 | + , stack_page_size{ stack_page_size } | |
| 48 | + , top_frame{ nullptr } | |
| 49 | + , top_page{ nullptr } | |
| 50 | + { | |
| 51 | + new_page(); | |
| 52 | + } | |
| 53 | + | |
| 54 | + ~vm_stack_layout() | |
| 55 | + { | |
| 56 | + assert(top_page != nullptr); | |
| 57 | + free_memory(top_page); | |
| 58 | + } | |
| 59 | + | |
| 60 | + const std::size_t stack_page_size; | |
| 61 | + | |
| 62 | + vm_stack_page *top_page; | |
| 63 | + vm_frame_t *top_frame; // Do not free. This is used as a tracking pointer. | |
| 64 | + | |
| 65 | + // Create a new 'top page' and return stack data address | |
| 66 | + void *new_page() | |
| 67 | + { | |
| 68 | + auto current_stack_page = top_page; | |
| 69 | + top_page = alloc_memory<vm_stack_page>(sizeof(vm_stack_page) + stack_page_size); | |
| 70 | + | |
| 71 | + // Initialize the stack allocation. | |
| 72 | + top_page->prev_page = current_stack_page; | |
| 73 | + | |
| 74 | + // The top frame member is only for tracking and so is initialized to null. | |
| 75 | + top_page->page_top_frame = nullptr; | |
| 76 | + top_page->page_limit_addr = reinterpret_cast<uintptr_t>(top_page)+stack_page_size; | |
| 77 | + | |
| 78 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "alloc: vm stack alloc: %#" PRIxPTR " %#" PRIxPTR "\n", top_page, top_page->page_limit_addr); | |
| 79 | + | |
| 80 | + return top_page->stack_data(); | |
| 81 | + } | |
| 82 | + | |
| 83 | + // Drop the current page and move to the previous one | |
| 84 | + void drop_page() | |
| 85 | + { | |
| 86 | + auto current_stack_page = top_page->prev_page; | |
| 87 | + assert(current_stack_page != nullptr); | |
| 88 | + | |
| 89 | + free_memory(top_page); | |
| 90 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "free: vm stack alloc\n"); | |
| 91 | + | |
| 92 | + // Set the top page | |
| 93 | + top_page = current_stack_page; | |
| 94 | + } | |
| 95 | + }; | |
| 96 | +} | |
| 97 | + | |
| 98 | +vm_frame_t::vm_frame_t(std::shared_ptr<const type_descriptor_t> &td) | |
| 99 | + : frame_type{ std::move(td) } | |
| 100 | +{ | |
| 101 | + assert(frame_type != nullptr); | |
| 102 | + | |
| 103 | + // Initialize the new frame | |
| 104 | + init_memory(*frame_type, base()); | |
| 105 | + | |
| 106 | + prev_pc() = runtime_constants::invalid_program_counter; | |
| 107 | + prev_frame() = nullptr; | |
| 108 | + prev_module_ref() = nullptr; | |
| 109 | + | |
| 110 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 111 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm frame: %#" PRIxPTR " %d\n", this, frame_type->size_in_bytes); | |
| 112 | +} | |
| 113 | + | |
| 114 | +vm_frame_t::~vm_frame_t() | |
| 115 | +{ | |
| 116 | + dec_ref_count_and_free(prev_module_ref()); | |
| 117 | + | |
| 118 | + destroy_memory(*frame_type, base()); | |
| 119 | + | |
| 120 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 121 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm frame: %#" PRIxPTR "\n", this); | |
| 122 | +} | |
| 123 | + | |
| 124 | +void vm_frame_t::copy_frame_contents(const vm_frame_t &other) | |
| 125 | +{ | |
| 126 | + auto this_frame_base = reinterpret_cast<uint8_t *>(base()); | |
| 127 | + auto this_frame_contents = this_frame_base + sizeof(vm_frame_base_alloc_t); | |
| 128 | + | |
| 129 | + auto other_frame_base = reinterpret_cast<uint8_t *>(other.base()); | |
| 130 | + auto other_frame_contents = other_frame_base + sizeof(vm_frame_base_alloc_t); | |
| 131 | + | |
| 132 | + assert(frame_type->size_in_bytes > sizeof(vm_frame_base_alloc_t)); | |
| 133 | + std::memcpy(this_frame_contents, other_frame_contents, frame_type->size_in_bytes - sizeof(vm_frame_base_alloc_t)); | |
| 134 | + | |
| 135 | + inc_ref_count_in_memory(*frame_type, this_frame_base); | |
| 136 | +} | |
| 137 | + | |
| 138 | +pointer_t vm_frame_t::base() const | |
| 139 | +{ | |
| 140 | + // The beginning of the frame data is at the end of the frame struct. | |
| 141 | + return reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(const_cast<vm_frame_t *>(this)) + sizeof(vm_frame_t)); | |
| 142 | +} | |
| 143 | + | |
| 144 | +vm_pc_t &vm_frame_t::prev_pc() const | |
| 145 | +{ | |
| 146 | + return base<vm_frame_base_alloc_t>().p_pc; | |
| 147 | +} | |
| 148 | + | |
| 149 | +vm_frame_t *&vm_frame_t::prev_frame() const | |
| 150 | +{ | |
| 151 | + return base<vm_frame_base_alloc_t>().p_fp; | |
| 152 | +} | |
| 153 | + | |
| 154 | +vm_module_ref_t *&vm_frame_t::prev_module_ref() const | |
| 155 | +{ | |
| 156 | + return base<vm_frame_base_alloc_t>().p_mr; | |
| 157 | +} | |
| 158 | + | |
| 159 | +pointer_t vm_frame_t::fixed_point_register_1() const | |
| 160 | +{ | |
| 161 | + const auto offset = runtime_constants::fixed_point_register_1_frame_offset; | |
| 162 | + assert(offset < frame_type->size_in_bytes); | |
| 163 | + | |
| 164 | + auto p = reinterpret_cast<uint8_t *>(base()) + offset; | |
| 165 | + return reinterpret_cast<pointer_t>(p); | |
| 166 | +} | |
| 167 | + | |
| 168 | +pointer_t vm_frame_t::fixed_point_register_2() const | |
| 169 | +{ | |
| 170 | + const auto offset = runtime_constants::fixed_point_register_2_frame_offset; | |
| 171 | + assert(offset < frame_type->size_in_bytes); | |
| 172 | + | |
| 173 | + auto p = reinterpret_cast<uint8_t *>(base()) + offset; | |
| 174 | + return reinterpret_cast<pointer_t>(p); | |
| 175 | +} | |
| 176 | + | |
| 177 | +vm_stack_t::vm_stack_t(std::size_t stack_extent) | |
| 178 | + : _mem{ std::make_unique<vm_stack_layout>(stack_extent) } | |
| 179 | +{ | |
| 180 | + assert(stack_extent != 0); | |
| 181 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm stack\n"); | |
| 182 | +} | |
| 183 | + | |
| 184 | +vm_stack_t::~vm_stack_t() | |
| 185 | +{ | |
| 186 | + while (nullptr != pop_frame()) | |
| 187 | + { | |
| 188 | + // Pop all frames off the stack | |
| 189 | + } | |
| 190 | + | |
| 191 | + // Free stack memory | |
| 192 | + _mem->release(); | |
| 193 | + _mem.reset(); | |
| 194 | + | |
| 195 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm stack\n"); | |
| 196 | +} | |
| 197 | + | |
| 198 | +vm_frame_t *vm_stack_t::alloc_frame(std::shared_ptr<const type_descriptor_t> frame_type) | |
| 199 | +{ | |
| 200 | + assert(frame_type != nullptr); | |
| 201 | + assert(sizeof(vm_frame_base_alloc_t) < frame_type->size_in_bytes && "Requested frame size less than VM frame base size"); | |
| 202 | + | |
| 203 | + auto layout = dynamic_cast<vm_stack_layout *>(_mem.get()); | |
| 204 | + | |
| 205 | + auto page_top_frame = layout->top_page->page_top_frame; | |
| 206 | + const auto new_frame_size = sizeof(vm_frame_t) + frame_type->size_in_bytes; | |
| 207 | + if (layout->stack_page_size < new_frame_size) | |
| 208 | + throw vm_system_exception{ "Requested stack frame larger than stack page" }; | |
| 209 | + | |
| 210 | + vm_frame_t *new_frame; | |
| 211 | + if (page_top_frame == nullptr) | |
| 212 | + { | |
| 213 | + new_frame = ::new(layout->top_page->stack_data())vm_frame_t{ frame_type }; | |
| 214 | + } | |
| 215 | + else | |
| 216 | + { | |
| 217 | + auto current_page_top_frame_addr = reinterpret_cast<std::size_t>(page_top_frame); | |
| 218 | + auto new_frame_addr_maybe = current_page_top_frame_addr + (sizeof(vm_frame_t) + page_top_frame->frame_type->size_in_bytes); | |
| 219 | + | |
| 220 | + // If the new frame is not going to fit on the current page, create a new page. | |
| 221 | + if ((new_frame_addr_maybe + new_frame_size) >= layout->top_page->page_limit_addr) | |
| 222 | + new_frame_addr_maybe = reinterpret_cast<uintptr_t>(layout->new_page()); | |
| 223 | + | |
| 224 | + new_frame = ::new(reinterpret_cast<void *>(new_frame_addr_maybe))vm_frame_t{ frame_type }; | |
| 225 | + } | |
| 226 | + | |
| 227 | + // Record the top frame on the page | |
| 228 | + layout->top_page->page_top_frame = new_frame; | |
| 229 | + | |
| 230 | + return new_frame; | |
| 231 | +} | |
| 232 | + | |
| 233 | +vm_frame_t *vm_stack_t::push_frame() | |
| 234 | +{ | |
| 235 | + auto layout = dynamic_cast<vm_stack_layout *>(_mem.get()); | |
| 236 | + | |
| 237 | + auto current_top_frame = layout->top_frame; | |
| 238 | + auto new_top_frame = layout->top_page->page_top_frame; | |
| 239 | + new_top_frame->prev_frame() = current_top_frame; | |
| 240 | + | |
| 241 | + layout->top_frame = new_top_frame; | |
| 242 | + | |
| 243 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>()) | |
| 244 | + debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "update: push vm frame: %#" PRIxPTR "\n", new_top_frame); | |
| 245 | + | |
| 246 | + return new_top_frame; | |
| 247 | +} | |
| 248 | + | |
| 249 | +vm_frame_t *vm_stack_t::pop_frame() | |
| 250 | +{ | |
| 251 | + auto layout = dynamic_cast<vm_stack_layout *>(_mem.get()); | |
| 252 | + | |
| 253 | + auto current_frame = layout->top_frame; | |
| 254 | + if (current_frame == nullptr) | |
| 255 | + return nullptr; | |
| 256 | + | |
| 257 | + { | |
| 258 | + // Dispose of the current top frame and update the current frame. | |
| 259 | + auto previous_frame = current_frame; | |
| 260 | + current_frame = previous_frame->prev_frame(); | |
| 261 | + | |
| 262 | + // Call destructor on previous frame since we allocated | |
| 263 | + // the frame on the stack page. | |
| 264 | + previous_frame->~vm_frame_t(); | |
| 265 | + } | |
| 266 | + | |
| 267 | + layout->top_frame = current_frame; | |
| 268 | + layout->top_page->page_top_frame = current_frame; | |
| 269 | + | |
| 270 | + // Check if the stack page is no longer needed. | |
| 271 | + if (current_frame != nullptr) | |
| 272 | + { | |
| 273 | + // Clean up the stack page allocation if the current frame is on a previous page | |
| 274 | + if (!layout->top_page->contains_frame(current_frame)) | |
| 275 | + { | |
| 276 | + layout->drop_page(); | |
| 277 | + | |
| 278 | + // Ensure the current frame is in the new page. | |
| 279 | + assert(layout->top_page->contains_frame(current_frame)); | |
| 280 | + } | |
| 281 | + } | |
| 282 | + | |
| 283 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>()) | |
| 284 | + debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "update: pop vm frame: %#" PRIxPTR "\n", current_frame); | |
| 285 | + | |
| 286 | + return current_frame; | |
| 287 | +} | |
| 288 | + | |
| 289 | +vm_frame_t *vm_stack_t::peek_frame() const | |
| 290 | +{ | |
| 291 | + auto layout = dynamic_cast<vm_stack_layout *>(_mem.get()); | |
| 292 | + return layout->top_frame; | |
| 293 | +} | |
| 294 | + | |
| 295 | +void vm_stack_t::walk_stack(vm_stack_walk_callback_t callback) const | |
| 296 | +{ | |
| 297 | + if (callback == nullptr) | |
| 298 | + return; | |
| 299 | + | |
| 300 | + auto layout = dynamic_cast<vm_stack_layout *>(_mem.get()); | |
| 301 | + auto current_page = layout->top_page; | |
| 302 | + auto current_frame = layout->top_frame; | |
| 303 | + | |
| 304 | + while (current_page != nullptr && current_frame != nullptr) | |
| 305 | + { | |
| 306 | + if (!callback(current_frame, current_page)) | |
| 307 | + break; | |
| 308 | + | |
| 309 | + current_frame = current_frame->prev_frame(); | |
| 310 | + | |
| 311 | + if (!current_page->contains_frame(current_frame)) | |
| 312 | + current_page = current_page->prev_page; | |
| 313 | + } | |
| 314 | +} | |
| @@ -0,0 +1,522 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: string.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cinttypes> | |
| 8 | +#include <runtime.h> | |
| 9 | +#include <utf8.h> | |
| 10 | +#include <vm_memory.h> | |
| 11 | +#include <debug.h> | |
| 12 | +#include <exceptions.h> | |
| 13 | + | |
| 14 | +using namespace disvm; | |
| 15 | +using namespace disvm::runtime; | |
| 16 | + | |
| 17 | +std::shared_ptr<const type_descriptor_t> vm_string_t::type_desc() | |
| 18 | +{ | |
| 19 | + return intrinsic_type_desc::type<vm_string_t>(); | |
| 20 | +} | |
| 21 | + | |
| 22 | +namespace | |
| 23 | +{ | |
| 24 | + // Enumeration for character type compare cases. | |
| 25 | + enum class compare_case | |
| 26 | + { | |
| 27 | + ascii_ascii, | |
| 28 | + ascii_rune, | |
| 29 | + rune_ascii, | |
| 30 | + rune_rune | |
| 31 | + }; | |
| 32 | + | |
| 33 | + // Scenario matrix for compare cases | |
| 34 | + const compare_case scenario_matrix[][2] = | |
| 35 | + { | |
| 36 | + { compare_case::ascii_ascii, compare_case::ascii_rune }, | |
| 37 | + { compare_case::rune_ascii, compare_case::rune_rune }, | |
| 38 | + }; | |
| 39 | + | |
| 40 | + // Case indices | |
| 41 | + const auto ascii_idx = std::size_t{ 0 }; | |
| 42 | + const auto rune_idx = std::size_t{ 1 }; | |
| 43 | + | |
| 44 | + // Compare function for character types. | |
| 45 | + template <typename CharacterType1, typename CharacterType2> | |
| 46 | + int _compare(word_t len, CharacterType1 *s1, CharacterType2 *s2) | |
| 47 | + { | |
| 48 | + for (auto i = word_t{ 0 }; i < len; ++i) | |
| 49 | + { | |
| 50 | + const auto sc1 = static_cast<rune_t>(s1[i]); | |
| 51 | + const auto sc2 = static_cast<rune_t>(s2[i]); | |
| 52 | + | |
| 53 | + if (sc1 != sc2) | |
| 54 | + return (sc1 < sc2) ? (-1) : 1; | |
| 55 | + } | |
| 56 | + | |
| 57 | + return 0; | |
| 58 | + } | |
| 59 | +} | |
| 60 | + | |
| 61 | +int vm_string_t::compare(const vm_string_t *s1, const vm_string_t *s2) | |
| 62 | +{ | |
| 63 | + auto len_1 = word_t{ 0 }; | |
| 64 | + auto scen_1 = ascii_idx; | |
| 65 | + void *str_mem_1 = ""; | |
| 66 | + if (s1 != nullptr) | |
| 67 | + { | |
| 68 | + len_1 = s1->_length; | |
| 69 | + scen_1 = s1->_character_size == sizeof(char) ? ascii_idx : rune_idx; | |
| 70 | + str_mem_1 = (s1->_is_alloc) ? s1->_mem.alloc : s1->_mem.local; | |
| 71 | + } | |
| 72 | + | |
| 73 | + auto len_2 = word_t{ 0 }; | |
| 74 | + auto scen_2 = ascii_idx; | |
| 75 | + void *str_mem_2 = ""; | |
| 76 | + if (s2 != nullptr) | |
| 77 | + { | |
| 78 | + len_2 = s2->_length; | |
| 79 | + scen_2 = s2->_character_size == sizeof(char) ? ascii_idx : rune_idx; | |
| 80 | + str_mem_2 = (s2->_is_alloc) ? s2->_mem.alloc : s2->_mem.local; | |
| 81 | + } | |
| 82 | + | |
| 83 | + // Check the simple compare case between the same string. | |
| 84 | + if (str_mem_1 == str_mem_2) | |
| 85 | + return 0; | |
| 86 | + | |
| 87 | + const auto scenario = scenario_matrix[scen_1][scen_2]; | |
| 88 | + | |
| 89 | + auto result = int{ 0 }; | |
| 90 | + const auto min_length = std::min(len_1, len_2); | |
| 91 | + switch (scenario) | |
| 92 | + { | |
| 93 | + default: | |
| 94 | + assert(false && "Unknown compare scenario"); | |
| 95 | + case compare_case::ascii_ascii: | |
| 96 | + case compare_case::rune_rune: | |
| 97 | + result = std::memcmp(str_mem_1, str_mem_2, min_length); | |
| 98 | + break; | |
| 99 | + case compare_case::ascii_rune: | |
| 100 | + result = _compare(min_length, reinterpret_cast<char *>(str_mem_1), reinterpret_cast<rune_t *>(str_mem_2)); | |
| 101 | + break; | |
| 102 | + case compare_case::rune_ascii: | |
| 103 | + result = _compare(min_length, reinterpret_cast<rune_t *>(str_mem_1), reinterpret_cast<char *>(str_mem_2)); | |
| 104 | + break; | |
| 105 | + } | |
| 106 | + | |
| 107 | + if (result != 0) | |
| 108 | + return result; | |
| 109 | + | |
| 110 | + // Comparing lengths if one string is a proper sub-string of the other. | |
| 111 | + return (len_1 - len_2); | |
| 112 | +} | |
| 113 | + | |
| 114 | +namespace | |
| 115 | +{ | |
| 116 | + template<typename DestCharacterType, typename SourceCharacterType> | |
| 117 | + void copy_characters_to(DestCharacterType *dest, word_t dest_start, const SourceCharacterType *source, word_t source_length) | |
| 118 | + { | |
| 119 | + for (auto i = word_t{ 0 }; i < source_length; ++i) | |
| 120 | + dest[dest_start + i] = static_cast<DestCharacterType>(source[i]); | |
| 121 | + } | |
| 122 | + | |
| 123 | + template<> | |
| 124 | + void copy_characters_to(char *dest, word_t dest_start, const char *source, word_t source_length) | |
| 125 | + { | |
| 126 | + assert(dest != nullptr && source != nullptr); | |
| 127 | + std::memcpy((dest + dest_start), source, source_length); | |
| 128 | + } | |
| 129 | + | |
| 130 | + template<> | |
| 131 | + void copy_characters_to(rune_t *dest, word_t dest_start, const rune_t *source, word_t source_length) | |
| 132 | + { | |
| 133 | + assert(dest != nullptr && source != nullptr); | |
| 134 | + std::memcpy((dest + dest_start), source, (source_length * sizeof(rune_t))); | |
| 135 | + } | |
| 136 | + | |
| 137 | + void combine( | |
| 138 | + bool dest_is_rune, | |
| 139 | + uint8_t *dest, | |
| 140 | + bool source1_is_rune, | |
| 141 | + word_t source1_length, | |
| 142 | + const uint8_t *source1, | |
| 143 | + bool source2_is_rune, | |
| 144 | + word_t source2_length, | |
| 145 | + const uint8_t *source2) | |
| 146 | + { | |
| 147 | + if (dest_is_rune) | |
| 148 | + { | |
| 149 | + auto dest_rune = reinterpret_cast<rune_t *>(dest); | |
| 150 | + if (source1_is_rune) | |
| 151 | + copy_characters_to(dest_rune, 0, reinterpret_cast<const rune_t *>(source1), source1_length); | |
| 152 | + else | |
| 153 | + copy_characters_to(dest_rune, 0, source1, source1_length); | |
| 154 | + | |
| 155 | + if (source2_is_rune) | |
| 156 | + copy_characters_to(dest_rune, source1_length, reinterpret_cast<const rune_t *>(source2), source2_length); | |
| 157 | + else | |
| 158 | + copy_characters_to(dest_rune, source1_length, source2, source2_length); | |
| 159 | + } | |
| 160 | + else | |
| 161 | + { | |
| 162 | + copy_characters_to(dest, 0, source1, source1_length); | |
| 163 | + copy_characters_to(dest, source1_length, source2, source2_length); | |
| 164 | + } | |
| 165 | + } | |
| 166 | + | |
| 167 | + word_t compute_max_length(word_t current_length) | |
| 168 | + { | |
| 169 | + // Length + 1 (null) + space for string expansion | |
| 170 | + return (current_length + 1 + (current_length / 4)); | |
| 171 | + } | |
| 172 | +} | |
| 173 | + | |
| 174 | +vm_string_t::vm_string_t() | |
| 175 | + : vm_alloc_t(vm_string_t::type_desc()) | |
| 176 | + , _character_size{ sizeof(char) } | |
| 177 | + , _encoded_str{ nullptr } | |
| 178 | + , _is_alloc{ false } | |
| 179 | + , _length{ 0 } | |
| 180 | + , _length_max{ sizeof(_mem.local) } | |
| 181 | + , _mem{} | |
| 182 | +{ } | |
| 183 | + | |
| 184 | +vm_string_t::vm_string_t(std::size_t encoded_str_len, const uint8_t *encoded_str) | |
| 185 | + : vm_alloc_t(vm_string_t::type_desc()) | |
| 186 | + , _character_size{ sizeof(char) } | |
| 187 | + , _encoded_str{ nullptr } | |
| 188 | + , _is_alloc{ false } | |
| 189 | + , _length{ 0 } | |
| 190 | + , _length_max{ sizeof(_mem.local) } | |
| 191 | + , _mem{} | |
| 192 | +{ | |
| 193 | + assert((encoded_str_len > 0 && encoded_str != nullptr) || (encoded_str_len == 0 && encoded_str == nullptr)); | |
| 194 | + auto character_count = utf8::count_codepoints(encoded_str, encoded_str_len); | |
| 195 | + _character_size = (encoded_str_len != character_count) ? sizeof(rune_t) : sizeof(char); | |
| 196 | + | |
| 197 | + _length = static_cast<word_t>(character_count); | |
| 198 | + _length_max = sizeof(_mem.local) / _character_size; | |
| 199 | + | |
| 200 | + // Allocate memory based on character count and size | |
| 201 | + auto dest = _mem.local; | |
| 202 | + if ((encoded_str_len + 1) > sizeof(_mem.local)) | |
| 203 | + { | |
| 204 | + // If we are going to allocate, make sure it is larger than needed. | |
| 205 | + _length_max = compute_max_length(_length); | |
| 206 | + _mem.alloc = calloc_memory<uint8_t>(_length_max * _character_size); | |
| 207 | + _is_alloc = true; | |
| 208 | + | |
| 209 | + dest = _mem.alloc; | |
| 210 | + } | |
| 211 | + | |
| 212 | + // Copy over the entire buffer if ASCII or convert and copy if multi-byte | |
| 213 | + if (_character_size == sizeof(char)) | |
| 214 | + { | |
| 215 | + std::memcpy(dest, encoded_str, encoded_str_len); | |
| 216 | + } | |
| 217 | + else | |
| 218 | + { | |
| 219 | + auto rune_dest = reinterpret_cast<rune_t *>(dest); | |
| 220 | + for (auto i = word_t{ 0 }; i < _length; ++i) | |
| 221 | + encoded_str += utf8::decode(encoded_str, rune_dest[i]); | |
| 222 | + } | |
| 223 | + | |
| 224 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 225 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm string: %d %d >>%s<<\n", _length, (_length_max * _character_size), dest); | |
| 226 | +} | |
| 227 | + | |
| 228 | +vm_string_t::vm_string_t(const vm_string_t &s1, const vm_string_t &s2) | |
| 229 | + : vm_alloc_t(vm_string_t::type_desc()) | |
| 230 | + , _character_size{ std::max(s1._character_size, s2._character_size) } | |
| 231 | + , _encoded_str{ nullptr } | |
| 232 | + , _is_alloc{ true } | |
| 233 | + , _length{ s1._length + s2._length } | |
| 234 | + , _length_max{ s1._length + s2._length } | |
| 235 | + , _mem{} | |
| 236 | +{ | |
| 237 | + _mem.alloc = calloc_memory<uint8_t>(_length_max * _character_size); | |
| 238 | + | |
| 239 | + const auto s1_data = (s1._is_alloc) ? s1._mem.alloc : s1._mem.local; | |
| 240 | + const auto s2_data = (s2._is_alloc) ? s2._mem.alloc : s2._mem.local; | |
| 241 | + | |
| 242 | + // Combine the two strings | |
| 243 | + combine( | |
| 244 | + _character_size == sizeof(rune_t), | |
| 245 | + _mem.alloc, | |
| 246 | + s1._character_size == sizeof(rune_t), | |
| 247 | + s1._length, | |
| 248 | + s1_data, | |
| 249 | + s2._character_size == sizeof(rune_t), | |
| 250 | + s2._length, | |
| 251 | + s2_data); | |
| 252 | +} | |
| 253 | + | |
| 254 | +vm_string_t::vm_string_t(const vm_string_t &other, word_t begin_index, word_t end_index) | |
| 255 | + : vm_alloc_t(other.alloc_type) | |
| 256 | + , _character_size{ other._character_size } | |
| 257 | + , _encoded_str{ nullptr } | |
| 258 | + , _is_alloc{ other._is_alloc } | |
| 259 | + , _length{ end_index - begin_index } | |
| 260 | + , _length_max{ compute_max_length(end_index - begin_index) } | |
| 261 | + , _mem{} | |
| 262 | +{ | |
| 263 | + if (end_index <= begin_index) | |
| 264 | + throw out_of_range_memory{}; | |
| 265 | + | |
| 266 | + if (begin_index < 0) | |
| 267 | + throw out_of_range_memory{ 0, other._length, begin_index }; | |
| 268 | + | |
| 269 | + if (other._length < end_index) | |
| 270 | + throw out_of_range_memory{ 0, other._length, end_index }; | |
| 271 | + | |
| 272 | + // Allocate memory based on character count and size | |
| 273 | + auto src = other._mem.local; | |
| 274 | + auto dest = _mem.local; | |
| 275 | + const auto length_in_bytes = _length * _character_size; | |
| 276 | + if (_is_alloc) | |
| 277 | + { | |
| 278 | + src = other._mem.alloc; | |
| 279 | + _mem.alloc = calloc_memory<uint8_t>(_length_max); | |
| 280 | + dest = _mem.alloc; | |
| 281 | + } | |
| 282 | + | |
| 283 | + // Copy over string content starting at the correct index (char vs. rune_t) | |
| 284 | + ::memcpy(dest, (src + (begin_index * _character_size)), length_in_bytes); | |
| 285 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "copy: vm string: %#" PRIxPTR " %d %d %#" PRIxPTR "\n", &other, begin_index, end_index, this); | |
| 286 | +} | |
| 287 | + | |
| 288 | +vm_string_t::~vm_string_t() | |
| 289 | +{ | |
| 290 | + // Free memory and string buffer | |
| 291 | + if (_is_alloc) | |
| 292 | + { | |
| 293 | + if (static_cast<void *>(_mem.alloc) != static_cast<void *>(_encoded_str)) | |
| 294 | + free_memory(_encoded_str); | |
| 295 | + | |
| 296 | + free_memory(_mem.alloc); | |
| 297 | + } | |
| 298 | + else | |
| 299 | + { | |
| 300 | + if (static_cast<void *>(_mem.local) != static_cast<void *>(_encoded_str)) | |
| 301 | + free_memory(_encoded_str); | |
| 302 | + } | |
| 303 | + | |
| 304 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm string\n"); | |
| 305 | + | |
| 306 | + debug::assign_debug_pointer(&_mem.alloc); | |
| 307 | + debug::assign_debug_pointer(&_encoded_str); | |
| 308 | +} | |
| 309 | + | |
| 310 | +vm_string_t &vm_string_t::append(const vm_string_t &other) | |
| 311 | +{ | |
| 312 | + // This function does not adhere to the immutable string contract. | |
| 313 | + // Therefore, it is unsafe to call this function if there is more | |
| 314 | + // than 1 reference to this string instance. | |
| 315 | + assert(get_ref_count() <= 1); | |
| 316 | + | |
| 317 | + // Check if the new string has any length at all. | |
| 318 | + const auto other_length = other._length; | |
| 319 | + if (other_length == 0) | |
| 320 | + return *this; | |
| 321 | + | |
| 322 | + const auto other_is_rune = other._character_size == sizeof(rune_t); | |
| 323 | + const auto other_source = (other._is_alloc) ? other._mem.alloc : other._mem.local; | |
| 324 | + | |
| 325 | + const auto local_is_rune = _character_size == sizeof(rune_t); | |
| 326 | + const auto local_length = _length; | |
| 327 | + const auto local_source = (_is_alloc) ? _mem.alloc : _mem.local; | |
| 328 | + | |
| 329 | + // Release the encoded string for this instance. | |
| 330 | + { | |
| 331 | + std::lock_guard<std::mutex> lock{ _encoded_str_lock }; | |
| 332 | + if (_encoded_str != reinterpret_cast<char*>(local_source)) | |
| 333 | + { | |
| 334 | + free_memory(_encoded_str); | |
| 335 | + _encoded_str = nullptr; | |
| 336 | + } | |
| 337 | + } | |
| 338 | + | |
| 339 | + // Define the values for the appended string | |
| 340 | + const auto new_length = local_length + other_length; | |
| 341 | + const auto new_length_max = (new_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(new_length); | |
| 342 | + const auto new_is_rune = local_is_rune || other_is_rune; | |
| 343 | + uint8_t *new_source = nullptr; | |
| 344 | + | |
| 345 | + // Check if this string and the supplied string have the same character size. | |
| 346 | + if (new_is_rune) | |
| 347 | + new_source = calloc_memory<uint8_t>(new_length_max * sizeof(rune_t)); | |
| 348 | + else | |
| 349 | + new_source = calloc_memory<uint8_t>(new_length_max); | |
| 350 | + | |
| 351 | + // Combine the two string sources | |
| 352 | + combine( | |
| 353 | + new_is_rune, | |
| 354 | + new_source, | |
| 355 | + local_is_rune, | |
| 356 | + local_length, | |
| 357 | + local_source, | |
| 358 | + other_is_rune, | |
| 359 | + other_length, | |
| 360 | + other_source); | |
| 361 | + | |
| 362 | + _length = new_length; | |
| 363 | + _length_max = new_length_max; | |
| 364 | + | |
| 365 | + // Free the local allocation | |
| 366 | + if (_is_alloc) | |
| 367 | + { | |
| 368 | + free_memory(local_source); | |
| 369 | + _mem.alloc = nullptr; | |
| 370 | + } | |
| 371 | + | |
| 372 | + _mem.alloc = new_source; | |
| 373 | + _is_alloc = true; | |
| 374 | + _character_size = new_is_rune ? sizeof(rune_t) : sizeof(char); | |
| 375 | + | |
| 376 | + return *this; | |
| 377 | +} | |
| 378 | + | |
| 379 | +word_t vm_string_t::get_length() const | |
| 380 | +{ | |
| 381 | + return _length; | |
| 382 | +} | |
| 383 | + | |
| 384 | +rune_t vm_string_t::get_rune(word_t index) const | |
| 385 | +{ | |
| 386 | + if (index < 0 && _length <= index) | |
| 387 | + throw out_of_range_memory{ 0, _length - 1, index }; | |
| 388 | + | |
| 389 | + // Determine if local or allocated memory | |
| 390 | + auto source = reinterpret_cast<const char *>(_mem.local); | |
| 391 | + if (_is_alloc) | |
| 392 | + source = reinterpret_cast<const char *>(_mem.alloc); | |
| 393 | + | |
| 394 | + // Check if multi-byte or ASCII | |
| 395 | + if (_character_size != sizeof(char)) | |
| 396 | + return reinterpret_cast<const rune_t *>(source)[index]; | |
| 397 | + else | |
| 398 | + return static_cast<const rune_t>(source[index]); | |
| 399 | +} | |
| 400 | + | |
| 401 | +void vm_string_t::set_rune(word_t index, rune_t value) | |
| 402 | +{ | |
| 403 | + // This function does not adhere to the immutable string contract. | |
| 404 | + // Therefore, it is unsafe to call this function if there is more | |
| 405 | + // than 1 reference to this string instance. | |
| 406 | + assert(get_ref_count() <= 1); | |
| 407 | + | |
| 408 | + if (value > utf8::constants::max_supported_codepoint) | |
| 409 | + throw invalid_utf8{}; | |
| 410 | + | |
| 411 | + // Index equal to length is allowed to grow the string | |
| 412 | + if (index < 0 && _length <= index) | |
| 413 | + throw out_of_range_memory{ 0, _length, index }; | |
| 414 | + | |
| 415 | + // Determine if local or allocated memory | |
| 416 | + auto source = static_cast<uint8_t *>(_mem.local); | |
| 417 | + if (_is_alloc) | |
| 418 | + { | |
| 419 | + source = static_cast<uint8_t *>(_mem.alloc); | |
| 420 | + | |
| 421 | + // Since we are setting a rune, the string value should be freed. | |
| 422 | + if (_encoded_str != nullptr) | |
| 423 | + free_memory(_encoded_str); | |
| 424 | + } | |
| 425 | + | |
| 426 | + // Reset the string so it can be regenerated. | |
| 427 | + _encoded_str = nullptr; | |
| 428 | + | |
| 429 | + const auto new_character_size = (value > utf8::constants::max_codepoint_ascii) ? sizeof(rune_t) : sizeof(char); | |
| 430 | + assert(new_character_size < std::numeric_limits<uint8_t>::max()); | |
| 431 | + | |
| 432 | + if (new_character_size > _character_size) // Expand the string if the new character is non-ASCII. | |
| 433 | + { | |
| 434 | + _character_size = static_cast<uint8_t>(new_character_size); | |
| 435 | + assert(_character_size == sizeof(rune_t)); | |
| 436 | + | |
| 437 | + _length_max = (_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(_length); | |
| 438 | + auto new_alloc = calloc_memory<rune_t>(_length_max * _character_size); | |
| 439 | + | |
| 440 | + // Copy source into the new alloc. | |
| 441 | + copy_characters_to(new_alloc, 0, source, _length); | |
| 442 | + | |
| 443 | + // Free memory, if allocated | |
| 444 | + if (_is_alloc) | |
| 445 | + free_memory(source); | |
| 446 | + | |
| 447 | + debug::assign_debug_memory(&_mem, sizeof(_mem)); | |
| 448 | + | |
| 449 | + _is_alloc = true; | |
| 450 | + _mem.alloc = reinterpret_cast<uint8_t *>(new_alloc); | |
| 451 | + source = _mem.alloc; | |
| 452 | + } | |
| 453 | + else if (static_cast<word_t>(_length_max) <= index) // Check if string can accomodate the new character. | |
| 454 | + { | |
| 455 | + _length_max = (_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(_length); | |
| 456 | + auto new_alloc = calloc_memory<uint8_t>(_length_max * _character_size); | |
| 457 | + ::memcpy(new_alloc, source, _length * _character_size); | |
| 458 | + | |
| 459 | + // Free memory, if allocated | |
| 460 | + if (_is_alloc) | |
| 461 | + free_memory(source); | |
| 462 | + | |
| 463 | + debug::assign_debug_memory(&_mem, sizeof(_mem)); | |
| 464 | + | |
| 465 | + _is_alloc = true; | |
| 466 | + _mem.alloc = new_alloc; | |
| 467 | + source = _mem.alloc; | |
| 468 | + } | |
| 469 | + | |
| 470 | + // Check if multi-byte or ASCII. | |
| 471 | + if (_character_size != sizeof(char)) | |
| 472 | + reinterpret_cast<rune_t *>(source)[index] = value; | |
| 473 | + else | |
| 474 | + source[index] = static_cast<uint8_t>(value & utf8::constants::max_codepoint_ascii); | |
| 475 | + | |
| 476 | + // Increment the length, if the index matches the length. | |
| 477 | + if (_length == index) | |
| 478 | + _length++; | |
| 479 | +} | |
| 480 | + | |
| 481 | +int vm_string_t::compare_to(const vm_string_t *s) const | |
| 482 | +{ | |
| 483 | + return vm_string_t::compare(this, s); | |
| 484 | +} | |
| 485 | + | |
| 486 | +const char *vm_string_t::str() const | |
| 487 | +{ | |
| 488 | + if (_encoded_str != nullptr) | |
| 489 | + return _encoded_str; | |
| 490 | + | |
| 491 | + { | |
| 492 | + // [PERF] Use a spin-lock instead of a mutex here. | |
| 493 | + std::lock_guard<std::mutex> lock{ _encoded_str_lock }; | |
| 494 | + | |
| 495 | + if (_encoded_str != nullptr) | |
| 496 | + return _encoded_str; | |
| 497 | + | |
| 498 | + // If the string is ASCII then just return the memory | |
| 499 | + if (_character_size == sizeof(char)) | |
| 500 | + { | |
| 501 | + _encoded_str = (_is_alloc) ? reinterpret_cast<char *>(_mem.alloc) : reinterpret_cast<char *>(_mem.local); | |
| 502 | + return _encoded_str; | |
| 503 | + } | |
| 504 | + | |
| 505 | + const auto rune_str = (_is_alloc) ? reinterpret_cast<rune_t *>(_mem.alloc) : reinterpret_cast<rune_t *>(_mem.local); | |
| 506 | + const auto str_len = (_length * _character_size) + 1; // Plus 1 for a null terminator | |
| 507 | + auto encoded_str_local = alloc_memory<char>(str_len); | |
| 508 | + | |
| 509 | + auto s = reinterpret_cast<uint8_t *>(encoded_str_local); | |
| 510 | + for (auto i = word_t{ 0 }; i < _length; ++i) | |
| 511 | + { | |
| 512 | + auto curr_rune = rune_str[i]; | |
| 513 | + s = utf8::encode(curr_rune, s); | |
| 514 | + } | |
| 515 | + | |
| 516 | + // Ensure the final value is null. | |
| 517 | + *s = 0; | |
| 518 | + | |
| 519 | + _encoded_str = encoded_str_local; | |
| 520 | + return _encoded_str; | |
| 521 | + } | |
| 522 | +} | |
| @@ -0,0 +1,478 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: Sysmod.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <array> | |
| 8 | +#include <chrono> | |
| 9 | +#include <thread> | |
| 10 | +#include <exceptions.h> | |
| 11 | +#include <utf8.h> | |
| 12 | +#include "Sysmod.h" | |
| 13 | +#include "sys_utils.h" | |
| 14 | + | |
| 15 | +using namespace disvm; | |
| 16 | +using namespace disvm::runtime; | |
| 17 | + | |
| 18 | +namespace | |
| 19 | +{ | |
| 20 | + disvm::runtime::builtin::vm_runtab_t Sysmodtab[] = { | |
| 21 | + "announce",0xb7c4ac0,Sys_announce,40,2,{ 0x0,0x80, }, | |
| 22 | + "aprint",0x77442d46,Sys_aprint,0,0,{ 0 }, | |
| 23 | + "bind",0x66326d91,Sys_bind,48,2,{ 0x0,0xc0, }, | |
| 24 | + "byte2char",0x3d6094f9,Sys_byte2char,40,2,{ 0x0,0x80, }, | |
| 25 | + "char2byte",0x2ba5ab41,Sys_char2byte,48,2,{ 0x0,0x40, }, | |
| 26 | + "chdir",0xc6935858,Sys_chdir,40,2,{ 0x0,0x80, }, | |
| 27 | + "create",0x54db77d9,Sys_create,48,2,{ 0x0,0x80, }, | |
| 28 | + "dial",0x29e90174,Sys_dial,40,2,{ 0x0,0xc0, }, | |
| 29 | + "dirread",0x72210d71,Sys_dirread,40,2,{ 0x0,0x80, }, | |
| 30 | + "dup",0x6584767b,Sys_dup,40,0,{ 0 }, | |
| 31 | + "export",0x6fc6dc03,Sys_export,48,2,{ 0x0,0xc0, }, | |
| 32 | + "fauth",0x20ccc34b,Sys_fauth,40,2,{ 0x0,0xc0, }, | |
| 33 | + "fd2path",0x749c6042,Sys_fd2path,40,2,{ 0x0,0x80, }, | |
| 34 | + "fildes",0x1478f993,Sys_fildes,40,0,{ 0 }, | |
| 35 | + "file2chan",0x9f34d686,Sys_file2chan,40,2,{ 0x0,0xc0, }, | |
| 36 | + "fprint",0xf46486c8,Sys_fprint,0,0,{ 0 }, | |
| 37 | + "fstat",0xda4499c2,Sys_fstat,40,2,{ 0x0,0x80, }, | |
| 38 | + "fversion",0xfe9c0a06,Sys_fversion,48,2,{ 0x0,0xa0, }, | |
| 39 | + "fwstat",0x50a6c7e0,Sys_fwstat,104,2,{ 0x0,0xbc, }, | |
| 40 | + "iounit",0x5583b730,Sys_iounit,40,2,{ 0x0,0x80, }, | |
| 41 | + "listen",0xb97416e0,Sys_listen,48,2,{ 0x0,0xe0, }, | |
| 42 | + "millisec",0x616977e8,Sys_millisec,32,0,{ 0 }, | |
| 43 | + "mount",0x74c17b3a,Sys_mount,56,2,{ 0x0,0xe8, }, | |
| 44 | + "open",0x8f477f99,Sys_open,40,2,{ 0x0,0x80, }, | |
| 45 | + "pctl",0x5df27fb,Sys_pctl,40,2,{ 0x0,0x40, }, | |
| 46 | + "pipe",0x1f2c52ea,Sys_pipe,40,2,{ 0x0,0x80, }, | |
| 47 | + "pread",0x9d8aac6,Sys_pread,56,2,{ 0x0,0xc0, }, | |
| 48 | + "print",0xac849033,Sys_print,0,0,{ 0 }, | |
| 49 | + "pwrite",0x9d8aac6,Sys_pwrite,56,2,{ 0x0,0xc0, }, | |
| 50 | + "read",0x7cfef557,Sys_read,48,2,{ 0x0,0xc0, }, | |
| 51 | + "readn",0x7cfef557,Sys_readn,48,2,{ 0x0,0xc0, }, | |
| 52 | + "remove",0xc6935858,Sys_remove,40,2,{ 0x0,0x80, }, | |
| 53 | + "seek",0xaeccaddb,Sys_seek,56,2,{ 0x0,0x80, }, | |
| 54 | + "sleep",0xe67bf126,Sys_sleep,40,0,{ 0 }, | |
| 55 | + "sprint",0x4c0624b6,Sys_sprint,0,0,{ 0 }, | |
| 56 | + "stat",0x319328dd,Sys_stat,40,2,{ 0x0,0x80, }, | |
| 57 | + "stream",0xb9e8f9ea,Sys_stream,48,2,{ 0x0,0xc0, }, | |
| 58 | + "tokenize",0x57338f20,Sys_tokenize,40,2,{ 0x0,0xc0, }, | |
| 59 | + "unmount",0x21e337e3,Sys_unmount,40,2,{ 0x0,0xc0, }, | |
| 60 | + "utfbytes",0x1d4a1f4,Sys_utfbytes,40,2,{ 0x0,0x80, }, | |
| 61 | + "werrstr",0xc6935858,Sys_werrstr,40,2,{ 0x0,0x80, }, | |
| 62 | + "write",0x7cfef557,Sys_write,48,2,{ 0x0,0xc0, }, | |
| 63 | + "wstat",0x56b02096,Sys_wstat,104,2,{ 0x0,0xbc, }, | |
| 64 | + 0 | |
| 65 | + }; | |
| 66 | + | |
| 67 | + const word_t Sysmodlen = 43; | |
| 68 | +} | |
| 69 | + | |
| 70 | +std::shared_ptr<const type_descriptor_t> T_Qid; | |
| 71 | +std::shared_ptr<const type_descriptor_t> T_Dir; | |
| 72 | +std::shared_ptr<const type_descriptor_t> T_FD; | |
| 73 | +std::shared_ptr<const type_descriptor_t> T_Connection; | |
| 74 | +std::shared_ptr<const type_descriptor_t> T_FileIO; | |
| 75 | + | |
| 76 | +void | |
| 77 | +Sysmodinit(void) | |
| 78 | +{ | |
| 79 | + builtin::register_module_exports(Sys_PATH, Sysmodlen, Sysmodtab); | |
| 80 | + T_Qid = type_descriptor_t::create(sizeof(Sys_Qid), sizeof(Sys_Qid_map), Sys_Qid_map); | |
| 81 | + T_Dir = type_descriptor_t::create(sizeof(Sys_Dir), sizeof(Sys_Dir_map), Sys_Dir_map); | |
| 82 | + T_FD = type_descriptor_t::create(sizeof(Sys_FD), sizeof(Sys_FD_map), Sys_FD_map); | |
| 83 | + T_Connection = type_descriptor_t::create(sizeof(Sys_Connection), sizeof(Sys_Connection_map), Sys_Connection_map); | |
| 84 | + T_FileIO = type_descriptor_t::create(sizeof(Sys_FileIO), sizeof(Sys_FileIO_map), Sys_FileIO_map); | |
| 85 | +} | |
| 86 | + | |
| 87 | +void | |
| 88 | +Sys_announce(vm_registers_t &r, vm_t &vm) | |
| 89 | +{ | |
| 90 | + auto &fp = r.stack.peek_frame()->base<F_Sys_announce>(); | |
| 91 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 92 | +} | |
| 93 | + | |
| 94 | +void | |
| 95 | +Sys_aprint(vm_registers_t &r, vm_t &vm) | |
| 96 | +{ | |
| 97 | + auto &fp = r.stack.peek_frame()->base<F_Sys_aprint>(); | |
| 98 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 99 | +} | |
| 100 | + | |
| 101 | +void | |
| 102 | +Sys_bind(vm_registers_t &r, vm_t &vm) | |
| 103 | +{ | |
| 104 | + auto &fp = r.stack.peek_frame()->base<F_Sys_bind>(); | |
| 105 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 106 | +} | |
| 107 | + | |
| 108 | +void | |
| 109 | +Sys_byte2char(vm_registers_t &r, vm_t &vm) | |
| 110 | +{ | |
| 111 | + auto &fp = r.stack.peek_frame()->base<F_Sys_byte2char>(); | |
| 112 | + | |
| 113 | + auto buffer = vm_alloc_t::from_allocation<vm_array_t>(fp.buf); | |
| 114 | + assert(buffer->get_element_type()->size_in_bytes == intrinsic_type_desc::type<byte_t>()->size_in_bytes); | |
| 115 | + | |
| 116 | + const auto n = fp.n; | |
| 117 | + if (buffer == nullptr || n >= buffer->get_length()) | |
| 118 | + throw out_of_range_memory{}; | |
| 119 | + | |
| 120 | + // Initialize for failure. | |
| 121 | + fp.ret->t0 = Sys_UTFerror; | |
| 122 | + fp.ret->t1 = 0; | |
| 123 | + fp.ret->t2 = 0; | |
| 124 | + | |
| 125 | + auto state = utf8::decode_state::accept; | |
| 126 | + auto result = rune_t{}; | |
| 127 | + const auto buffer_len = buffer->get_length(); | |
| 128 | + for (auto i = n; i < buffer_len; ++i) | |
| 129 | + { | |
| 130 | + auto b = buffer->at<byte_t>(i); | |
| 131 | + state = utf8::decode_step(state, result, b); | |
| 132 | + if (state == utf8::decode_state::accept) | |
| 133 | + { | |
| 134 | + fp.ret->t0 = result; | |
| 135 | + fp.ret->t1 = (i - n) + 1; // Number of bytes consumed | |
| 136 | + fp.ret->t2 = 1; | |
| 137 | + return; | |
| 138 | + } | |
| 139 | + else if (state == utf8::decode_state::reject) | |
| 140 | + { | |
| 141 | + fp.ret->t1 = 1; | |
| 142 | + return; | |
| 143 | + } | |
| 144 | + } | |
| 145 | +} | |
| 146 | + | |
| 147 | +void | |
| 148 | +Sys_char2byte(vm_registers_t &r, vm_t &vm) | |
| 149 | +{ | |
| 150 | + auto &fp = r.stack.peek_frame()->base<F_Sys_char2byte>(); | |
| 151 | + | |
| 152 | + auto buffer = vm_alloc_t::from_allocation<vm_array_t>(fp.buf); | |
| 153 | + assert(buffer->get_element_type()->size_in_bytes == intrinsic_type_desc::type<byte_t>()->size_in_bytes); | |
| 154 | + | |
| 155 | + const auto n = fp.n; | |
| 156 | + if (buffer == nullptr || n >= buffer->get_length()) | |
| 157 | + throw out_of_range_memory{}; | |
| 158 | + | |
| 159 | + const auto rune = static_cast<rune_t>(fp.c); | |
| 160 | + | |
| 161 | + static_assert(Sys_UTFmax == sizeof(rune_t), "UTF max should be same size as rune"); | |
| 162 | + uint8_t buffer_local[sizeof(Sys_UTFmax)]; | |
| 163 | + auto begin = buffer_local; | |
| 164 | + | |
| 165 | + auto end = utf8::encode(rune, begin); | |
| 166 | + const auto count = static_cast<word_t>(end - begin); | |
| 167 | + | |
| 168 | + // Initialize for failure. | |
| 169 | + *fp.ret = 0; | |
| 170 | + | |
| 171 | + const auto final_index = n + count; | |
| 172 | + if (final_index > buffer->get_length()) | |
| 173 | + return; | |
| 174 | + | |
| 175 | + // Insert the bytes into the buffer | |
| 176 | + for (auto i = word_t{ n }; i < final_index; ++i) | |
| 177 | + { | |
| 178 | + buffer->at<uint8_t>(i) = *begin; | |
| 179 | + begin++; | |
| 180 | + } | |
| 181 | + | |
| 182 | + *fp.ret = count; | |
| 183 | +} | |
| 184 | + | |
| 185 | +void | |
| 186 | +Sys_chdir(vm_registers_t &r, vm_t &vm) | |
| 187 | +{ | |
| 188 | + auto &fp = r.stack.peek_frame()->base<F_Sys_chdir>(); | |
| 189 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 190 | +} | |
| 191 | + | |
| 192 | +void | |
| 193 | +Sys_create(vm_registers_t &r, vm_t &vm) | |
| 194 | +{ | |
| 195 | + auto &fp = r.stack.peek_frame()->base<F_Sys_create>(); | |
| 196 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 197 | +} | |
| 198 | + | |
| 199 | +void | |
| 200 | +Sys_dial(vm_registers_t &r, vm_t &vm) | |
| 201 | +{ | |
| 202 | + auto &fp = r.stack.peek_frame()->base<F_Sys_dial>(); | |
| 203 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 204 | +} | |
| 205 | + | |
| 206 | +void | |
| 207 | +Sys_dirread(vm_registers_t &r, vm_t &vm) | |
| 208 | +{ | |
| 209 | + auto &fp = r.stack.peek_frame()->base<F_Sys_dirread>(); | |
| 210 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 211 | +} | |
| 212 | + | |
| 213 | +void | |
| 214 | +Sys_dup(vm_registers_t &r, vm_t &vm) | |
| 215 | +{ | |
| 216 | + auto &fp = r.stack.peek_frame()->base<F_Sys_dup>(); | |
| 217 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 218 | +} | |
| 219 | + | |
| 220 | +void | |
| 221 | +Sys_export(vm_registers_t &r, vm_t &vm) | |
| 222 | +{ | |
| 223 | + auto &fp = r.stack.peek_frame()->base<F_Sys_export>(); | |
| 224 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 225 | +} | |
| 226 | + | |
| 227 | +void | |
| 228 | +Sys_fauth(vm_registers_t &r, vm_t &vm) | |
| 229 | +{ | |
| 230 | + auto &fp = r.stack.peek_frame()->base<F_Sys_fauth>(); | |
| 231 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 232 | +} | |
| 233 | + | |
| 234 | +void | |
| 235 | +Sys_fd2path(vm_registers_t &r, vm_t &vm) | |
| 236 | +{ | |
| 237 | + auto &fp = r.stack.peek_frame()->base<F_Sys_fd2path>(); | |
| 238 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 239 | +} | |
| 240 | + | |
| 241 | +void | |
| 242 | +Sys_fildes(vm_registers_t &r, vm_t &vm) | |
| 243 | +{ | |
| 244 | + auto &fp = r.stack.peek_frame()->base<F_Sys_fildes>(); | |
| 245 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 246 | +} | |
| 247 | + | |
| 248 | +void | |
| 249 | +Sys_file2chan(vm_registers_t &r, vm_t &vm) | |
| 250 | +{ | |
| 251 | + auto &fp = r.stack.peek_frame()->base<F_Sys_file2chan>(); | |
| 252 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 253 | +} | |
| 254 | + | |
| 255 | +void | |
| 256 | +Sys_fprint(vm_registers_t &r, vm_t &vm) | |
| 257 | +{ | |
| 258 | + auto &fp = r.stack.peek_frame()->base<F_Sys_fprint>(); | |
| 259 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 260 | +} | |
| 261 | + | |
| 262 | +void | |
| 263 | +Sys_fstat(vm_registers_t &r, vm_t &vm) | |
| 264 | +{ | |
| 265 | + auto &fp = r.stack.peek_frame()->base<F_Sys_fstat>(); | |
| 266 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 267 | +} | |
| 268 | + | |
| 269 | +void | |
| 270 | +Sys_fversion(vm_registers_t &r, vm_t &vm) | |
| 271 | +{ | |
| 272 | + auto &fp = r.stack.peek_frame()->base<F_Sys_fversion>(); | |
| 273 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 274 | +} | |
| 275 | + | |
| 276 | +void | |
| 277 | +Sys_fwstat(vm_registers_t &r, vm_t &vm) | |
| 278 | +{ | |
| 279 | + auto &fp = r.stack.peek_frame()->base<F_Sys_fwstat>(); | |
| 280 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 281 | +} | |
| 282 | + | |
| 283 | +void | |
| 284 | +Sys_iounit(vm_registers_t &r, vm_t &vm) | |
| 285 | +{ | |
| 286 | + auto &fp = r.stack.peek_frame()->base<F_Sys_iounit>(); | |
| 287 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 288 | +} | |
| 289 | + | |
| 290 | +void | |
| 291 | +Sys_listen(vm_registers_t &r, vm_t &vm) | |
| 292 | +{ | |
| 293 | + auto &fp = r.stack.peek_frame()->base<F_Sys_listen>(); | |
| 294 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 295 | +} | |
| 296 | + | |
| 297 | +void | |
| 298 | +Sys_millisec(vm_registers_t &r, vm_t &vm) | |
| 299 | +{ | |
| 300 | + const auto current_time = std::chrono::steady_clock::now(); | |
| 301 | + const auto current_time_in_ms = std::chrono::duration_cast<std::chrono::milliseconds>(current_time.time_since_epoch()); | |
| 302 | + | |
| 303 | + auto &fp = r.stack.peek_frame()->base<F_Sys_millisec>(); | |
| 304 | + *fp.ret = static_cast<word_t>(current_time_in_ms.count()); | |
| 305 | +} | |
| 306 | + | |
| 307 | +void | |
| 308 | +Sys_mount(vm_registers_t &r, vm_t &vm) | |
| 309 | +{ | |
| 310 | + auto &fp = r.stack.peek_frame()->base<F_Sys_mount>(); | |
| 311 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 312 | +} | |
| 313 | + | |
| 314 | +void | |
| 315 | +Sys_open(vm_registers_t &r, vm_t &vm) | |
| 316 | +{ | |
| 317 | + auto &fp = r.stack.peek_frame()->base<F_Sys_open>(); | |
| 318 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 319 | +} | |
| 320 | + | |
| 321 | +void | |
| 322 | +Sys_pctl(vm_registers_t &r, vm_t &vm) | |
| 323 | +{ | |
| 324 | + auto &fp = r.stack.peek_frame()->base<F_Sys_pctl>(); | |
| 325 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 326 | +} | |
| 327 | + | |
| 328 | +void | |
| 329 | +Sys_pipe(vm_registers_t &r, vm_t &vm) | |
| 330 | +{ | |
| 331 | + auto &fp = r.stack.peek_frame()->base<F_Sys_pipe>(); | |
| 332 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 333 | +} | |
| 334 | + | |
| 335 | +void | |
| 336 | +Sys_pread(vm_registers_t &r, vm_t &vm) | |
| 337 | +{ | |
| 338 | + auto &fp = r.stack.peek_frame()->base<F_Sys_pread>(); | |
| 339 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 340 | +} | |
| 341 | + | |
| 342 | +void | |
| 343 | +Sys_print(vm_registers_t &r, vm_t &vm) | |
| 344 | +{ | |
| 345 | + auto &fp = r.stack.peek_frame()->base<F_Sys_print>(); | |
| 346 | + auto str = vm_alloc_t::from_allocation<vm_string_t>(fp.s); | |
| 347 | + | |
| 348 | + if (str == nullptr) | |
| 349 | + throw dereference_nil{ "Print string" }; | |
| 350 | + | |
| 351 | + auto static_buffer = std::array<char, 1024>{}; | |
| 352 | + auto written = printf_to_buffer(*str, &fp.vargs, static_buffer.size(), static_buffer.data()); | |
| 353 | + if (written >= 0) | |
| 354 | + { | |
| 355 | + std::printf(static_buffer.data()); | |
| 356 | + } | |
| 357 | + else | |
| 358 | + { | |
| 359 | + auto dynamic_buffer = std::vector<char>(static_buffer.size() * 2); | |
| 360 | + written = printf_to_dynamic_buffer(*str, &fp.vargs, dynamic_buffer); | |
| 361 | + std::printf(dynamic_buffer.data()); | |
| 362 | + } | |
| 363 | + | |
| 364 | + *fp.ret = written; | |
| 365 | +} | |
| 366 | + | |
| 367 | +void | |
| 368 | +Sys_pwrite(vm_registers_t &r, vm_t &vm) | |
| 369 | +{ | |
| 370 | + auto &fp = r.stack.peek_frame()->base<F_Sys_pwrite>(); | |
| 371 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 372 | +} | |
| 373 | + | |
| 374 | +void | |
| 375 | +Sys_read(vm_registers_t &r, vm_t &vm) | |
| 376 | +{ | |
| 377 | + auto &fp = r.stack.peek_frame()->base<F_Sys_read>(); | |
| 378 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 379 | +} | |
| 380 | + | |
| 381 | +void | |
| 382 | +Sys_readn(vm_registers_t &r, vm_t &vm) | |
| 383 | +{ | |
| 384 | + auto &fp = r.stack.peek_frame()->base<F_Sys_readn>(); | |
| 385 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 386 | +} | |
| 387 | + | |
| 388 | +void | |
| 389 | +Sys_remove(vm_registers_t &r, vm_t &vm) | |
| 390 | +{ | |
| 391 | + auto &fp = r.stack.peek_frame()->base<F_Sys_remove>(); | |
| 392 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 393 | +} | |
| 394 | + | |
| 395 | +void | |
| 396 | +Sys_seek(vm_registers_t &r, vm_t &vm) | |
| 397 | +{ | |
| 398 | + auto &fp = r.stack.peek_frame()->base<F_Sys_seek>(); | |
| 399 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 400 | +} | |
| 401 | + | |
| 402 | +void | |
| 403 | +Sys_sleep(vm_registers_t &r, vm_t &vm) | |
| 404 | +{ | |
| 405 | + auto &fp = r.stack.peek_frame()->base<F_Sys_sleep>(); | |
| 406 | + | |
| 407 | + const auto period_ms = fp.period; | |
| 408 | + | |
| 409 | + // [TODO] Thread should yield to another according to the sleep() specification. | |
| 410 | + r.current_thread_state = vm_thread_state_t::release; | |
| 411 | + std::this_thread::sleep_for(std::chrono::milliseconds(period_ms)); | |
| 412 | + r.current_thread_state = vm_thread_state_t::ready; | |
| 413 | + | |
| 414 | + *fp.ret = 0; | |
| 415 | +} | |
| 416 | + | |
| 417 | +void | |
| 418 | +Sys_sprint(vm_registers_t &r, vm_t &vm) | |
| 419 | +{ | |
| 420 | + auto &fp = r.stack.peek_frame()->base<F_Sys_sprint>(); | |
| 421 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 422 | +} | |
| 423 | + | |
| 424 | +void | |
| 425 | +Sys_stat(vm_registers_t &r, vm_t &vm) | |
| 426 | +{ | |
| 427 | + auto &fp = r.stack.peek_frame()->base<F_Sys_stat>(); | |
| 428 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 429 | +} | |
| 430 | + | |
| 431 | +void | |
| 432 | +Sys_stream(vm_registers_t &r, vm_t &vm) | |
| 433 | +{ | |
| 434 | + auto &fp = r.stack.peek_frame()->base<F_Sys_stream>(); | |
| 435 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 436 | +} | |
| 437 | + | |
| 438 | +void | |
| 439 | +Sys_tokenize(vm_registers_t &r, vm_t &vm) | |
| 440 | +{ | |
| 441 | + auto &fp = r.stack.peek_frame()->base<F_Sys_tokenize>(); | |
| 442 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 443 | +} | |
| 444 | + | |
| 445 | +void | |
| 446 | +Sys_unmount(vm_registers_t &r, vm_t &vm) | |
| 447 | +{ | |
| 448 | + auto &fp = r.stack.peek_frame()->base<F_Sys_unmount>(); | |
| 449 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 450 | +} | |
| 451 | + | |
| 452 | +void | |
| 453 | +Sys_utfbytes(vm_registers_t &r, vm_t &vm) | |
| 454 | +{ | |
| 455 | + auto &fp = r.stack.peek_frame()->base<F_Sys_utfbytes>(); | |
| 456 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 457 | +} | |
| 458 | + | |
| 459 | +void | |
| 460 | +Sys_werrstr(vm_registers_t &r, vm_t &vm) | |
| 461 | +{ | |
| 462 | + auto &fp = r.stack.peek_frame()->base<F_Sys_werrstr>(); | |
| 463 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 464 | +} | |
| 465 | + | |
| 466 | +void | |
| 467 | +Sys_write(vm_registers_t &r, vm_t &vm) | |
| 468 | +{ | |
| 469 | + auto &fp = r.stack.peek_frame()->base<F_Sys_write>(); | |
| 470 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 471 | +} | |
| 472 | + | |
| 473 | +void | |
| 474 | +Sys_wstat(vm_registers_t &r, vm_t &vm) | |
| 475 | +{ | |
| 476 | + auto &fp = r.stack.peek_frame()->base<F_Sys_wstat>(); | |
| 477 | + throw vm_system_exception{ "Instruction not implemented" }; | |
| 478 | +} | |
| @@ -0,0 +1,464 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: Sysmod.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_VM_SYS_SYSMOD_H_ | |
| 8 | +#define _DISVM_SRC_VM_SYS_SYSMOD_H_ | |
| 9 | + | |
| 10 | +#include <vector> | |
| 11 | +#include <disvm.h> | |
| 12 | +#include <builtin_module.h> | |
| 13 | + | |
| 14 | +using namespace disvm; | |
| 15 | +using namespace disvm::runtime; | |
| 16 | + | |
| 17 | +struct Sys_Qid | |
| 18 | +{ | |
| 19 | + big_t path; | |
| 20 | + word_t vers; | |
| 21 | + word_t qtype; | |
| 22 | +}; | |
| 23 | +const byte_t Sys_Qid_map[] = { 0 }; | |
| 24 | +struct Sys_Dir | |
| 25 | +{ | |
| 26 | + /* vm_string_t */ pointer_t name; | |
| 27 | + /* vm_string_t */ pointer_t uid; | |
| 28 | + /* vm_string_t */ pointer_t gid; | |
| 29 | + /* vm_string_t */ pointer_t muid; | |
| 30 | + Sys_Qid qid; | |
| 31 | + word_t mode; | |
| 32 | + word_t atime; | |
| 33 | + word_t mtime; | |
| 34 | + byte_t _pad44[4]; | |
| 35 | + big_t length; | |
| 36 | + word_t dtype; | |
| 37 | + word_t dev; | |
| 38 | +}; | |
| 39 | +const byte_t Sys_Dir_map[] = { 0xf0, }; | |
| 40 | +struct Sys_FD | |
| 41 | +{ | |
| 42 | + word_t fd; | |
| 43 | +}; | |
| 44 | +const byte_t Sys_FD_map[] = { 0 }; | |
| 45 | +struct Sys_Connection | |
| 46 | +{ | |
| 47 | + /* Sys_FD */ pointer_t dfd; | |
| 48 | + /* Sys_FD */ pointer_t cfd; | |
| 49 | + /* vm_string_t */ pointer_t dir; | |
| 50 | +}; | |
| 51 | +const byte_t Sys_Connection_map[] = { 0xe0, }; | |
| 52 | +using Sys_Rread = struct { /* vm_array_t */ pointer_t t0; /* vm_string_t */ pointer_t t1; }; | |
| 53 | +#define Sys_Rread_map {0xc0,} | |
| 54 | +using Sys_Rwrite = struct { word_t t0; /* vm_string_t */ pointer_t t1; }; | |
| 55 | +#define Sys_Rwrite_map {0x40,} | |
| 56 | +struct Sys_FileIO | |
| 57 | +{ | |
| 58 | + /* vm_channel_t */ pointer_t read; | |
| 59 | + /* vm_channel_t */ pointer_t write; | |
| 60 | +}; | |
| 61 | +using Sys_FileIO_read = struct { word_t t0; word_t t1; word_t t2; /* vm_channel_t */ pointer_t t3; }; | |
| 62 | +#define Sys_FileIO_read_map {0x10,} | |
| 63 | +using Sys_FileIO_write = struct { word_t t0; /* vm_array_t */ pointer_t t1; word_t t2; /* vm_channel_t */ pointer_t t3; }; | |
| 64 | +#define Sys_FileIO_write_map {0x50,} | |
| 65 | +const byte_t Sys_FileIO_map[] = { 0xc0, }; | |
| 66 | +void Sys_announce(vm_registers_t &r, vm_t &vm); | |
| 67 | +struct F_Sys_announce : public vm_frame_base_alloc_t | |
| 68 | +{ | |
| 69 | + struct { word_t t0; Sys_Connection t1; }*ret; | |
| 70 | + byte_t temps[12]; | |
| 71 | + /* vm_string_t */ pointer_t addr; | |
| 72 | +}; | |
| 73 | +void Sys_aprint(vm_registers_t &r, vm_t &vm); | |
| 74 | +struct F_Sys_aprint : public vm_frame_base_alloc_t | |
| 75 | +{ | |
| 76 | + /* vm_array_t */ pointer_t* ret; | |
| 77 | + byte_t temps[12]; | |
| 78 | + /* vm_string_t */ pointer_t s; | |
| 79 | + byte_t vargs; | |
| 80 | +}; | |
| 81 | +void Sys_bind(vm_registers_t &r, vm_t &vm); | |
| 82 | +struct F_Sys_bind : public vm_frame_base_alloc_t | |
| 83 | +{ | |
| 84 | + word_t* ret; | |
| 85 | + byte_t temps[12]; | |
| 86 | + /* vm_string_t */ pointer_t s; | |
| 87 | + /* vm_string_t */ pointer_t on; | |
| 88 | + word_t flags; | |
| 89 | +}; | |
| 90 | +void Sys_byte2char(vm_registers_t &r, vm_t &vm); | |
| 91 | +struct F_Sys_byte2char : public vm_frame_base_alloc_t | |
| 92 | +{ | |
| 93 | + struct { word_t t0; word_t t1; word_t t2; }*ret; | |
| 94 | + byte_t temps[12]; | |
| 95 | + /* vm_array_t */ pointer_t buf; | |
| 96 | + word_t n; | |
| 97 | +}; | |
| 98 | +void Sys_char2byte(vm_registers_t &r, vm_t &vm); | |
| 99 | +struct F_Sys_char2byte : public vm_frame_base_alloc_t | |
| 100 | +{ | |
| 101 | + word_t* ret; | |
| 102 | + byte_t temps[12]; | |
| 103 | + word_t c; | |
| 104 | + /* vm_array_t */ pointer_t buf; | |
| 105 | + word_t n; | |
| 106 | +}; | |
| 107 | +void Sys_chdir(vm_registers_t &r, vm_t &vm); | |
| 108 | +struct F_Sys_chdir : public vm_frame_base_alloc_t | |
| 109 | +{ | |
| 110 | + word_t* ret; | |
| 111 | + byte_t temps[12]; | |
| 112 | + /* vm_string_t */ pointer_t path; | |
| 113 | +}; | |
| 114 | +void Sys_create(vm_registers_t &r, vm_t &vm); | |
| 115 | +struct F_Sys_create : public vm_frame_base_alloc_t | |
| 116 | +{ | |
| 117 | + /* Sys_FD */ pointer_t* ret; | |
| 118 | + byte_t temps[12]; | |
| 119 | + /* vm_string_t */ pointer_t s; | |
| 120 | + word_t mode; | |
| 121 | + word_t perm; | |
| 122 | +}; | |
| 123 | +void Sys_dial(vm_registers_t &r, vm_t &vm); | |
| 124 | +struct F_Sys_dial : public vm_frame_base_alloc_t | |
| 125 | +{ | |
| 126 | + struct { word_t t0; Sys_Connection t1; }*ret; | |
| 127 | + byte_t temps[12]; | |
| 128 | + /* vm_string_t */ pointer_t addr; | |
| 129 | + /* vm_string_t */ pointer_t local; | |
| 130 | +}; | |
| 131 | +void Sys_dirread(vm_registers_t &r, vm_t &vm); | |
| 132 | +struct F_Sys_dirread : public vm_frame_base_alloc_t | |
| 133 | +{ | |
| 134 | + struct { word_t t0; /* vm_array_t */ pointer_t t1; }*ret; | |
| 135 | + byte_t temps[12]; | |
| 136 | + /* Sys_FD */ pointer_t fd; | |
| 137 | +}; | |
| 138 | +void Sys_dup(vm_registers_t &r, vm_t &vm); | |
| 139 | +struct F_Sys_dup : public vm_frame_base_alloc_t | |
| 140 | +{ | |
| 141 | + word_t* ret; | |
| 142 | + byte_t temps[12]; | |
| 143 | + word_t old; | |
| 144 | + word_t new_; | |
| 145 | +}; | |
| 146 | +void Sys_export(vm_registers_t &r, vm_t &vm); | |
| 147 | +struct F_Sys_export : public vm_frame_base_alloc_t | |
| 148 | +{ | |
| 149 | + word_t* ret; | |
| 150 | + byte_t temps[12]; | |
| 151 | + /* Sys_FD */ pointer_t c; | |
| 152 | + /* vm_string_t */ pointer_t dir; | |
| 153 | + word_t flag; | |
| 154 | +}; | |
| 155 | +void Sys_fauth(vm_registers_t &r, vm_t &vm); | |
| 156 | +struct F_Sys_fauth : public vm_frame_base_alloc_t | |
| 157 | +{ | |
| 158 | + /* Sys_FD */ pointer_t* ret; | |
| 159 | + byte_t temps[12]; | |
| 160 | + /* Sys_FD */ pointer_t fd; | |
| 161 | + /* vm_string_t */ pointer_t aname; | |
| 162 | +}; | |
| 163 | +void Sys_fd2path(vm_registers_t &r, vm_t &vm); | |
| 164 | +struct F_Sys_fd2path : public vm_frame_base_alloc_t | |
| 165 | +{ | |
| 166 | + /* vm_string_t */ pointer_t* ret; | |
| 167 | + byte_t temps[12]; | |
| 168 | + /* Sys_FD */ pointer_t fd; | |
| 169 | +}; | |
| 170 | +void Sys_fildes(vm_registers_t &r, vm_t &vm); | |
| 171 | +struct F_Sys_fildes : public vm_frame_base_alloc_t | |
| 172 | +{ | |
| 173 | + /* Sys_FD */ pointer_t* ret; | |
| 174 | + byte_t temps[12]; | |
| 175 | + word_t fd; | |
| 176 | +}; | |
| 177 | +void Sys_file2chan(vm_registers_t &r, vm_t &vm); | |
| 178 | +struct F_Sys_file2chan : public vm_frame_base_alloc_t | |
| 179 | +{ | |
| 180 | + /* Sys_FileIO */ pointer_t* ret; | |
| 181 | + byte_t temps[12]; | |
| 182 | + /* vm_string_t */ pointer_t dir; | |
| 183 | + /* vm_string_t */ pointer_t file; | |
| 184 | +}; | |
| 185 | +void Sys_fprint(vm_registers_t &r, vm_t &vm); | |
| 186 | +struct F_Sys_fprint : public vm_frame_base_alloc_t | |
| 187 | +{ | |
| 188 | + word_t* ret; | |
| 189 | + byte_t temps[12]; | |
| 190 | + /* Sys_FD */ pointer_t fd; | |
| 191 | + /* vm_string_t */ pointer_t s; | |
| 192 | + byte_t vargs; | |
| 193 | +}; | |
| 194 | +void Sys_fstat(vm_registers_t &r, vm_t &vm); | |
| 195 | +struct F_Sys_fstat : public vm_frame_base_alloc_t | |
| 196 | +{ | |
| 197 | + struct { word_t t0; byte_t _pad4[4]; Sys_Dir t1; }*ret; | |
| 198 | + byte_t temps[12]; | |
| 199 | + /* Sys_FD */ pointer_t fd; | |
| 200 | +}; | |
| 201 | +void Sys_fversion(vm_registers_t &r, vm_t &vm); | |
| 202 | +struct F_Sys_fversion : public vm_frame_base_alloc_t | |
| 203 | +{ | |
| 204 | + struct { word_t t0; /* vm_string_t */ pointer_t t1; }*ret; | |
| 205 | + byte_t temps[12]; | |
| 206 | + /* Sys_FD */ pointer_t fd; | |
| 207 | + word_t msize; | |
| 208 | + /* vm_string_t */ pointer_t version; | |
| 209 | +}; | |
| 210 | +void Sys_fwstat(vm_registers_t &r, vm_t &vm); | |
| 211 | +struct F_Sys_fwstat : public vm_frame_base_alloc_t | |
| 212 | +{ | |
| 213 | + word_t* ret; | |
| 214 | + byte_t temps[12]; | |
| 215 | + /* Sys_FD */ pointer_t fd; | |
| 216 | + byte_t _pad36[4]; | |
| 217 | + Sys_Dir d; | |
| 218 | +}; | |
| 219 | +void Sys_iounit(vm_registers_t &r, vm_t &vm); | |
| 220 | +struct F_Sys_iounit : public vm_frame_base_alloc_t | |
| 221 | +{ | |
| 222 | + word_t* ret; | |
| 223 | + byte_t temps[12]; | |
| 224 | + /* Sys_FD */ pointer_t fd; | |
| 225 | +}; | |
| 226 | +void Sys_listen(vm_registers_t &r, vm_t &vm); | |
| 227 | +struct F_Sys_listen : public vm_frame_base_alloc_t | |
| 228 | +{ | |
| 229 | + struct { word_t t0; Sys_Connection t1; }*ret; | |
| 230 | + byte_t temps[12]; | |
| 231 | + Sys_Connection c; | |
| 232 | +}; | |
| 233 | +void Sys_millisec(vm_registers_t &r, vm_t &vm); | |
| 234 | +struct F_Sys_millisec : public vm_frame_base_alloc_t | |
| 235 | +{ | |
| 236 | + word_t* ret; | |
| 237 | + byte_t temps[12]; | |
| 238 | +}; | |
| 239 | +void Sys_mount(vm_registers_t &r, vm_t &vm); | |
| 240 | +struct F_Sys_mount : public vm_frame_base_alloc_t | |
| 241 | +{ | |
| 242 | + word_t* ret; | |
| 243 | + byte_t temps[12]; | |
| 244 | + /* Sys_FD */ pointer_t fd; | |
| 245 | + /* Sys_FD */ pointer_t afd; | |
| 246 | + /* vm_string_t */ pointer_t on; | |
| 247 | + word_t flags; | |
| 248 | + /* vm_string_t */ pointer_t spec; | |
| 249 | +}; | |
| 250 | +void Sys_open(vm_registers_t &r, vm_t &vm); | |
| 251 | +struct F_Sys_open : public vm_frame_base_alloc_t | |
| 252 | +{ | |
| 253 | + /* Sys_FD */ pointer_t* ret; | |
| 254 | + byte_t temps[12]; | |
| 255 | + /* vm_string_t */ pointer_t s; | |
| 256 | + word_t mode; | |
| 257 | +}; | |
| 258 | +void Sys_pctl(vm_registers_t &r, vm_t &vm); | |
| 259 | +struct F_Sys_pctl : public vm_frame_base_alloc_t | |
| 260 | +{ | |
| 261 | + word_t* ret; | |
| 262 | + byte_t temps[12]; | |
| 263 | + word_t flags; | |
| 264 | + /* vm_list_t */ pointer_t movefd; | |
| 265 | +}; | |
| 266 | +void Sys_pipe(vm_registers_t &r, vm_t &vm); | |
| 267 | +struct F_Sys_pipe : public vm_frame_base_alloc_t | |
| 268 | +{ | |
| 269 | + word_t* ret; | |
| 270 | + byte_t temps[12]; | |
| 271 | + /* vm_array_t */ pointer_t fds; | |
| 272 | +}; | |
| 273 | +void Sys_pread(vm_registers_t &r, vm_t &vm); | |
| 274 | +struct F_Sys_pread : public vm_frame_base_alloc_t | |
| 275 | +{ | |
| 276 | + word_t* ret; | |
| 277 | + byte_t temps[12]; | |
| 278 | + /* Sys_FD */ pointer_t fd; | |
| 279 | + /* vm_array_t */ pointer_t buf; | |
| 280 | + word_t n; | |
| 281 | + byte_t _pad44[4]; | |
| 282 | + big_t off; | |
| 283 | +}; | |
| 284 | +void Sys_print(vm_registers_t &r, vm_t &vm); | |
| 285 | +struct F_Sys_print : public vm_frame_base_alloc_t | |
| 286 | +{ | |
| 287 | + word_t* ret; | |
| 288 | + byte_t temps[12]; | |
| 289 | + /* vm_string_t */ pointer_t s; | |
| 290 | + byte_t vargs; | |
| 291 | +}; | |
| 292 | +void Sys_pwrite(vm_registers_t &r, vm_t &vm); | |
| 293 | +struct F_Sys_pwrite : public vm_frame_base_alloc_t | |
| 294 | +{ | |
| 295 | + word_t* ret; | |
| 296 | + byte_t temps[12]; | |
| 297 | + /* Sys_FD */ pointer_t fd; | |
| 298 | + /* vm_array_t */ pointer_t buf; | |
| 299 | + word_t n; | |
| 300 | + byte_t _pad44[4]; | |
| 301 | + big_t off; | |
| 302 | +}; | |
| 303 | +void Sys_read(vm_registers_t &r, vm_t &vm); | |
| 304 | +struct F_Sys_read : public vm_frame_base_alloc_t | |
| 305 | +{ | |
| 306 | + word_t* ret; | |
| 307 | + byte_t temps[12]; | |
| 308 | + /* Sys_FD */ pointer_t fd; | |
| 309 | + /* vm_array_t */ pointer_t buf; | |
| 310 | + word_t n; | |
| 311 | +}; | |
| 312 | +void Sys_readn(vm_registers_t &r, vm_t &vm); | |
| 313 | +struct F_Sys_readn : public vm_frame_base_alloc_t | |
| 314 | +{ | |
| 315 | + word_t* ret; | |
| 316 | + byte_t temps[12]; | |
| 317 | + /* Sys_FD */ pointer_t fd; | |
| 318 | + /* vm_array_t */ pointer_t buf; | |
| 319 | + word_t n; | |
| 320 | +}; | |
| 321 | +void Sys_remove(vm_registers_t &r, vm_t &vm); | |
| 322 | +struct F_Sys_remove : public vm_frame_base_alloc_t | |
| 323 | +{ | |
| 324 | + word_t* ret; | |
| 325 | + byte_t temps[12]; | |
| 326 | + /* vm_string_t */ pointer_t s; | |
| 327 | +}; | |
| 328 | +void Sys_seek(vm_registers_t &r, vm_t &vm); | |
| 329 | +struct F_Sys_seek : public vm_frame_base_alloc_t | |
| 330 | +{ | |
| 331 | + big_t* ret; | |
| 332 | + byte_t temps[12]; | |
| 333 | + /* Sys_FD */ pointer_t fd; | |
| 334 | + byte_t _pad36[4]; | |
| 335 | + big_t off; | |
| 336 | + word_t start; | |
| 337 | +}; | |
| 338 | +void Sys_sleep(vm_registers_t &r, vm_t &vm); | |
| 339 | +struct F_Sys_sleep : public vm_frame_base_alloc_t | |
| 340 | +{ | |
| 341 | + word_t* ret; | |
| 342 | + byte_t temps[12]; | |
| 343 | + word_t period; | |
| 344 | +}; | |
| 345 | +void Sys_sprint(vm_registers_t &r, vm_t &vm); | |
| 346 | +struct F_Sys_sprint : public vm_frame_base_alloc_t | |
| 347 | +{ | |
| 348 | + /* vm_string_t */ pointer_t* ret; | |
| 349 | + byte_t temps[12]; | |
| 350 | + /* vm_string_t */ pointer_t s; | |
| 351 | + byte_t vargs; | |
| 352 | +}; | |
| 353 | +void Sys_stat(vm_registers_t &r, vm_t &vm); | |
| 354 | +struct F_Sys_stat : public vm_frame_base_alloc_t | |
| 355 | +{ | |
| 356 | + struct { word_t t0; byte_t _pad4[4]; Sys_Dir t1; }*ret; | |
| 357 | + byte_t temps[12]; | |
| 358 | + /* vm_string_t */ pointer_t s; | |
| 359 | +}; | |
| 360 | +void Sys_stream(vm_registers_t &r, vm_t &vm); | |
| 361 | +struct F_Sys_stream : public vm_frame_base_alloc_t | |
| 362 | +{ | |
| 363 | + word_t* ret; | |
| 364 | + byte_t temps[12]; | |
| 365 | + /* Sys_FD */ pointer_t src; | |
| 366 | + /* Sys_FD */ pointer_t dst; | |
| 367 | + word_t bufsiz; | |
| 368 | +}; | |
| 369 | +void Sys_tokenize(vm_registers_t &r, vm_t &vm); | |
| 370 | +struct F_Sys_tokenize : public vm_frame_base_alloc_t | |
| 371 | +{ | |
| 372 | + struct { word_t t0; /* vm_list_t */ pointer_t t1; }*ret; | |
| 373 | + byte_t temps[12]; | |
| 374 | + /* vm_string_t */ pointer_t s; | |
| 375 | + /* vm_string_t */ pointer_t delim; | |
| 376 | +}; | |
| 377 | +void Sys_unmount(vm_registers_t &r, vm_t &vm); | |
| 378 | +struct F_Sys_unmount : public vm_frame_base_alloc_t | |
| 379 | +{ | |
| 380 | + word_t* ret; | |
| 381 | + byte_t temps[12]; | |
| 382 | + /* vm_string_t */ pointer_t s1; | |
| 383 | + /* vm_string_t */ pointer_t s2; | |
| 384 | +}; | |
| 385 | +void Sys_utfbytes(vm_registers_t &r, vm_t &vm); | |
| 386 | +struct F_Sys_utfbytes : public vm_frame_base_alloc_t | |
| 387 | +{ | |
| 388 | + word_t* ret; | |
| 389 | + byte_t temps[12]; | |
| 390 | + /* vm_array_t */ pointer_t buf; | |
| 391 | + word_t n; | |
| 392 | +}; | |
| 393 | +void Sys_werrstr(vm_registers_t &r, vm_t &vm); | |
| 394 | +struct F_Sys_werrstr : public vm_frame_base_alloc_t | |
| 395 | +{ | |
| 396 | + word_t* ret; | |
| 397 | + byte_t temps[12]; | |
| 398 | + /* vm_string_t */ pointer_t s; | |
| 399 | +}; | |
| 400 | +void Sys_write(vm_registers_t &r, vm_t &vm); | |
| 401 | +struct F_Sys_write : public vm_frame_base_alloc_t | |
| 402 | +{ | |
| 403 | + word_t* ret; | |
| 404 | + byte_t temps[12]; | |
| 405 | + /* Sys_FD */ pointer_t fd; | |
| 406 | + /* vm_array_t */ pointer_t buf; | |
| 407 | + word_t n; | |
| 408 | +}; | |
| 409 | +void Sys_wstat(vm_registers_t &r, vm_t &vm); | |
| 410 | +struct F_Sys_wstat : public vm_frame_base_alloc_t | |
| 411 | +{ | |
| 412 | + word_t* ret; | |
| 413 | + byte_t temps[12]; | |
| 414 | + /* vm_string_t */ pointer_t s; | |
| 415 | + byte_t _pad36[4]; | |
| 416 | + Sys_Dir d; | |
| 417 | +}; | |
| 418 | +const char *Sys_PATH = "$Sys"; | |
| 419 | +const word_t Sys_Maxint = 0x7fffffff; | |
| 420 | +const word_t Sys_QTDIR = 0x80; | |
| 421 | +const word_t Sys_QTAPPEND = 0x40; | |
| 422 | +const word_t Sys_QTEXCL = 0x20; | |
| 423 | +const word_t Sys_QTAUTH = 0x8; | |
| 424 | +const word_t Sys_QTTMP = 0x4; | |
| 425 | +const word_t Sys_QTFILE = 0x0; | |
| 426 | +const word_t Sys_ATOMICIO = 0x2000; | |
| 427 | +const word_t Sys_SEEKSTART = 0x0; | |
| 428 | +const word_t Sys_SEEKRELA = 0x1; | |
| 429 | +const word_t Sys_SEEKEND = 0x2; | |
| 430 | +const word_t Sys_NAMEMAX = 0x100; | |
| 431 | +const word_t Sys_ERRMAX = 0x80; | |
| 432 | +const word_t Sys_WAITLEN = 0xc0; | |
| 433 | +const word_t Sys_OREAD = 0x0; | |
| 434 | +const word_t Sys_OWRITE = 0x1; | |
| 435 | +const word_t Sys_ORDWR = 0x2; | |
| 436 | +const word_t Sys_OTRUNC = 0x10; | |
| 437 | +const word_t Sys_ORCLOSE = 0x40; | |
| 438 | +const word_t Sys_OEXCL = 0x1000; | |
| 439 | +const word_t Sys_DMDIR = 0x80000000; | |
| 440 | +const word_t Sys_DMAPPEND = 0x40000000; | |
| 441 | +const word_t Sys_DMEXCL = 0x20000000; | |
| 442 | +const word_t Sys_DMAUTH = 0x8000000; | |
| 443 | +const word_t Sys_DMTMP = 0x4000000; | |
| 444 | +const word_t Sys_MREPL = 0x0; | |
| 445 | +const word_t Sys_MBEFORE = 0x1; | |
| 446 | +const word_t Sys_MAFTER = 0x2; | |
| 447 | +const word_t Sys_MCREATE = 0x4; | |
| 448 | +const word_t Sys_MCACHE = 0x10; | |
| 449 | +const word_t Sys_NEWFD = 0x1; | |
| 450 | +const word_t Sys_FORKFD = 0x2; | |
| 451 | +const word_t Sys_NEWNS = 0x4; | |
| 452 | +const word_t Sys_FORKNS = 0x8; | |
| 453 | +const word_t Sys_NEWPGRP = 0x10; | |
| 454 | +const word_t Sys_NODEVS = 0x20; | |
| 455 | +const word_t Sys_NEWENV = 0x40; | |
| 456 | +const word_t Sys_FORKENV = 0x80; | |
| 457 | +const word_t Sys_EXPWAIT = 0x0; | |
| 458 | +const word_t Sys_EXPASYNC = 0x1; | |
| 459 | +const word_t Sys_UTFmax = 0x4; | |
| 460 | +const word_t Sys_UTFerror = 0xfffd; | |
| 461 | +const word_t Sys_Runemax = 0x10ffff; | |
| 462 | +const word_t Sys_Runemask = 0x1fffff; | |
| 463 | + | |
| 464 | +#endif // _DISVM_SRC_VM_SYS_SYSMOD_H_ | |
| @@ -0,0 +1,131 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: print.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <array> | |
| 8 | +#include <cinttypes> | |
| 9 | +#include <utf8.h> | |
| 10 | +#include "sys_utils.h" | |
| 11 | + | |
| 12 | +using namespace disvm::runtime; | |
| 13 | +using namespace disvm::runtime::utf8; | |
| 14 | + | |
| 15 | +// Shamelessly adopted and altered from Inferno implementation (xprint - libinterp/runt.c) | |
| 16 | +// [TODO] Handle UTF-8 | |
| 17 | +word_t printf_to_buffer(const vm_string_t &msg_fmt, byte_t *msg_args, std::size_t buffer_size, char *buffer) | |
| 18 | +{ | |
| 19 | + auto b_curr = buffer; | |
| 20 | + const auto b_end = buffer + (buffer_size - 1); | |
| 21 | + const auto fmt_len = msg_fmt.get_length(); | |
| 22 | + auto i = word_t{ 0 }; | |
| 23 | + | |
| 24 | + // Loop over the entire format string | |
| 25 | + while (i < fmt_len) | |
| 26 | + { | |
| 27 | + if (b_curr >= b_end) | |
| 28 | + return -1; | |
| 29 | + | |
| 30 | + auto c = msg_fmt.get_rune(i); | |
| 31 | + ++i; | |
| 32 | + if (c != '%') | |
| 33 | + { | |
| 34 | + b_curr = reinterpret_cast<char *>(utf8::encode(c, reinterpret_cast<uint8_t *>(b_curr))); | |
| 35 | + continue; | |
| 36 | + } | |
| 37 | + | |
| 38 | + auto format = std::vector<char>{}; | |
| 39 | + format.push_back('%'); | |
| 40 | + auto wb = int{ 0 }; | |
| 41 | + auto big_flag = false; | |
| 42 | + | |
| 43 | + // Decode the format specifier | |
| 44 | + while (i < fmt_len) | |
| 45 | + { | |
| 46 | + c = msg_fmt.get_rune(i); | |
| 47 | + ++i; | |
| 48 | + switch (c) | |
| 49 | + { | |
| 50 | + // Format flags | |
| 51 | + default: | |
| 52 | + continue; | |
| 53 | + case 'b': // Print big type (64-bit) | |
| 54 | + format.push_back('l'); | |
| 55 | + format.push_back('l'); | |
| 56 | + big_flag = true; | |
| 57 | + continue; | |
| 58 | + | |
| 59 | + // Format verbs | |
| 60 | + case '%': // Escaped specifier | |
| 61 | + wb = std::snprintf(b_curr, (b_end - b_curr), "%%%%"); | |
| 62 | + break; | |
| 63 | + case 'o': // octal | |
| 64 | + case 'd': // decimal | |
| 65 | + case 'x': // hexidecimal | |
| 66 | + case 'X': // Upper-case hexidecimal | |
| 67 | + format.push_back(static_cast<char>(c)); | |
| 68 | + format.push_back('\0'); | |
| 69 | + if (big_flag) | |
| 70 | + { | |
| 71 | + const auto b = *reinterpret_cast<big_t *>(msg_args); | |
| 72 | + wb = std::snprintf(b_curr, (b_end - b_curr), format.data(), b); | |
| 73 | + msg_args += sizeof(b); | |
| 74 | + } | |
| 75 | + else | |
| 76 | + { | |
| 77 | + const auto w = *reinterpret_cast<word_t *>(msg_args); | |
| 78 | + wb = std::snprintf(b_curr, (b_end - b_curr), format.data(), w); | |
| 79 | + msg_args += sizeof(w); | |
| 80 | + } | |
| 81 | + break; | |
| 82 | + case 's': // string | |
| 83 | + { | |
| 84 | + auto s = vm_alloc_t::from_allocation<vm_string_t>(*reinterpret_cast<pointer_t *>(msg_args)); | |
| 85 | + if (s != nullptr) | |
| 86 | + wb = std::snprintf(b_curr, (b_end - b_curr), "%s", s->str()); | |
| 87 | + | |
| 88 | + msg_args += sizeof(pointer_t); | |
| 89 | + break; | |
| 90 | + } | |
| 91 | + // [SPEC] Undocumented format verb for debugging VM objects. | |
| 92 | + case 'H': | |
| 93 | + { | |
| 94 | + auto alloc = vm_alloc_t::from_allocation(*reinterpret_cast<pointer_t *>(msg_args)); | |
| 95 | + auto rc = static_cast<std::size_t>(-1); | |
| 96 | + const void *type_alloc = nullptr; | |
| 97 | + if (alloc != nullptr) | |
| 98 | + { | |
| 99 | + rc = alloc->get_ref_count(); | |
| 100 | + type_alloc = alloc->alloc_type.get(); | |
| 101 | + } | |
| 102 | + | |
| 103 | + wb = std::snprintf(b_curr, (b_end - b_curr), "%" PRIdPTR ".%#08" PRIxPTR, rc, reinterpret_cast<std::size_t>(type_alloc)); | |
| 104 | + msg_args += sizeof(pointer_t); | |
| 105 | + break; | |
| 106 | + } | |
| 107 | + } | |
| 108 | + | |
| 109 | + break; | |
| 110 | + } | |
| 111 | + | |
| 112 | + if (wb < 0) | |
| 113 | + return -1; | |
| 114 | + | |
| 115 | + b_curr += wb; | |
| 116 | + } | |
| 117 | + | |
| 118 | + assert(*b_curr == '\0' && "The buffer should be null-terminated"); | |
| 119 | + return (b_curr - buffer); | |
| 120 | +} | |
| 121 | + | |
| 122 | +word_t printf_to_dynamic_buffer(const vm_string_t &msg_fmt, byte_t *msg_args, std::vector<char> &buffer) | |
| 123 | +{ | |
| 124 | + auto result = word_t{ 0 }; | |
| 125 | + while (-1 == (result = printf_to_buffer(msg_fmt, msg_args, buffer.size(), buffer.data()))) | |
| 126 | + { | |
| 127 | + buffer.resize(buffer.size() * 2); | |
| 128 | + } | |
| 129 | + | |
| 130 | + return result; | |
| 131 | +} | |
| @@ -0,0 +1,21 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: sys_utils.h | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#ifndef _DISVM_SRC_VM_SYS_UTILS_H_ | |
| 8 | +#define _DISVM_SRC_VM_SYS_UTILS_H_ | |
| 9 | + | |
| 10 | +#include <vector> | |
| 11 | +#include <disvm.h> | |
| 12 | + | |
| 13 | +// Prints the format and arguments to the supplied buffer. | |
| 14 | +// Returns the number of bytes written. | |
| 15 | +disvm::runtime::word_t printf_to_buffer(const disvm::runtime::vm_string_t &msg_fmt, disvm::runtime::byte_t *msg_args, std::size_t buffer_size, char *buffer); | |
| 16 | + | |
| 17 | +// Prints the format and arguments to the supplied dynamic buffer. | |
| 18 | +// Returns the number of bytes written. | |
| 19 | +disvm::runtime::word_t printf_to_dynamic_buffer(const disvm::runtime::vm_string_t &msg_fmt, disvm::runtime::byte_t *msg_args, std::vector<char> &buffer); | |
| 20 | + | |
| 21 | +#endif // _DISVM_SRC_VM_SYS_UTILS_H_ | |
| @@ -0,0 +1,298 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: thread.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cinttypes> | |
| 8 | +#include <cassert> | |
| 9 | +#include <memory> | |
| 10 | +#include <atomic> | |
| 11 | +#include <disvm.h> | |
| 12 | +#include <exceptions.h> | |
| 13 | +#include <vm_memory.h> | |
| 14 | +#include <debug.h> | |
| 15 | +#include "execution_table.h" | |
| 16 | + | |
| 17 | +using namespace disvm; | |
| 18 | +using namespace disvm::runtime; | |
| 19 | + | |
| 20 | +namespace | |
| 21 | +{ | |
| 22 | + // Update VM registers based on the instruction. | |
| 23 | + void decode_address(const vm_exec_op_t &inst, vm_registers_t ®) | |
| 24 | + { | |
| 25 | + switch (inst.source.mode) | |
| 26 | + { | |
| 27 | + case address_mode_t::offset_indirect_fp: | |
| 28 | + reg.src = reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(reg.stack.peek_frame()->base()) + inst.source.register1); | |
| 29 | + break; | |
| 30 | + case address_mode_t::offset_indirect_mp: | |
| 31 | + reg.src = reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(reg.mp_base->allocation()) + inst.source.register1); | |
| 32 | + break; | |
| 33 | + case address_mode_t::offset_double_indirect_fp: | |
| 34 | + { | |
| 35 | + const auto frame_offset = *reinterpret_cast<std::size_t *>(reinterpret_cast<uint8_t *>(reg.stack.peek_frame()->base()) + inst.source.register1); | |
| 36 | + reg.src = reinterpret_cast<pointer_t>(frame_offset + inst.source.register2); | |
| 37 | + break; | |
| 38 | + } | |
| 39 | + case address_mode_t::offset_double_indirect_mp: | |
| 40 | + { | |
| 41 | + const auto mp_offset = *reinterpret_cast<std::size_t *>(reinterpret_cast<uint8_t *>(reg.mp_base->allocation()) + inst.source.register1); | |
| 42 | + reg.src = reinterpret_cast<pointer_t>(mp_offset + inst.source.register2); | |
| 43 | + break; | |
| 44 | + } | |
| 45 | + case address_mode_t::immediate: | |
| 46 | + reg.src = reinterpret_cast<pointer_t>(const_cast<word_t *>(&inst.source.register1)); | |
| 47 | + break; | |
| 48 | + case address_mode_t::none: | |
| 49 | + reg.src = nullptr; | |
| 50 | + break; | |
| 51 | + default: | |
| 52 | + assert(false && "Unknown source addressing"); | |
| 53 | + } | |
| 54 | + | |
| 55 | + switch (inst.destination.mode) | |
| 56 | + { | |
| 57 | + case address_mode_t::offset_indirect_fp: | |
| 58 | + reg.dest = reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(reg.stack.peek_frame()->base()) + inst.destination.register1); | |
| 59 | + break; | |
| 60 | + case address_mode_t::offset_indirect_mp: | |
| 61 | + reg.dest = reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(reg.mp_base->allocation()) + inst.destination.register1); | |
| 62 | + break; | |
| 63 | + case address_mode_t::offset_double_indirect_fp: | |
| 64 | + { | |
| 65 | + const auto frame_offset = *reinterpret_cast<std::size_t *>(reinterpret_cast<uint8_t *>(reg.stack.peek_frame()->base()) + inst.destination.register1); | |
| 66 | + reg.dest = reinterpret_cast<pointer_t>(frame_offset + inst.destination.register2); | |
| 67 | + break; | |
| 68 | + } | |
| 69 | + case address_mode_t::offset_double_indirect_mp: | |
| 70 | + { | |
| 71 | + const auto mp_offset = *reinterpret_cast<std::size_t *>(reinterpret_cast<uint8_t *>(reg.mp_base->allocation()) + inst.destination.register1); | |
| 72 | + reg.dest = reinterpret_cast<pointer_t>(mp_offset + inst.destination.register2); | |
| 73 | + break; | |
| 74 | + } | |
| 75 | + case address_mode_t::immediate: | |
| 76 | + reg.dest = reinterpret_cast<pointer_t>(const_cast<word_t *>(&inst.destination.register1)); | |
| 77 | + break; | |
| 78 | + case address_mode_t::none: | |
| 79 | + reg.dest = nullptr; | |
| 80 | + break; | |
| 81 | + default: | |
| 82 | + assert(false && "Unknown destination addressing"); | |
| 83 | + } | |
| 84 | + | |
| 85 | + switch (inst.middle.mode) | |
| 86 | + { | |
| 87 | + case address_mode_middle_t::small_offset_indirect_fp: | |
| 88 | + reg.mid = reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(reg.stack.peek_frame()->base()) + inst.middle.register1); | |
| 89 | + break; | |
| 90 | + case address_mode_middle_t::small_offset_indirect_mp: | |
| 91 | + reg.mid = reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(reg.mp_base->allocation()) + inst.middle.register1); | |
| 92 | + break; | |
| 93 | + case address_mode_middle_t::small_immediate: | |
| 94 | + reg.mid = reinterpret_cast<pointer_t>(const_cast<word_t *>(&inst.middle.register1)); | |
| 95 | + break; | |
| 96 | + case address_mode_middle_t::none: | |
| 97 | + // [SPEC] This is an undocumented expectation but required in many operations (e.g. i++). | |
| 98 | + reg.mid = reg.dest; | |
| 99 | + break; | |
| 100 | + default: | |
| 101 | + assert(false && "Unknown middle addressing"); | |
| 102 | + } | |
| 103 | + | |
| 104 | +#ifndef NDEBUG | |
| 105 | + // This is a perf critical function so logging is only available in debug builds | |
| 106 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::addressing>()) | |
| 107 | + { | |
| 108 | + debug::log_msg( | |
| 109 | + debug::component_trace_t::addressing, | |
| 110 | + debug::log_level_t::debug, | |
| 111 | + "decode: registers: %d (%#" PRIxPTR " %#" PRIxPTR " %#" PRIxPTR ")\n", | |
| 112 | + inst.opcode, | |
| 113 | + reg.src, | |
| 114 | + reg.mid, | |
| 115 | + reg.dest); | |
| 116 | + } | |
| 117 | +#endif | |
| 118 | + } | |
| 119 | + | |
| 120 | + std::atomic<uint32_t> thread_id_counter(1); | |
| 121 | + | |
| 122 | + uint32_t get_unique_thread_id() | |
| 123 | + { | |
| 124 | + return thread_id_counter.fetch_add(1); | |
| 125 | + } | |
| 126 | +} | |
| 127 | + | |
| 128 | +vm_registers_t::vm_registers_t( | |
| 129 | + const vm_thread_t &thread, | |
| 130 | + vm_module_ref_t &entry) | |
| 131 | + : current_thread_state{ vm_thread_state_t::ready } | |
| 132 | + , module_ref{ &entry } | |
| 133 | + , mp_base{ entry.mp_base } | |
| 134 | + , thread{ thread } | |
| 135 | + , pc{ entry.module->header.entry_pc } | |
| 136 | + , stack{ entry.module->header.stack_extent } | |
| 137 | + , src{ nullptr } | |
| 138 | + , mid{ nullptr } | |
| 139 | + , dest{ nullptr } | |
| 140 | +{ | |
| 141 | + // Add a reference to the module ref and MP register | |
| 142 | + if (mp_base != nullptr) | |
| 143 | + mp_base->add_ref(); | |
| 144 | + | |
| 145 | + module_ref->add_ref(); | |
| 146 | +} | |
| 147 | + | |
| 148 | +vm_registers_t::~vm_registers_t() | |
| 149 | +{ | |
| 150 | + dec_ref_count_and_free(mp_base); | |
| 151 | + debug::assign_debug_pointer(&mp_base); | |
| 152 | + | |
| 153 | + dec_ref_count_and_free(module_ref); | |
| 154 | + debug::assign_debug_pointer(&module_ref); | |
| 155 | +} | |
| 156 | + | |
| 157 | +std::shared_ptr<const type_descriptor_t> vm_thread_t::type_desc() | |
| 158 | +{ | |
| 159 | + return intrinsic_type_desc::type<vm_thread_t>(); | |
| 160 | +} | |
| 161 | + | |
| 162 | +vm_thread_t::vm_thread_t( | |
| 163 | + vm_module_ref_t &entry, | |
| 164 | + uint32_t parent_thread_id) | |
| 165 | + : vm_alloc_t(vm_thread_t::type_desc()) | |
| 166 | + , _parent_thread_id{ parent_thread_id } | |
| 167 | + , _registers{ *this, entry } | |
| 168 | + , _thread_id{ get_unique_thread_id() } | |
| 169 | +{ | |
| 170 | + assert(_parent_thread_id != _thread_id); | |
| 171 | + assert(static_cast<std::size_t>(_registers.pc) < entry.code_section.size()); | |
| 172 | + | |
| 173 | + // Set up the stack | |
| 174 | + const auto entry_type = entry.module->header.entry_type; | |
| 175 | + auto frame_type = entry.type_section[entry_type]; | |
| 176 | + _registers.stack.alloc_frame(frame_type); | |
| 177 | + | |
| 178 | + // Pushing the initial frame sets the FP register | |
| 179 | + _registers.stack.push_frame(); | |
| 180 | + | |
| 181 | + debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "init: vm thread: %d %d\n", _thread_id, _parent_thread_id); | |
| 182 | +} | |
| 183 | + | |
| 184 | +vm_thread_t::vm_thread_t( | |
| 185 | + vm_module_ref_t &entry, | |
| 186 | + const vm_frame_t &initial_frame, | |
| 187 | + vm_pc_t start_pc, | |
| 188 | + uint32_t parent_thread_id) | |
| 189 | + : vm_alloc_t(vm_thread_t::type_desc()) | |
| 190 | + , _parent_thread_id{ parent_thread_id } | |
| 191 | + , _registers{ *this, entry } | |
| 192 | + , _thread_id{ get_unique_thread_id() } | |
| 193 | +{ | |
| 194 | + _registers.pc = start_pc; | |
| 195 | + | |
| 196 | + assert(_parent_thread_id != _thread_id); | |
| 197 | + assert(static_cast<std::size_t>(_registers.pc) < entry.code_section.size()); | |
| 198 | + | |
| 199 | + // Set the stack | |
| 200 | + auto frame_type = initial_frame.frame_type; | |
| 201 | + _registers.stack.alloc_frame(frame_type); | |
| 202 | + | |
| 203 | + // Pushing the initial frame sets the FP register | |
| 204 | + auto current_frame = _registers.stack.push_frame(); | |
| 205 | + | |
| 206 | + // Copy over the frame into this thread - pass arguments to the thread | |
| 207 | + current_frame->copy_frame_contents(initial_frame); | |
| 208 | + | |
| 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 | +} | |
| 211 | + | |
| 212 | +vm_thread_t::~vm_thread_t() | |
| 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); | |
| 215 | +} | |
| 216 | + | |
| 217 | +vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, uint32_t quanta) | |
| 218 | +{ | |
| 219 | + _registers.current_thread_state = vm_thread_state_t::running; | |
| 220 | + assert(_registers.pc != runtime_constants::invalid_program_counter); | |
| 221 | + | |
| 222 | + auto execution_duration = uint32_t{ 0 }; | |
| 223 | + | |
| 224 | + try | |
| 225 | + { | |
| 226 | + for (; execution_duration < quanta; ++execution_duration) | |
| 227 | + { | |
| 228 | + if (_registers.stack.peek_frame() == nullptr) | |
| 229 | + { | |
| 230 | + debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "exit: vm thread: stack exhausted: %d\n", _thread_id); | |
| 231 | + _registers.current_thread_state = vm_thread_state_t::empty_stack; | |
| 232 | + break; | |
| 233 | + } | |
| 234 | + | |
| 235 | + const auto &code_section = _registers.module_ref->code_section; | |
| 236 | + assert(static_cast<std::size_t>(_registers.pc) < code_section.size()); | |
| 237 | + | |
| 238 | + const auto &inst = code_section[_registers.pc].op; | |
| 239 | + decode_address(inst, _registers); | |
| 240 | + _registers.pc++; | |
| 241 | + vm_exec_table[static_cast<std::size_t>(inst.opcode)](_registers, virtual_machine); | |
| 242 | + | |
| 243 | + if (_registers.current_thread_state != vm_thread_state_t::running) | |
| 244 | + break; | |
| 245 | + } | |
| 246 | + } | |
| 247 | + catch (const unhandled_user_exception &uue) | |
| 248 | + { | |
| 249 | + // [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); | |
| 251 | + _registers.current_thread_state = vm_thread_state_t::broken; | |
| 252 | + } | |
| 253 | + catch (const vm_user_exception &ue) | |
| 254 | + { | |
| 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()); | |
| 257 | + _registers.current_thread_state = vm_thread_state_t::broken; | |
| 258 | + } | |
| 259 | + | |
| 260 | + // Log the execution result | |
| 261 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::thread>()) | |
| 262 | + { | |
| 263 | + debug::log_msg( | |
| 264 | + debug::component_trace_t::thread, | |
| 265 | + debug::log_level_t::debug, | |
| 266 | + "status: vm thread: execute: %d %d %d %d\n", | |
| 267 | + _thread_id, | |
| 268 | + execution_duration, | |
| 269 | + quanta, | |
| 270 | + _registers.current_thread_state); | |
| 271 | + } | |
| 272 | + | |
| 273 | + // If the thread is still in the running state transition it back to ready. | |
| 274 | + if (_registers.current_thread_state == vm_thread_state_t::running) | |
| 275 | + _registers.current_thread_state = vm_thread_state_t::ready; | |
| 276 | + | |
| 277 | + return _registers.current_thread_state; | |
| 278 | +} | |
| 279 | + | |
| 280 | +vm_thread_state_t vm_thread_t::get_state() const | |
| 281 | +{ | |
| 282 | + return _registers.current_thread_state; | |
| 283 | +} | |
| 284 | + | |
| 285 | +uint32_t vm_thread_t::get_thread_id() const | |
| 286 | +{ | |
| 287 | + return _thread_id; | |
| 288 | +} | |
| 289 | + | |
| 290 | +uint32_t vm_thread_t::get_parent_thread_id() const | |
| 291 | +{ | |
| 292 | + return _parent_thread_id; | |
| 293 | +} | |
| 294 | + | |
| 295 | +const vm_registers_t& vm_thread_t::get_registers() const | |
| 296 | +{ | |
| 297 | + return _registers; | |
| 298 | +} | |
| @@ -0,0 +1,136 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: utf8.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +// Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> | |
| 8 | +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. | |
| 9 | +// Most code in this file falls under the above copyright. It was augmented only to be more C++ friendly. | |
| 10 | + | |
| 11 | +#include <cinttypes> | |
| 12 | +#include <utf8.h> | |
| 13 | +#include <exceptions.h> | |
| 14 | + | |
| 15 | +using namespace disvm::runtime; | |
| 16 | + | |
| 17 | +namespace | |
| 18 | +{ | |
| 19 | + const uint8_t utf8d[] = | |
| 20 | + { | |
| 21 | + // The first part of the table maps bytes to character classes that | |
| 22 | + // to reduce the size of the transition table and create bitmasks. | |
| 23 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 24 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 25 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 26 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 27 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, | |
| 28 | + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, | |
| 29 | + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, | |
| 30 | + 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, | |
| 31 | + | |
| 32 | + // The second part is a transition table that maps a combination | |
| 33 | + // of a state of the automaton and a character class to a state. | |
| 34 | + 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, | |
| 35 | + 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, | |
| 36 | + 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, | |
| 37 | + 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, | |
| 38 | + 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, | |
| 39 | + }; | |
| 40 | +} | |
| 41 | + | |
| 42 | +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. | |
| 43 | +utf8::decode_state disvm::runtime::utf8::decode_step(utf8::decode_state &state, rune_t &codepoint, uint8_t byte) | |
| 44 | +{ | |
| 45 | + auto type = uint32_t{ utf8d[byte] }; | |
| 46 | + | |
| 47 | + codepoint = (state != utf8::decode_state::accept) ? | |
| 48 | + (byte & 0x3fu) | (codepoint << 6) : | |
| 49 | + (0xff >> type) & (byte); | |
| 50 | + | |
| 51 | + state = static_cast<utf8::decode_state>(utf8d[256 + static_cast<uint32_t>(state) + type]); | |
| 52 | + return state; | |
| 53 | +} | |
| 54 | + | |
| 55 | +// Adopted from 'countCodePoints(uint8_t* s, size_t* count)' | |
| 56 | +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. | |
| 57 | +std::size_t disvm::runtime::utf8::count_codepoints(const uint8_t *str, const std::size_t max_len) | |
| 58 | +{ | |
| 59 | + assert(str != nullptr); | |
| 60 | + auto single_character = rune_t{}; | |
| 61 | + auto points = std::size_t{ 0 }; | |
| 62 | + auto state = utf8::decode_state::accept; | |
| 63 | + | |
| 64 | + for (auto i = std::size_t{ 0 }; i < max_len; ++i) | |
| 65 | + { | |
| 66 | + if (utf8::decode_state::accept == utf8::decode_step(state, single_character, str[i])) | |
| 67 | + points++; | |
| 68 | + } | |
| 69 | + | |
| 70 | + if (state != utf8::decode_state::accept) | |
| 71 | + throw invalid_utf8{}; | |
| 72 | + | |
| 73 | + return points; | |
| 74 | +} | |
| 75 | + | |
| 76 | +std::size_t disvm::runtime::utf8::decode(const uint8_t *str, rune_t &codepoint) | |
| 77 | +{ | |
| 78 | + assert(str != nullptr); | |
| 79 | + auto state = utf8::decode_state::accept; | |
| 80 | + | |
| 81 | + codepoint = 0; | |
| 82 | + auto begin = str; | |
| 83 | + auto curr = str; | |
| 84 | + auto c = uint8_t{}; | |
| 85 | + while (0 != (c = *curr)) | |
| 86 | + { | |
| 87 | + curr++; | |
| 88 | + state = utf8::decode_step(state, codepoint, c); | |
| 89 | + if (state == utf8::decode_state::accept) | |
| 90 | + return (curr - begin); | |
| 91 | + } | |
| 92 | + | |
| 93 | + // Empty string | |
| 94 | + if (begin == curr) | |
| 95 | + return 0; | |
| 96 | + | |
| 97 | + throw invalid_utf8{}; | |
| 98 | +} | |
| 99 | + | |
| 100 | +uint8_t *disvm::runtime::utf8::encode(const rune_t rune, uint8_t *str) | |
| 101 | +{ | |
| 102 | + assert(str != nullptr); | |
| 103 | + if (rune <= utf8::constants::max_codepoint_ascii) | |
| 104 | + { | |
| 105 | + // Layout: 0xxxxxxx | |
| 106 | + str[0] = static_cast<uint8_t>(rune); | |
| 107 | + return (str + 1); | |
| 108 | + } | |
| 109 | + else if (rune <= utf8::constants::max_codepoint_2_byte) | |
| 110 | + { | |
| 111 | + // Layout: 110xxxxx 10xxxxxx | |
| 112 | + str[0] = static_cast<uint8_t>(0xc0 + (rune >> 6)); | |
| 113 | + str[1] = static_cast<uint8_t>(0x80 + (rune & utf8::constants::multi_byte_value_mask)); | |
| 114 | + return (str + 2); | |
| 115 | + } | |
| 116 | + else if (rune <= utf8::constants::max_codepoint_3_byte) | |
| 117 | + { | |
| 118 | + // Layout: 1110xxxx 10xxxxxx 10xxxxxx | |
| 119 | + str[0] = static_cast<uint8_t>(0xe0 + (rune >> 12)); | |
| 120 | + str[1] = static_cast<uint8_t>(0x80 + ((rune >> 6) & utf8::constants::multi_byte_value_mask)); | |
| 121 | + str[2] = static_cast<uint8_t>(0x80 + (rune & utf8::constants::multi_byte_value_mask)); | |
| 122 | + return (str + 3); | |
| 123 | + } | |
| 124 | + else if (rune <= utf8::constants::max_codepoint_4_byte) | |
| 125 | + { | |
| 126 | + // Layout: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | |
| 127 | + str[0] = static_cast<uint8_t>(0xf0 + (rune >> 18)); | |
| 128 | + str[1] = static_cast<uint8_t>(0x80 + ((rune >> 12) & utf8::constants::multi_byte_value_mask)); | |
| 129 | + str[2] = static_cast<uint8_t>(0x80 + ((rune >> 6) & utf8::constants::multi_byte_value_mask)); | |
| 130 | + str[3] = static_cast<uint8_t>(0x80 + (rune & utf8::constants::multi_byte_value_mask)); | |
| 131 | + return (str + 4); | |
| 132 | + } | |
| 133 | + | |
| 134 | + assert(false && "Unsupported UTF-8 character"); | |
| 135 | + return nullptr; | |
| 136 | +} | |
| @@ -0,0 +1,197 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: vm.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cassert> | |
| 8 | +#include <memory> | |
| 9 | +#include <algorithm> | |
| 10 | +#include <disvm.h> | |
| 11 | +#include <debug.h> | |
| 12 | +#include <runtime.h> | |
| 13 | +#include <exceptions.h> | |
| 14 | +#include <builtin_module.h> | |
| 15 | +#include "scheduler.h" | |
| 16 | +#include "garbage_collector.h" | |
| 17 | + | |
| 18 | +using namespace disvm; | |
| 19 | +using namespace disvm::runtime; | |
| 20 | + | |
| 21 | +vm_t::loaded_module_t::loaded_module_t(std::unique_ptr<runtime::vm_string_t> origin, std::shared_ptr<const runtime::vm_module_t> module) | |
| 22 | + : origin{ std::move(origin) } | |
| 23 | + , module{ module } | |
| 24 | +{ | |
| 25 | + assert(this->origin != nullptr); | |
| 26 | +} | |
| 27 | + | |
| 28 | +vm_t::loaded_module_t::loaded_module_t(loaded_module_t &&other) | |
| 29 | + : origin{ std::move(other.origin) } | |
| 30 | + , module{ std::move(other.module) } | |
| 31 | +{ | |
| 32 | +} | |
| 33 | + | |
| 34 | +vm_t::loaded_module_t::~loaded_module_t() | |
| 35 | +{ | |
| 36 | + if (origin != nullptr) | |
| 37 | + origin->release(); | |
| 38 | +} | |
| 39 | + | |
| 40 | +vm_t::vm_t( | |
| 41 | + create_vm_interface_callback_t<runtime::vm_scheduler_t> create_scheduler, | |
| 42 | + create_vm_interface_callback_t<runtime::vm_garbage_collector_t> create_gc) | |
| 43 | +{ | |
| 44 | + // Initialize intrinsic type descriptors | |
| 45 | + intrinsic_type_desc::initialize(); | |
| 46 | + | |
| 47 | + // Initialize built-in modules. | |
| 48 | + builtin::initialize_builtin_modules(); | |
| 49 | + | |
| 50 | + if (create_gc == nullptr) | |
| 51 | + _gc = std::make_unique<default_garbage_collector_t>(*this); | |
| 52 | + else | |
| 53 | + _gc = create_gc(*this); | |
| 54 | + | |
| 55 | + if (create_scheduler == nullptr) | |
| 56 | + { | |
| 57 | + const auto system_thread_count = uint32_t{ 1 }; | |
| 58 | + | |
| 59 | + // The Inferno implementation defined the thread quanta as 2048 (include/interp.h) | |
| 60 | + const auto thread_quanta = uint32_t{ 2048 }; | |
| 61 | + | |
| 62 | + _scheduler = std::make_unique<default_scheduler_t>(*this, system_thread_count, thread_quanta); | |
| 63 | + } | |
| 64 | + else | |
| 65 | + { | |
| 66 | + _scheduler = create_scheduler(*this); | |
| 67 | + } | |
| 68 | +} | |
| 69 | + | |
| 70 | +vm_t::vm_t(uint32_t system_thread_count, uint32_t thread_quanta) | |
| 71 | +{ | |
| 72 | + // Initialize intrinsic type descriptors | |
| 73 | + runtime::intrinsic_type_desc::initialize(); | |
| 74 | + | |
| 75 | + // Initialize built-in modules. | |
| 76 | + builtin::initialize_builtin_modules(); | |
| 77 | + | |
| 78 | + _gc = std::make_unique<default_garbage_collector_t>(*this); | |
| 79 | + _scheduler = std::make_unique<default_scheduler_t>(*this, system_thread_count, thread_quanta); | |
| 80 | +} | |
| 81 | + | |
| 82 | +vm_t::~vm_t() | |
| 83 | +{ | |
| 84 | + // Release the scheduler first since it could be using the gc instance. | |
| 85 | + _scheduler.reset(); | |
| 86 | + _gc.reset(); | |
| 87 | +} | |
| 88 | + | |
| 89 | +namespace | |
| 90 | +{ | |
| 91 | + const runtime::vm_thread_t& _exec(runtime::vm_scheduler_t &s, std::unique_ptr<runtime::vm_module_ref_t> &entry_module_ref) | |
| 92 | + { | |
| 93 | + auto thread = std::make_unique<vm_thread_t>(*entry_module_ref, vm_t::root_vm_thread_id); | |
| 94 | + | |
| 95 | + // Now that the thread has been created and has inherited ownership of the pointer, relinquish the smart pointer's ownership. | |
| 96 | + entry_module_ref->release(); // decrement the vm alloc ref count | |
| 97 | + entry_module_ref.release(); // release the smart pointer | |
| 98 | + | |
| 99 | + return s.schedule_thread(std::move(thread)); | |
| 100 | + } | |
| 101 | +} | |
| 102 | + | |
| 103 | +const runtime::vm_thread_t& vm_t::exec(const char *path) | |
| 104 | +{ | |
| 105 | + assert(path != nullptr); | |
| 106 | + auto entry_module = load_module(path); | |
| 107 | + | |
| 108 | + auto entry_module_ref = std::make_unique<vm_module_ref_t>(entry_module); | |
| 109 | + return _exec(*_scheduler, entry_module_ref); | |
| 110 | +} | |
| 111 | + | |
| 112 | +const runtime::vm_thread_t& vm_t::exec(std::unique_ptr<runtime::vm_module_t> entry_module) | |
| 113 | +{ | |
| 114 | + assert(entry_module != nullptr); | |
| 115 | + | |
| 116 | + auto entry_module_ref = std::make_unique<vm_module_ref_t>(std::move(entry_module)); | |
| 117 | + return _exec(*_scheduler, entry_module_ref); | |
| 118 | +} | |
| 119 | + | |
| 120 | +const runtime::vm_thread_t& vm_t::fork( | |
| 121 | + const runtime::vm_thread_t &parent, | |
| 122 | + runtime::vm_module_ref_t &module_ref, | |
| 123 | + const runtime::vm_frame_t &initial_frame, | |
| 124 | + runtime::vm_pc_t initial_pc) | |
| 125 | +{ | |
| 126 | + if (initial_pc < 0 || static_cast<vm_pc_t>(module_ref.code_section.size()) <= initial_pc) | |
| 127 | + throw vm_system_exception{ "Invalid entry program counter" }; | |
| 128 | + | |
| 129 | + auto thread = std::make_unique<vm_thread_t>(module_ref, initial_frame, initial_pc, parent.get_thread_id()); | |
| 130 | + return _scheduler->schedule_thread(std::move(thread)); | |
| 131 | +} | |
| 132 | + | |
| 133 | +std::shared_ptr<const runtime::vm_module_t> vm_t::load_module(const char *path) | |
| 134 | +{ | |
| 135 | + if (path == nullptr) | |
| 136 | + throw vm_user_exception{ "Invalid module path" }; | |
| 137 | + | |
| 138 | + std::lock_guard<std::mutex> lock{ _modules_lock }; | |
| 139 | + | |
| 140 | + // Check if the module is already loaded. | |
| 141 | + auto iter = std::begin(_modules); | |
| 142 | + while (iter != std::end(_modules)) | |
| 143 | + { | |
| 144 | + if (0 == ::strcmp(iter->origin->str(), path)) | |
| 145 | + { | |
| 146 | + auto module = iter->module.lock(); | |
| 147 | + | |
| 148 | + // If an entry exists but is null, the module was loaded before. Read the module again and return. | |
| 149 | + if (module == nullptr) | |
| 150 | + { | |
| 151 | + module = std::shared_ptr<vm_module_t>{ std::move(read_module(path)) }; | |
| 152 | + iter->module = module; | |
| 153 | + | |
| 154 | + debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<\n", path); | |
| 155 | + } | |
| 156 | + | |
| 157 | + return module; | |
| 158 | + } | |
| 159 | + | |
| 160 | + ++iter; | |
| 161 | + } | |
| 162 | + | |
| 163 | + auto new_module = std::shared_ptr<vm_module_t>{}; | |
| 164 | + if (path[0] == BUILTIN_MODULE_PREFIX_CHAR) | |
| 165 | + { | |
| 166 | + new_module = std::move(builtin::get_builtin_module(path)); | |
| 167 | + } | |
| 168 | + else | |
| 169 | + { | |
| 170 | + // Read in the module | |
| 171 | + new_module = std::move(read_module(path)); | |
| 172 | + } | |
| 173 | + | |
| 174 | + auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path)); | |
| 175 | + _modules.push_front(std::move(loaded_module_t{ std::move(path_local), new_module })); | |
| 176 | + | |
| 177 | + debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "load: vm module: >>%s<<\n", path); | |
| 178 | + | |
| 179 | + return new_module; | |
| 180 | +} | |
| 181 | + | |
| 182 | +runtime::vm_scheduler_control_t &vm_t::get_scheduler_control() const | |
| 183 | +{ | |
| 184 | + return _scheduler->get_controller(); | |
| 185 | +} | |
| 186 | + | |
| 187 | +runtime::vm_garbage_collector_t &vm_t::get_garbage_collector() const | |
| 188 | +{ | |
| 189 | + return *_gc; | |
| 190 | +} | |
| 191 | + | |
| 192 | +void vm_t::spin_sleep_till_idle(std::chrono::milliseconds sleep_interval) const | |
| 193 | +{ | |
| 194 | + do | |
| 195 | + std::this_thread::sleep_for(sleep_interval); | |
| 196 | + while (!_scheduler->is_idle()); | |
| 197 | +} | |
| @@ -0,0 +1,71 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: vm_exception_handler.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <cstdint> | |
| 8 | +#include <runtime.h> | |
| 9 | +#include <vm_memory.h> | |
| 10 | +#include <exceptions.h> | |
| 11 | +#include <debug.h> | |
| 12 | + | |
| 13 | +using namespace disvm; | |
| 14 | +using namespace disvm::runtime; | |
| 15 | + | |
| 16 | +std::tuple<const handler_t *, vm_frame_t *, vm_pc_t> disvm::runtime::find_exception_handler( | |
| 17 | + const vm_stack_t &stack, | |
| 18 | + const vm_module_ref_t &faulting_module, | |
| 19 | + const vm_pc_t faulting_pc, | |
| 20 | + const vm_string_t &exception_id) | |
| 21 | +{ | |
| 22 | + auto unwind_depth = uint32_t{ 0 }; | |
| 23 | + auto current_module = &faulting_module; | |
| 24 | + auto current_pc = faulting_pc; | |
| 25 | + auto current_frame = stack.peek_frame(); | |
| 26 | + | |
| 27 | + while (current_frame != nullptr) | |
| 28 | + { | |
| 29 | + debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "begin: handler: %d\n", unwind_depth); | |
| 30 | + | |
| 31 | + auto &handler_section = current_module->module->handler_section; | |
| 32 | + | |
| 33 | + for (auto &handler : handler_section) | |
| 34 | + { | |
| 35 | + // Outside pc range of this handler | |
| 36 | + if (current_pc < handler.begin_pc || handler.end_pc <= current_pc) | |
| 37 | + continue; | |
| 38 | + | |
| 39 | + // Find the appropriate exception case | |
| 40 | + for (const auto &exception : handler.exception_table) | |
| 41 | + { | |
| 42 | + auto exception_name = exception.name.get(); | |
| 43 | + if (exception_id.compare_to(exception_name) == 0) | |
| 44 | + { | |
| 45 | + debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "end: handler: match: %d\n", unwind_depth); | |
| 46 | + return std::tuple<const handler_t *, vm_frame_t *, std::size_t>{ &handler, current_frame, exception.pc }; | |
| 47 | + } | |
| 48 | + else if (exception_name == nullptr && exception.pc != runtime_constants::invalid_program_counter) | |
| 49 | + { | |
| 50 | + debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "end: handler: fallback: %d\n", unwind_depth); | |
| 51 | + return std::tuple<const handler_t *, vm_frame_t *, std::size_t>{ &handler, current_frame, exception.pc }; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + } | |
| 55 | + | |
| 56 | + // Decrement the frame program counter. See raise instruction for details. | |
| 57 | + current_pc = static_cast<word_t>(current_frame->prev_pc() - 1); | |
| 58 | + | |
| 59 | + // Update the current module if it changes walking up the stack. | |
| 60 | + auto prev_mod_ref = current_frame->prev_module_ref(); | |
| 61 | + if (prev_mod_ref != nullptr) | |
| 62 | + current_module = prev_mod_ref; | |
| 63 | + | |
| 64 | + // Move up the stack. | |
| 65 | + current_frame = current_frame->prev_frame(); | |
| 66 | + ++unwind_depth; | |
| 67 | + } | |
| 68 | + | |
| 69 | + auto faulting_module_name = faulting_module.module->module_name->str(); | |
| 70 | + throw unhandled_user_exception{ exception_id.str(), faulting_module_name, faulting_pc }; | |
| 71 | +} | |
| @@ -0,0 +1,471 @@ | ||
| 1 | +// | |
| 2 | +// Dis VM | |
| 3 | +// File: vm_memory.cpp | |
| 4 | +// Author: arr | |
| 5 | +// | |
| 6 | + | |
| 7 | +#include <memory> | |
| 8 | +#include <atomic> | |
| 9 | +#include <bitset> | |
| 10 | +#include <mutex> | |
| 11 | +#include <condition_variable> | |
| 12 | +#include <cinttypes> | |
| 13 | +#include <vm_memory.h> | |
| 14 | +#include <debug.h> | |
| 15 | +#include <exceptions.h> | |
| 16 | + | |
| 17 | +using namespace disvm; | |
| 18 | +using namespace disvm::runtime; | |
| 19 | + | |
| 20 | +namespace | |
| 21 | +{ | |
| 22 | + std::mutex global_lock; | |
| 23 | + std::condition_variable release_lock; | |
| 24 | + | |
| 25 | + enum class lock_state_t | |
| 26 | + { | |
| 27 | + none, | |
| 28 | + pending, | |
| 29 | + active | |
| 30 | + }; | |
| 31 | + | |
| 32 | + std::atomic<uint32_t> allocating_thread_count; | |
| 33 | + std::atomic<lock_state_t> lock_state; | |
| 34 | + | |
| 35 | + struct safe_alloc final | |
| 36 | + { | |
| 37 | + safe_alloc() | |
| 38 | + { | |
| 39 | + allocating_thread_count++; | |
| 40 | + if (lock_state != lock_state_t::none) | |
| 41 | + { | |
| 42 | + allocating_thread_count--; | |
| 43 | + | |
| 44 | + std::unique_lock<std::mutex> lock{ global_lock }; | |
| 45 | + release_lock.wait(lock, [&]{ return lock_state == lock_state_t::none; }); | |
| 46 | + | |
| 47 | + allocating_thread_count++; | |
| 48 | + } | |
| 49 | + } | |
| 50 | + | |
| 51 | + ~safe_alloc() | |
| 52 | + { | |
| 53 | + assert(allocating_thread_count > 0); | |
| 54 | + allocating_thread_count--; | |
| 55 | + } | |
| 56 | + }; | |
| 57 | +} | |
| 58 | + | |
| 59 | +global_alloc_lock_t::global_alloc_lock_t(bool owner) | |
| 60 | + : _owner{ owner } | |
| 61 | +{ } | |
| 62 | + | |
| 63 | +global_alloc_lock_t::global_alloc_lock_t(global_alloc_lock_t &&other) | |
| 64 | + : _owner{ other._owner } | |
| 65 | +{ | |
| 66 | + other._owner = false; | |
| 67 | +} | |
| 68 | + | |
| 69 | +global_alloc_lock_t::~global_alloc_lock_t() | |
| 70 | +{ | |
| 71 | + if (_owner) | |
| 72 | + { | |
| 73 | + lock_state = lock_state_t::none; | |
| 74 | + release_lock.notify_all(); | |
| 75 | + } | |
| 76 | +} | |
| 77 | + | |
| 78 | +// Lock all allocations on the VM heap. | |
| 79 | +global_alloc_lock_t disvm::runtime::get_global_alloc_lock() | |
| 80 | +{ | |
| 81 | + auto expected = lock_state_t::none; | |
| 82 | + | |
| 83 | + // Block while trying to get lock | |
| 84 | + while (!lock_state.compare_exchange_strong(expected, lock_state_t::pending)) | |
| 85 | + { | |
| 86 | + std::unique_lock<std::mutex> lock{ global_lock }; | |
| 87 | + release_lock.wait(lock); | |
| 88 | + } | |
| 89 | + | |
| 90 | + while (allocating_thread_count != 0) | |
| 91 | + { | |
| 92 | + // Spin-wait for all allocating threads to stop | |
| 93 | + } | |
| 94 | + | |
| 95 | + // Transition to active lock state | |
| 96 | + lock_state = lock_state_t::active; | |
| 97 | + | |
| 98 | + return{ true }; | |
| 99 | +} | |
| 100 | + | |
| 101 | +void *disvm::runtime::alloc_memory(std::size_t amount_in_bytes) | |
| 102 | +{ | |
| 103 | + safe_alloc s; | |
| 104 | + | |
| 105 | + auto memory = std::malloc(amount_in_bytes); | |
| 106 | + if (memory == nullptr) | |
| 107 | + throw vm_system_exception{ "Out of memory" }; | |
| 108 | + | |
| 109 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 110 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "alloc: %#" PRIxPTR " %d\n", memory, amount_in_bytes); | |
| 111 | + | |
| 112 | + return memory; | |
| 113 | +} | |
| 114 | + | |
| 115 | +void *disvm::runtime::calloc_memory(std::size_t amount_in_bytes) | |
| 116 | +{ | |
| 117 | + safe_alloc s; | |
| 118 | + | |
| 119 | + auto memory = std::calloc(amount_in_bytes, sizeof(uint8_t)); | |
| 120 | + if (memory == nullptr) | |
| 121 | + throw vm_system_exception{ "Out of memory" }; | |
| 122 | + | |
| 123 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 124 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "alloc: zeroed: %#" PRIxPTR " %d\n", memory, amount_in_bytes); | |
| 125 | + | |
| 126 | + return memory; | |
| 127 | +} | |
| 128 | + | |
| 129 | +void disvm::runtime::free_memory(void *memory) | |
| 130 | +{ | |
| 131 | + if (memory == nullptr) | |
| 132 | + return; | |
| 133 | + | |
| 134 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 135 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "free: %#" PRIxPTR "\n", memory); | |
| 136 | + | |
| 137 | + std::free(memory); | |
| 138 | +} | |
| 139 | + | |
| 140 | +void disvm::runtime::init_memory(const type_descriptor_t &type_desc, void *data) | |
| 141 | +{ | |
| 142 | + assert(data != nullptr); | |
| 143 | + auto memory_word_len = std::size_t{ type_desc.size_in_bytes / sizeof(word_t) }; | |
| 144 | + if (memory_word_len == 0) | |
| 145 | + return; | |
| 146 | + | |
| 147 | + debug::assign_debug_memory(data, type_desc.size_in_bytes); | |
| 148 | + | |
| 149 | + auto memory = reinterpret_cast<word_t *>(data); | |
| 150 | + assert((memory_word_len / sizeof(word_t)) <= static_cast<std::size_t>(type_desc.size_in_bytes)); // type desc is in bytes convert len to bytes from words. | |
| 151 | + for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8) | |
| 152 | + { | |
| 153 | + const auto words8 = type_desc.pointer_map[i]; | |
| 154 | + if (words8 != 0) | |
| 155 | + { | |
| 156 | + auto flags = std::bitset<sizeof(words8) * 8>{ words8 }; | |
| 157 | + | |
| 158 | + // Enumerating the flags in reverse order so memory access is linear. | |
| 159 | + if (flags[7]) memory[0] = runtime_constants::nil; | |
| 160 | + if (flags[6]) memory[1] = runtime_constants::nil; | |
| 161 | + if (flags[5]) memory[2] = runtime_constants::nil; | |
| 162 | + if (flags[4]) memory[3] = runtime_constants::nil; | |
| 163 | + if (flags[3]) memory[4] = runtime_constants::nil; | |
| 164 | + if (flags[2]) memory[5] = runtime_constants::nil; | |
| 165 | + if (flags[1]) memory[6] = runtime_constants::nil; | |
| 166 | + if (flags[0]) memory[7] = runtime_constants::nil; | |
| 167 | + } | |
| 168 | + } | |
| 169 | +} | |
| 170 | + | |
| 171 | +namespace | |
| 172 | +{ | |
| 173 | + void dec_ref_and_free_pointer_field(pointer_t pointer_field) | |
| 174 | + { | |
| 175 | + assert(pointer_field != nullptr); | |
| 176 | + dec_ref_count_and_free(vm_alloc_t::from_allocation(pointer_field)); | |
| 177 | + } | |
| 178 | + | |
| 179 | + void add_ref_pointer_field(pointer_t pointer_field) | |
| 180 | + { | |
| 181 | + assert(pointer_field != nullptr); | |
| 182 | + vm_alloc_t::from_allocation(pointer_field)->add_ref(); | |
| 183 | + } | |
| 184 | +} | |
| 185 | + | |
| 186 | +void disvm::runtime::destroy_memory(const type_descriptor_t &type_desc, void *data) | |
| 187 | +{ | |
| 188 | + enum_pointer_fields(type_desc, data, dec_ref_and_free_pointer_field); | |
| 189 | + | |
| 190 | + debug::assign_debug_memory(data, type_desc.size_in_bytes); | |
| 191 | +} | |
| 192 | + | |
| 193 | +void disvm::runtime::inc_ref_count_in_memory(const type_descriptor_t &type_desc, void *data) | |
| 194 | +{ | |
| 195 | + if (data == nullptr) | |
| 196 | + return; | |
| 197 | + | |
| 198 | + enum_pointer_fields(type_desc, data, add_ref_pointer_field); | |
| 199 | +} | |
| 200 | + | |
| 201 | +void disvm::runtime::dec_ref_count_and_free(vm_alloc_t *alloc) | |
| 202 | +{ | |
| 203 | + if (alloc == nullptr) | |
| 204 | + return; | |
| 205 | + | |
| 206 | + auto current_ref = alloc->release(); | |
| 207 | + if (current_ref == 0) | |
| 208 | + delete alloc; | |
| 209 | +} | |
| 210 | + | |
| 211 | +const zero_memory_t vm_alloc_t::zero_memory = {}; | |
| 212 | + | |
| 213 | +void *vm_alloc_t::operator new(std::size_t sz) | |
| 214 | +{ | |
| 215 | + return alloc_memory(sz); | |
| 216 | +} | |
| 217 | + | |
| 218 | +void vm_alloc_t::operator delete(void *ptr) | |
| 219 | +{ | |
| 220 | + free_memory(ptr); | |
| 221 | +} | |
| 222 | + | |
| 223 | +vm_alloc_t *vm_alloc_t::allocate(std::shared_ptr<const type_descriptor_t> td) | |
| 224 | +{ | |
| 225 | + assert(td != nullptr); | |
| 226 | + if (td->size_in_bytes <= 0) | |
| 227 | + throw vm_system_exception{ "Invalid dynamic memory allocation size" }; | |
| 228 | + | |
| 229 | + auto mem = alloc_memory(sizeof(vm_alloc_t) + td->size_in_bytes); | |
| 230 | + auto alloc = ::new(mem)vm_alloc_t{ td }; | |
| 231 | + | |
| 232 | + // Initialize pointer types | |
| 233 | + init_memory(*td, alloc->allocation()); | |
| 234 | + | |
| 235 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm alloc: %d\n", td->size_in_bytes); | |
| 236 | + return alloc; | |
| 237 | +} | |
| 238 | + | |
| 239 | +vm_alloc_t *vm_alloc_t::allocate(std::shared_ptr<const type_descriptor_t> td, const zero_memory_t &) | |
| 240 | +{ | |
| 241 | + assert(td != nullptr); | |
| 242 | + if (td->size_in_bytes <= 0) | |
| 243 | + throw vm_system_exception{ "Invalid dynamic memory allocation size" }; | |
| 244 | + | |
| 245 | + // No need to initialize memory since it is already zeroed out. | |
| 246 | + auto mem = calloc_memory(sizeof(vm_alloc_t) + td->size_in_bytes); | |
| 247 | + auto alloc = ::new(mem)vm_alloc_t{ td }; | |
| 248 | + | |
| 249 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "initz: vm alloc: %d\n", td->size_in_bytes); | |
| 250 | + return alloc; | |
| 251 | +} | |
| 252 | + | |
| 253 | +vm_alloc_t *vm_alloc_t::copy(const vm_alloc_t &other) | |
| 254 | +{ | |
| 255 | + auto alloc_copy = vm_alloc_t::allocate(other.alloc_type); | |
| 256 | + | |
| 257 | + // Copy the current memory contents of the allocation | |
| 258 | + std::memcpy(alloc_copy->allocation(), other.allocation(), alloc_copy->alloc_type->size_in_bytes); | |
| 259 | + | |
| 260 | + // Ref count all dynamic allocations | |
| 261 | + inc_ref_count_in_memory(*alloc_copy->alloc_type, alloc_copy->allocation()); | |
| 262 | + | |
| 263 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "copy: vm alloc: %#" PRIxPTR " %#" PRIxPTR "\n", &other, alloc_copy); | |
| 264 | + return alloc_copy; | |
| 265 | +} | |
| 266 | + | |
| 267 | +vm_alloc_t *vm_alloc_t::from_allocation(pointer_t allocation) | |
| 268 | +{ | |
| 269 | + if (allocation == nullptr) | |
| 270 | + return nullptr; | |
| 271 | + | |
| 272 | + return reinterpret_cast<vm_alloc_t *>(reinterpret_cast<uint8_t *>(allocation) - sizeof(vm_alloc_t)); | |
| 273 | +} | |
| 274 | + | |
| 275 | +vm_alloc_t::vm_alloc_t(std::shared_ptr<const type_descriptor_t> td) | |
| 276 | + : alloc_type{ td } | |
| 277 | + , gc_reserved{ nullptr } | |
| 278 | + , _ref_count{ 1 } | |
| 279 | +{ | |
| 280 | + assert(alloc_type != nullptr); | |
| 281 | +} | |
| 282 | + | |
| 283 | +vm_alloc_t::~vm_alloc_t() | |
| 284 | +{ | |
| 285 | + assert(_ref_count == 0); | |
| 286 | + | |
| 287 | + auto finalizer = alloc_type->finalizer; | |
| 288 | + if (finalizer != type_descriptor_t::no_finalizer) | |
| 289 | + finalizer(this); | |
| 290 | + | |
| 291 | + // Free pointer types | |
| 292 | + destroy_memory(*alloc_type, allocation()); | |
| 293 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm alloc\n"); | |
| 294 | +} | |
| 295 | + | |
| 296 | +std::size_t vm_alloc_t::add_ref() | |
| 297 | +{ | |
| 298 | + assert(_ref_count > 0); | |
| 299 | + | |
| 300 | + auto prev_value = _ref_count.fetch_add(1, std::memory_order::memory_order_relaxed); | |
| 301 | + return (prev_value + 1); | |
| 302 | +} | |
| 303 | + | |
| 304 | +std::size_t vm_alloc_t::release() | |
| 305 | +{ | |
| 306 | + assert(_ref_count > 0); | |
| 307 | + | |
| 308 | + auto prev_value = _ref_count.fetch_sub(1, std::memory_order::memory_order_relaxed); | |
| 309 | + return (prev_value - 1); | |
| 310 | +} | |
| 311 | + | |
| 312 | +std::size_t vm_alloc_t::get_ref_count() const | |
| 313 | +{ | |
| 314 | + return _ref_count; | |
| 315 | +} | |
| 316 | + | |
| 317 | +pointer_t vm_alloc_t::allocation() const | |
| 318 | +{ | |
| 319 | + // Remove const on the this pointer so the offset can be computed. | |
| 320 | + auto non_const_this = const_cast<vm_alloc_t *>(this); | |
| 321 | + return reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(non_const_this)+sizeof(vm_alloc_t)); | |
| 322 | +} | |
| 323 | + | |
| 324 | +namespace | |
| 325 | +{ | |
| 326 | + namespace hidden_type_desc | |
| 327 | + { | |
| 328 | + std::shared_ptr<const type_descriptor_t> byte; | |
| 329 | + std::shared_ptr<const type_descriptor_t> short_word; | |
| 330 | + std::shared_ptr<const type_descriptor_t> word; | |
| 331 | + std::shared_ptr<const type_descriptor_t> short_real; | |
| 332 | + std::shared_ptr<const type_descriptor_t> real; | |
| 333 | + std::shared_ptr<const type_descriptor_t> big; | |
| 334 | + std::shared_ptr<const type_descriptor_t> pointer; | |
| 335 | + | |
| 336 | + std::shared_ptr<const type_descriptor_t> vm_array; | |
| 337 | + std::shared_ptr<const type_descriptor_t> vm_list; | |
| 338 | + std::shared_ptr<const type_descriptor_t> vm_channel; | |
| 339 | + std::shared_ptr<const type_descriptor_t> vm_string; | |
| 340 | + | |
| 341 | + std::shared_ptr<const type_descriptor_t> vm_module_ref; | |
| 342 | + std::shared_ptr<const type_descriptor_t> vm_stack; | |
| 343 | + std::shared_ptr<const type_descriptor_t> vm_thread; | |
| 344 | + } | |
| 345 | +} | |
| 346 | + | |
| 347 | +std::atomic_bool intrinsic_type_desc::is_initialized; | |
| 348 | + | |
| 349 | +void intrinsic_type_desc::initialize() | |
| 350 | +{ | |
| 351 | + auto is_false = bool{ false }; | |
| 352 | + if (!intrinsic_type_desc::is_initialized.compare_exchange_strong(is_false, true)) | |
| 353 | + return; | |
| 354 | + | |
| 355 | + hidden_type_desc::byte = type_descriptor_t::create(sizeof(byte_t)); | |
| 356 | + hidden_type_desc::short_word = type_descriptor_t::create(sizeof(short_word_t)); | |
| 357 | + hidden_type_desc::word = type_descriptor_t::create(sizeof(word_t)); | |
| 358 | + hidden_type_desc::short_real = type_descriptor_t::create(sizeof(short_real_t)); | |
| 359 | + hidden_type_desc::real = type_descriptor_t::create(sizeof(real_t)); | |
| 360 | + hidden_type_desc::big = type_descriptor_t::create(sizeof(big_t)); | |
| 361 | + hidden_type_desc::pointer = type_descriptor_t::create(sizeof(pointer_t), std::vector<byte_t>{0x80}); | |
| 362 | + | |
| 363 | + hidden_type_desc::vm_array = type_descriptor_t::create(0); | |
| 364 | + hidden_type_desc::vm_list = type_descriptor_t::create(0); | |
| 365 | + hidden_type_desc::vm_channel = type_descriptor_t::create(0); | |
| 366 | + hidden_type_desc::vm_string = type_descriptor_t::create(0); | |
| 367 | + | |
| 368 | + hidden_type_desc::vm_module_ref = type_descriptor_t::create(0); | |
| 369 | + hidden_type_desc::vm_stack = type_descriptor_t::create(0); | |
| 370 | + hidden_type_desc::vm_thread = type_descriptor_t::create(0); | |
| 371 | +} | |
| 372 | + | |
| 373 | +template<> | |
| 374 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<byte_t>() { return hidden_type_desc::byte; } | |
| 375 | + | |
| 376 | +template<> | |
| 377 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<short_word_t>() { return hidden_type_desc::short_word; } | |
| 378 | + | |
| 379 | +template<> | |
| 380 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<word_t>() { return hidden_type_desc::word; } | |
| 381 | + | |
| 382 | +template<> | |
| 383 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<short_real_t>() { return hidden_type_desc::short_real; } | |
| 384 | + | |
| 385 | +template<> | |
| 386 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<real_t>() { return hidden_type_desc::real; } | |
| 387 | + | |
| 388 | +template<> | |
| 389 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<big_t>() { return hidden_type_desc::big; } | |
| 390 | + | |
| 391 | +template<> | |
| 392 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<pointer_t>() { return hidden_type_desc::pointer; } | |
| 393 | + | |
| 394 | +template<> | |
| 395 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_array_t>() { return hidden_type_desc::vm_array; } | |
| 396 | + | |
| 397 | +template<> | |
| 398 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_list_t>() { return hidden_type_desc::vm_list; } | |
| 399 | + | |
| 400 | +template<> | |
| 401 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_channel_t>() { return hidden_type_desc::vm_channel; } | |
| 402 | + | |
| 403 | +template<> | |
| 404 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_string_t>() { return hidden_type_desc::vm_string; } | |
| 405 | + | |
| 406 | +template<> | |
| 407 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_module_ref_t>() { return hidden_type_desc::vm_module_ref; } | |
| 408 | + | |
| 409 | +template<> | |
| 410 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_stack_t>() { return hidden_type_desc::vm_stack; } | |
| 411 | + | |
| 412 | +template<> | |
| 413 | +static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_thread_t>() { return hidden_type_desc::vm_thread; } | |
| 414 | + | |
| 415 | +vm_alloc_instance_finalizer_t type_descriptor_t::no_finalizer = nullptr; | |
| 416 | + | |
| 417 | +std::shared_ptr<const type_descriptor_t> type_descriptor_t::create(const word_t size) | |
| 418 | +{ | |
| 419 | + return type_descriptor_t::create(size, 0, nullptr); | |
| 420 | +} | |
| 421 | + | |
| 422 | +std::shared_ptr<const type_descriptor_t> type_descriptor_t::create(const word_t size, const std::vector<byte_t> &pointer_map) | |
| 423 | +{ | |
| 424 | + assert(pointer_map.size() < static_cast<std::size_t>(std::numeric_limits<word_t>::max())); | |
| 425 | + return type_descriptor_t::create(size, static_cast<word_t>(pointer_map.size()), pointer_map.data()); | |
| 426 | +} | |
| 427 | + | |
| 428 | +std::shared_ptr<const type_descriptor_t> type_descriptor_t::create( | |
| 429 | + const word_t size, | |
| 430 | + const word_t pointer_map_length, | |
| 431 | + const byte_t *pointer_map, | |
| 432 | + const vm_alloc_instance_finalizer_t finalizer) | |
| 433 | +{ | |
| 434 | + struct | |
| 435 | + { | |
| 436 | + void operator()(type_descriptor_t *td) | |
| 437 | + { | |
| 438 | + free_memory(const_cast<byte_t *>(td->pointer_map)); | |
| 439 | + debug::assign_debug_pointer(const_cast<byte_t **>(&td->pointer_map)); | |
| 440 | + | |
| 441 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: type descriptor\n"); | |
| 442 | + free_memory(td); | |
| 443 | + } | |
| 444 | + } deleter; | |
| 445 | + | |
| 446 | + auto new_type_memory = alloc_memory(sizeof(type_descriptor_t)); | |
| 447 | + | |
| 448 | + byte_t *pointer_map_local = nullptr; | |
| 449 | + if (pointer_map_length > 0) | |
| 450 | + { | |
| 451 | + pointer_map_local = alloc_memory<byte_t>(pointer_map_length); | |
| 452 | + for (auto i = word_t{ 0 }; i < pointer_map_length; ++i) | |
| 453 | + pointer_map_local[i] = pointer_map[i]; | |
| 454 | + } | |
| 455 | + | |
| 456 | + auto new_type = ::new(new_type_memory) type_descriptor_t{ size, pointer_map_length, pointer_map_local, finalizer }; | |
| 457 | + return std::shared_ptr<type_descriptor_t>{ new_type, deleter }; | |
| 458 | +} | |
| 459 | + | |
| 460 | +type_descriptor_t::type_descriptor_t( | |
| 461 | + word_t size_in_bytes, | |
| 462 | + word_t map_in_bytes, | |
| 463 | + const byte_t * pointer_map, | |
| 464 | + vm_alloc_instance_finalizer_t finalizer) | |
| 465 | + : size_in_bytes{ size_in_bytes } | |
| 466 | + , map_in_bytes{ map_in_bytes } | |
| 467 | + , pointer_map{ pointer_map } | |
| 468 | + , finalizer{ finalizer } | |
| 469 | +{ | |
| 470 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: type descriptor\n"); | |
| 471 | +} | |