History

Initial public commit for Dis VM

arr committed 10 years ago

Commit f95902eNo parent (first commit)40 files · +9358 −0Browse files

Unified · Split

40 files
  1. .gitignore +23 −0
  2. readme.md +21 −0
  3. src/include/builtin_module.h +66 −0
  4. src/include/debug.h +99 −0
  5. src/include/disvm.h +98 −0
  6. src/include/exceptions.h +137 −0
  7. src/include/module_reader.h +29 −0
  8. src/include/opcodes.h +199 −0
  9. src/include/runtime.h +846 −0
  10. src/include/utf8.h +57 −0
  11. src/include/utils.h +47 −0
  12. src/include/vm_memory.h +109 −0
  13. src/sh/main.cpp +52 −0
  14. src/sh/sh.gyp +72 −0
  15. src/vm/array.cpp +188 −0
  16. src/vm/buffered_reader.h +125 −0
  17. src/vm/builtin_module.cpp +113 −0
  18. src/vm/channel.cpp +292 −0
  19. src/vm/debug.cpp +45 −0
  20. src/vm/disvm.gyp +83 −0
  21. src/vm/execution_table.cpp +1814 −0
  22. src/vm/execution_table.h +21 −0
  23. src/vm/garbage_collector.cpp +207 −0
  24. src/vm/garbage_collector.h +45 −0
  25. src/vm/list.cpp +105 −0
  26. src/vm/module_reader.cpp +800 −0
  27. src/vm/module_ref.cpp +128 −0
  28. src/vm/scheduler.cpp +332 −0
  29. src/vm/scheduler.h +102 −0
  30. src/vm/stack.cpp +314 −0
  31. src/vm/string.cpp +522 −0
  32. src/vm/sys/Sysmod.cpp +478 −0
  33. src/vm/sys/Sysmod.h +464 −0
  34. src/vm/sys/print.cpp +131 −0
  35. src/vm/sys/sys_utils.h +21 −0
  36. src/vm/thread.cpp +298 −0
  37. src/vm/utf8.cpp +136 −0
  38. src/vm/vm.cpp +197 −0
  39. src/vm/vm_exception_handler.cpp +71 −0
  40. src/vm/vm_memory.cpp +471 −0
.gitignore+23−0
@@ -0,0 +1,23 @@
1# Ignore VS noise
2Debug
3Release
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
readme.md+21−0
@@ -0,0 +1,21 @@
1Dis VM Project Overview
2========================
3
4# Build instructions
5
6Dis 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
121. Install Python 2.7
132. Get the current source for gyp
143. 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.
src/include/builtin_module.h+66−0
@@ -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
17namespace 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_
src/include/debug.h+99−0
@@ -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
14namespace 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_
src/include/disvm.h+98−0
@@ -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
17namespace 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_
src/include/exceptions.h+137−0
@@ -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
15namespace 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_
src/include/module_reader.h+29−0
@@ -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
13namespace 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_
src/include/opcodes.h+199−0
@@ -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
12namespace 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_
src/include/runtime.h+846−0
@@ -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
22namespace 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_
src/include/utf8.h+57−0
@@ -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
13namespace 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_
src/include/utils.h+47−0
@@ -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
13namespace 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) \
39inline 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)); } \
40inline TYPE &operator |= (TYPE &a, TYPE b) { a = a | b; return a; } \
41inline 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)); } \
42inline TYPE &operator &= (TYPE &a, TYPE b) { a = a & b; return a; } \
43inline 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)); } \
44inline TYPE &operator ^= (TYPE &a, TYPE b) { a = a ^ b; return a; } \
45inline TYPE operator ~ (TYPE a) { return static_cast<TYPE>(~static_cast<std::underlying_type_t<TYPE>>(a)); }
46
47#endif // _DISVM_SRC_INCLUDE_UTILS_H_
src/include/vm_memory.h+109−0
@@ -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
18namespace 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_
src/sh/main.cpp+52−0
@@ -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
13int 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
src/sh/sh.gyp+72−0
@@ -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}
src/vm/array.cpp+188−0
@@ -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
12using namespace disvm;
13using namespace disvm::runtime;
14
15std::shared_ptr<const type_descriptor_t> vm_array_t::type_desc()
16{
17 return intrinsic_type_desc::type<vm_array_t>();
18}
19
20vm_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
46vm_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
59vm_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
87vm_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
110vm_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
146std::shared_ptr<const type_descriptor_t> vm_array_t::get_element_type() const
147{
148 return _element_type;
149}
150
151word_t vm_array_t::get_length() const
152{
153 return _length;
154}
155
156pointer_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
164void 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}
src/vm/buffered_reader.h+125−0
@@ -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
16namespace 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_
src/vm/builtin_module.cpp+113−0
@@ -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
17using namespace disvm::runtime;
18using namespace disvm::runtime::builtin;
19
20namespace
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.
29extern void Sysmodinit(void);
30
31void 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
43void 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
103std::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}
src/vm/channel.cpp+292−0
@@ -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
12using namespace disvm;
13using namespace disvm::runtime;
14
15vm_request_mutex_t::vm_request_mutex_t()
16 : pending_request{ false }
17{ }
18
19vm_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
25std::shared_ptr<const type_descriptor_t> vm_channel_t::type_desc()
26{
27 return intrinsic_type_desc::type<vm_channel_t>();
28}
29
30vm_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
50vm_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
56std::shared_ptr<const type_descriptor_t> vm_channel_t::get_data_type() const
57{
58 return _data_type;
59}
60
61bool 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
132bool 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
216void 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
241bool 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
267bool 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}
src/vm/debug.cpp+45−0
@@ -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
11namespace
12{
13 std::mutex log_mutex;
14 disvm::debug::log_callback_t log_callback = nullptr;
15}
16
17std::array<bool, static_cast<std::size_t>(disvm::debug::component_trace_t::last) + 1> disvm::debug::trace_enabled = {};
18
19disvm::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
27void 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}
src/vm/disvm.gyp+83−0
@@ -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}
src/vm/execution_table.cpp+1814−0
@@ -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
22using namespace disvm;
23using namespace disvm::runtime;
24
25namespace
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
1637const 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};
src/vm/execution_table.h+21−0
@@ -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
12namespace 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_
src/vm/garbage_collector.cpp+207−0
@@ -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
15using namespace disvm;
16using namespace disvm::runtime;
17
18// Empty destructors for vm garbage collector 'interfaces'
19vm_garbage_collector_t::~vm_garbage_collector_t()
20{
21}
22
23default_garbage_collector_t::default_garbage_collector_t(vm_t &vm)
24 : _collection_epoch{ 0 }
25 , _vm{ vm }
26{
27}
28
29default_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
36namespace
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
69void 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
83void 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
93namespace
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
183bool 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}
src/vm/garbage_collector.h+45−0
@@ -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
16namespace 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_
src/vm/list.cpp+105−0
@@ -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
11using namespace disvm;
12using namespace disvm::runtime;
13
14std::shared_ptr<const type_descriptor_t> vm_list_t::type_desc()
15{
16 return intrinsic_type_desc::type<vm_list_t>();
17}
18
19vm_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
47vm_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
68std::shared_ptr<const type_descriptor_t> vm_list_t::get_element_type() const
69{
70 return _element_type;
71}
72
73word_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
86vm_list_t *vm_list_t::get_tail() const
87{
88 return _tail;
89}
90
91vm_list_t *vm_list_t::drop_tail()
92{
93 auto tail = _tail;
94 _tail = nullptr;
95 tail->release();
96 return tail;
97}
98
99pointer_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}
src/vm/module_reader.cpp+800−0
@@ -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
21using namespace disvm;
22using namespace disvm::runtime;
23using namespace disvm::format;
24
25namespace
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
697export_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
705export_function_t::~export_function_t()
706{
707 if (name != nullptr)
708 name->release();
709}
710
711import_function_t::import_function_t(import_function_t &&other)
712 : sig{ other.sig }
713 , name{ std::move(other.name) }
714{
715}
716
717import_function_t::~import_function_t()
718{
719 if (name != nullptr)
720 name->release();
721}
722
723import_vm_module_t::import_vm_module_t(import_vm_module_t &&other)
724 : functions{ std::move(other.functions) }
725{
726}
727
728exception_t::exception_t(exception_t &&other)
729 : pc{ other.pc }
730 , name{ std::move(other.name) }
731{
732}
733
734exception_t::~exception_t()
735{
736 if (name != nullptr)
737 name->release();
738}
739
740handler_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
750vm_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
759std::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
773std::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}
src/vm/module_ref.cpp+128−0
@@ -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
13using namespace disvm;
14using namespace disvm::runtime;
15
16namespace
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
66std::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
71vm_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
86vm_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
103vm_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
114bool vm_module_ref_t::is_builtin_module() const
115{
116 return _builtin_module;
117}
118
119const 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
src/vm/scheduler.cpp+332−0
@@ -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
11using namespace disvm;
12using namespace disvm::runtime;
13
14// Empty destructors for vm scheduler 'interfaces'
15vm_scheduler_t::~vm_scheduler_t()
16{
17}
18
19vm_scheduler_control_t::~vm_scheduler_control_t()
20{
21}
22
23void 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
46default_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
55default_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
82bool 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
88vm_scheduler_control_t &default_scheduler_t::get_controller() const
89{
90 return const_cast<default_scheduler_t&>(*this);
91}
92
93const 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
127void 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
163std::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
176std::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
241void 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
269void 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}
src/vm/scheduler.h+102−0
@@ -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
21namespace 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_
src/vm/stack.cpp+314−0
@@ -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
14using namespace disvm;
15using namespace disvm::runtime;
16
17namespace
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
98vm_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
114vm_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
124void 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
138pointer_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
144vm_pc_t &vm_frame_t::prev_pc() const
145{
146 return base<vm_frame_base_alloc_t>().p_pc;
147}
148
149vm_frame_t *&vm_frame_t::prev_frame() const
150{
151 return base<vm_frame_base_alloc_t>().p_fp;
152}
153
154vm_module_ref_t *&vm_frame_t::prev_module_ref() const
155{
156 return base<vm_frame_base_alloc_t>().p_mr;
157}
158
159pointer_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
168pointer_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
177vm_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
184vm_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
198vm_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
233vm_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
249vm_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
289vm_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
295void 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}
src/vm/string.cpp+522−0
@@ -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
14using namespace disvm;
15using namespace disvm::runtime;
16
17std::shared_ptr<const type_descriptor_t> vm_string_t::type_desc()
18{
19 return intrinsic_type_desc::type<vm_string_t>();
20}
21
22namespace
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
61int 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
114namespace
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
174vm_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
184vm_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
228vm_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
254vm_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
288vm_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
310vm_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
379word_t vm_string_t::get_length() const
380{
381 return _length;
382}
383
384rune_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
401void 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
481int vm_string_t::compare_to(const vm_string_t *s) const
482{
483 return vm_string_t::compare(this, s);
484}
485
486const 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}
src/vm/sys/Sysmod.cpp+478−0
@@ -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
15using namespace disvm;
16using namespace disvm::runtime;
17
18namespace
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
70std::shared_ptr<const type_descriptor_t> T_Qid;
71std::shared_ptr<const type_descriptor_t> T_Dir;
72std::shared_ptr<const type_descriptor_t> T_FD;
73std::shared_ptr<const type_descriptor_t> T_Connection;
74std::shared_ptr<const type_descriptor_t> T_FileIO;
75
76void
77Sysmodinit(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
87void
88Sys_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
94void
95Sys_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
101void
102Sys_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
108void
109Sys_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
147void
148Sys_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
185void
186Sys_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
192void
193Sys_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
199void
200Sys_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
206void
207Sys_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
213void
214Sys_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
220void
221Sys_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
227void
228Sys_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
234void
235Sys_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
241void
242Sys_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
248void
249Sys_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
255void
256Sys_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
262void
263Sys_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
269void
270Sys_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
276void
277Sys_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
283void
284Sys_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
290void
291Sys_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
297void
298Sys_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
307void
308Sys_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
314void
315Sys_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
321void
322Sys_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
328void
329Sys_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
335void
336Sys_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
342void
343Sys_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
367void
368Sys_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
374void
375Sys_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
381void
382Sys_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
388void
389Sys_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
395void
396Sys_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
402void
403Sys_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
417void
418Sys_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
424void
425Sys_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
431void
432Sys_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
438void
439Sys_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
445void
446Sys_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
452void
453Sys_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
459void
460Sys_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
466void
467Sys_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
473void
474Sys_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}
src/vm/sys/Sysmod.h+464−0
@@ -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
14using namespace disvm;
15using namespace disvm::runtime;
16
17struct Sys_Qid
18{
19 big_t path;
20 word_t vers;
21 word_t qtype;
22};
23const byte_t Sys_Qid_map[] = { 0 };
24struct 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};
39const byte_t Sys_Dir_map[] = { 0xf0, };
40struct Sys_FD
41{
42 word_t fd;
43};
44const byte_t Sys_FD_map[] = { 0 };
45struct Sys_Connection
46{
47 /* Sys_FD */ pointer_t dfd;
48 /* Sys_FD */ pointer_t cfd;
49 /* vm_string_t */ pointer_t dir;
50};
51const byte_t Sys_Connection_map[] = { 0xe0, };
52using Sys_Rread = struct { /* vm_array_t */ pointer_t t0; /* vm_string_t */ pointer_t t1; };
53#define Sys_Rread_map {0xc0,}
54using Sys_Rwrite = struct { word_t t0; /* vm_string_t */ pointer_t t1; };
55#define Sys_Rwrite_map {0x40,}
56struct Sys_FileIO
57{
58 /* vm_channel_t */ pointer_t read;
59 /* vm_channel_t */ pointer_t write;
60};
61using 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,}
63using 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,}
65const byte_t Sys_FileIO_map[] = { 0xc0, };
66void Sys_announce(vm_registers_t &r, vm_t &vm);
67struct 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};
73void Sys_aprint(vm_registers_t &r, vm_t &vm);
74struct 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};
81void Sys_bind(vm_registers_t &r, vm_t &vm);
82struct 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};
90void Sys_byte2char(vm_registers_t &r, vm_t &vm);
91struct 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};
98void Sys_char2byte(vm_registers_t &r, vm_t &vm);
99struct 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};
107void Sys_chdir(vm_registers_t &r, vm_t &vm);
108struct 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};
114void Sys_create(vm_registers_t &r, vm_t &vm);
115struct 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};
123void Sys_dial(vm_registers_t &r, vm_t &vm);
124struct 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};
131void Sys_dirread(vm_registers_t &r, vm_t &vm);
132struct 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};
138void Sys_dup(vm_registers_t &r, vm_t &vm);
139struct 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};
146void Sys_export(vm_registers_t &r, vm_t &vm);
147struct 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};
155void Sys_fauth(vm_registers_t &r, vm_t &vm);
156struct 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};
163void Sys_fd2path(vm_registers_t &r, vm_t &vm);
164struct 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};
170void Sys_fildes(vm_registers_t &r, vm_t &vm);
171struct 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};
177void Sys_file2chan(vm_registers_t &r, vm_t &vm);
178struct 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};
185void Sys_fprint(vm_registers_t &r, vm_t &vm);
186struct 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};
194void Sys_fstat(vm_registers_t &r, vm_t &vm);
195struct 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};
201void Sys_fversion(vm_registers_t &r, vm_t &vm);
202struct 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};
210void Sys_fwstat(vm_registers_t &r, vm_t &vm);
211struct 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};
219void Sys_iounit(vm_registers_t &r, vm_t &vm);
220struct 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};
226void Sys_listen(vm_registers_t &r, vm_t &vm);
227struct 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};
233void Sys_millisec(vm_registers_t &r, vm_t &vm);
234struct F_Sys_millisec : public vm_frame_base_alloc_t
235{
236 word_t* ret;
237 byte_t temps[12];
238};
239void Sys_mount(vm_registers_t &r, vm_t &vm);
240struct 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};
250void Sys_open(vm_registers_t &r, vm_t &vm);
251struct 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};
258void Sys_pctl(vm_registers_t &r, vm_t &vm);
259struct 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};
266void Sys_pipe(vm_registers_t &r, vm_t &vm);
267struct 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};
273void Sys_pread(vm_registers_t &r, vm_t &vm);
274struct 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};
284void Sys_print(vm_registers_t &r, vm_t &vm);
285struct 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};
292void Sys_pwrite(vm_registers_t &r, vm_t &vm);
293struct 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};
303void Sys_read(vm_registers_t &r, vm_t &vm);
304struct 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};
312void Sys_readn(vm_registers_t &r, vm_t &vm);
313struct 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};
321void Sys_remove(vm_registers_t &r, vm_t &vm);
322struct 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};
328void Sys_seek(vm_registers_t &r, vm_t &vm);
329struct 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};
338void Sys_sleep(vm_registers_t &r, vm_t &vm);
339struct F_Sys_sleep : public vm_frame_base_alloc_t
340{
341 word_t* ret;
342 byte_t temps[12];
343 word_t period;
344};
345void Sys_sprint(vm_registers_t &r, vm_t &vm);
346struct 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};
353void Sys_stat(vm_registers_t &r, vm_t &vm);
354struct 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};
360void Sys_stream(vm_registers_t &r, vm_t &vm);
361struct 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};
369void Sys_tokenize(vm_registers_t &r, vm_t &vm);
370struct 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};
377void Sys_unmount(vm_registers_t &r, vm_t &vm);
378struct 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};
385void Sys_utfbytes(vm_registers_t &r, vm_t &vm);
386struct 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};
393void Sys_werrstr(vm_registers_t &r, vm_t &vm);
394struct 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};
400void Sys_write(vm_registers_t &r, vm_t &vm);
401struct 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};
409void Sys_wstat(vm_registers_t &r, vm_t &vm);
410struct 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};
418const char *Sys_PATH = "$Sys";
419const word_t Sys_Maxint = 0x7fffffff;
420const word_t Sys_QTDIR = 0x80;
421const word_t Sys_QTAPPEND = 0x40;
422const word_t Sys_QTEXCL = 0x20;
423const word_t Sys_QTAUTH = 0x8;
424const word_t Sys_QTTMP = 0x4;
425const word_t Sys_QTFILE = 0x0;
426const word_t Sys_ATOMICIO = 0x2000;
427const word_t Sys_SEEKSTART = 0x0;
428const word_t Sys_SEEKRELA = 0x1;
429const word_t Sys_SEEKEND = 0x2;
430const word_t Sys_NAMEMAX = 0x100;
431const word_t Sys_ERRMAX = 0x80;
432const word_t Sys_WAITLEN = 0xc0;
433const word_t Sys_OREAD = 0x0;
434const word_t Sys_OWRITE = 0x1;
435const word_t Sys_ORDWR = 0x2;
436const word_t Sys_OTRUNC = 0x10;
437const word_t Sys_ORCLOSE = 0x40;
438const word_t Sys_OEXCL = 0x1000;
439const word_t Sys_DMDIR = 0x80000000;
440const word_t Sys_DMAPPEND = 0x40000000;
441const word_t Sys_DMEXCL = 0x20000000;
442const word_t Sys_DMAUTH = 0x8000000;
443const word_t Sys_DMTMP = 0x4000000;
444const word_t Sys_MREPL = 0x0;
445const word_t Sys_MBEFORE = 0x1;
446const word_t Sys_MAFTER = 0x2;
447const word_t Sys_MCREATE = 0x4;
448const word_t Sys_MCACHE = 0x10;
449const word_t Sys_NEWFD = 0x1;
450const word_t Sys_FORKFD = 0x2;
451const word_t Sys_NEWNS = 0x4;
452const word_t Sys_FORKNS = 0x8;
453const word_t Sys_NEWPGRP = 0x10;
454const word_t Sys_NODEVS = 0x20;
455const word_t Sys_NEWENV = 0x40;
456const word_t Sys_FORKENV = 0x80;
457const word_t Sys_EXPWAIT = 0x0;
458const word_t Sys_EXPASYNC = 0x1;
459const word_t Sys_UTFmax = 0x4;
460const word_t Sys_UTFerror = 0xfffd;
461const word_t Sys_Runemax = 0x10ffff;
462const word_t Sys_Runemask = 0x1fffff;
463
464#endif // _DISVM_SRC_VM_SYS_SYSMOD_H_
src/vm/sys/print.cpp+131−0
@@ -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
12using namespace disvm::runtime;
13using namespace disvm::runtime::utf8;
14
15// Shamelessly adopted and altered from Inferno implementation (xprint - libinterp/runt.c)
16// [TODO] Handle UTF-8
17word_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
122word_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}
src/vm/sys/sys_utils.h+21−0
@@ -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.
15disvm::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.
19disvm::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_
src/vm/thread.cpp+298−0
@@ -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
17using namespace disvm;
18using namespace disvm::runtime;
19
20namespace
21{
22 // Update VM registers based on the instruction.
23 void decode_address(const vm_exec_op_t &inst, vm_registers_t &reg)
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
128vm_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
148vm_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
157std::shared_ptr<const type_descriptor_t> vm_thread_t::type_desc()
158{
159 return intrinsic_type_desc::type<vm_thread_t>();
160}
161
162vm_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
184vm_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
212vm_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
217vm_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
280vm_thread_state_t vm_thread_t::get_state() const
281{
282 return _registers.current_thread_state;
283}
284
285uint32_t vm_thread_t::get_thread_id() const
286{
287 return _thread_id;
288}
289
290uint32_t vm_thread_t::get_parent_thread_id() const
291{
292 return _parent_thread_id;
293}
294
295const vm_registers_t& vm_thread_t::get_registers() const
296{
297 return _registers;
298}
src/vm/utf8.cpp+136−0
@@ -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
15using namespace disvm::runtime;
16
17namespace
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.
43utf8::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.
57std::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
76std::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
100uint8_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}
src/vm/vm.cpp+197−0
@@ -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
18using namespace disvm;
19using namespace disvm::runtime;
20
21vm_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
28vm_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
34vm_t::loaded_module_t::~loaded_module_t()
35{
36 if (origin != nullptr)
37 origin->release();
38}
39
40vm_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
70vm_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
82vm_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
89namespace
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
103const 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
112const 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
120const 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
133std::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
182runtime::vm_scheduler_control_t &vm_t::get_scheduler_control() const
183{
184 return _scheduler->get_controller();
185}
186
187runtime::vm_garbage_collector_t &vm_t::get_garbage_collector() const
188{
189 return *_gc;
190}
191
192void 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}
src/vm/vm_exception_handler.cpp+71−0
@@ -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
13using namespace disvm;
14using namespace disvm::runtime;
15
16std::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}
src/vm/vm_memory.cpp+471−0
@@ -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
17using namespace disvm;
18using namespace disvm::runtime;
19
20namespace
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
59global_alloc_lock_t::global_alloc_lock_t(bool owner)
60 : _owner{ owner }
61{ }
62
63global_alloc_lock_t::global_alloc_lock_t(global_alloc_lock_t &&other)
64 : _owner{ other._owner }
65{
66 other._owner = false;
67}
68
69global_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.
79global_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
101void *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
115void *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
129void 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
140void 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
171namespace
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
186void 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
193void 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
201void 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
211const zero_memory_t vm_alloc_t::zero_memory = {};
212
213void *vm_alloc_t::operator new(std::size_t sz)
214{
215 return alloc_memory(sz);
216}
217
218void vm_alloc_t::operator delete(void *ptr)
219{
220 free_memory(ptr);
221}
222
223vm_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
239vm_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
253vm_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
267vm_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
275vm_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
283vm_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
296std::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
304std::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
312std::size_t vm_alloc_t::get_ref_count() const
313{
314 return _ref_count;
315}
316
317pointer_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
324namespace
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
347std::atomic_bool intrinsic_type_desc::is_initialized;
348
349void 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
373template<>
374static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<byte_t>() { return hidden_type_desc::byte; }
375
376template<>
377static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<short_word_t>() { return hidden_type_desc::short_word; }
378
379template<>
380static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<word_t>() { return hidden_type_desc::word; }
381
382template<>
383static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<short_real_t>() { return hidden_type_desc::short_real; }
384
385template<>
386static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<real_t>() { return hidden_type_desc::real; }
387
388template<>
389static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<big_t>() { return hidden_type_desc::big; }
390
391template<>
392static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<pointer_t>() { return hidden_type_desc::pointer; }
393
394template<>
395static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_array_t>() { return hidden_type_desc::vm_array; }
396
397template<>
398static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_list_t>() { return hidden_type_desc::vm_list; }
399
400template<>
401static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_channel_t>() { return hidden_type_desc::vm_channel; }
402
403template<>
404static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_string_t>() { return hidden_type_desc::vm_string; }
405
406template<>
407static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_module_ref_t>() { return hidden_type_desc::vm_module_ref; }
408
409template<>
410static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_stack_t>() { return hidden_type_desc::vm_stack; }
411
412template<>
413static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_thread_t>() { return hidden_type_desc::vm_thread; }
414
415vm_alloc_instance_finalizer_t type_descriptor_t::no_finalizer = nullptr;
416
417std::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
422std::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
428std::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
460type_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}