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 | } | ||