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
2+Debug
3+Release
4+*suo
5+*filters
6+*sdf
7+*sln
8+*proj
9+*.VC.opendb
10+*.db
11+*.user
12+
13+# Ignore all log files
14+*.log
15+
16+# Ignore VIM temporary files
17+*~
18+*.swp
19+
20+# Ignore limbo binary files
21+*.dis
22+*.s
23+*.sbl
readme.md+21−0
@@ -0,0 +1,21 @@
1+Dis VM Project Overview
2+========================
3+
4+# Build instructions
5+
6+Dis VM build scripts can be generated using gyp (Generate-Your-Project)
7+
8+### Requirements
9+ * gyp - http://gyp.gsrc.io
10+ * Python 2.7 - http://www.python.org/download/releases/2.7/
11+
12+1. Install Python 2.7
13+2. Get the current source for gyp
14+3. Generate build file for your platform
15+ * Windows - gyp.bat src/sh/sh.gyp
16+ * Other - gyp_main.py src/sh/sh.gyp
17+
18+# Source Comment Definitions:
19+[TODO] - Work to be done in the future.
20+[SPEC] - A departure or undocumented assumption in the Dis VM specification.
21+[PERF] - Potential for performance improvement.
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+
17+namespace disvm
18+{
19+ namespace runtime
20+ {
21+ namespace builtin
22+ {
23+ // Called to initialize built-in modules.
24+ // This will be called once per process hosting a VM.
25+ void initialize_builtin_modules();
26+
27+ // Defined as 'Runtab' in Inferno (limbo/stubs.c)
28+ struct vm_runtab_t
29+ {
30+ // Function identification
31+ char *name;
32+ uint32_t sig;
33+
34+ // Function pointer
35+ vm_exec_t fn;
36+
37+ // Function frame type
38+ word_t frame_size;
39+ word_t frame_pointer_count;
40+ byte_t frame_pointer_map[16];
41+ };
42+
43+ // Called by the built-in module during initialization
44+ void register_module_exports(const char *name, word_t table_length, const vm_runtab_t *module_runtime_table);
45+
46+ // Get the supplied built-in module.
47+ std::shared_ptr<vm_module_t> get_builtin_module(const char *name);
48+ }
49+
50+ // [SPEC] All allocated frames have the following base layout.
51+ // See 'struct Frame' in Inferno (include/interp.h)
52+ struct vm_frame_base_alloc_t
53+ {
54+ vm_pc_t p_pc; // Previous PC
55+ vm_frame_t *p_fp; // Previous frame pointer
56+ vm_module_ref_t *p_mr; // Previous module ref
57+ pointer_t reserved; // Reserved for VM
58+ };
59+
60+ static_assert(
61+ sizeof(vm_frame_base_alloc_t) == runtime_constants::default_register_count * runtime_constants::default_register_size_bytes,
62+ "Default register allocation should match base allocation type");
63+ }
64+}
65+
66+#endif // _DISVM_SRC_INCLUDE_BUILTIN_MODULE_H_
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+
14+namespace disvm
15+{
16+ namespace debug
17+ {
18+ // Components that have tracing
19+ enum class component_trace_t
20+ {
21+ builtin,
22+ module,
23+ thread,
24+ stack,
25+ channel,
26+ exception,
27+ memory, // Noisy component
28+ addressing, // Noisy component [DEBUG build only]
29+
30+ // Potentially logged from a VM extension.
31+ scheduler,
32+ garbage_collector,
33+
34+ // Tracking purposes
35+ last = garbage_collector
36+ };
37+
38+ // Trace component enable state.
39+ extern std::array<bool, static_cast<std::size_t>(disvm::debug::component_trace_t::last) + 1> trace_enabled;
40+
41+ // Set tracing for a component
42+ inline void set_component_tracing(const component_trace_t c, bool is_enabled)
43+ {
44+ trace_enabled[static_cast<const std::size_t>(c)] = is_enabled;
45+ }
46+
47+ // Check if tracing is enabled on a component (compile time optimization)
48+ template<component_trace_t C>
49+ bool is_component_tracing_enabled()
50+ {
51+ return trace_enabled[static_cast<const std::size_t>(C)];
52+ }
53+
54+ // Check if tracing is enabled on a component
55+ inline bool is_component_tracing_enabled(const component_trace_t c)
56+ {
57+ return trace_enabled[static_cast<const std::size_t>(c)];
58+ }
59+
60+ // Message log level
61+ enum class log_level_t
62+ {
63+ warning,
64+ debug,
65+ };
66+
67+ using log_callback_t = void(*)(const component_trace_t origin, const log_level_t level, const char *msg_fmt, std::va_list args);
68+
69+ // Set the logging callback
70+ // Returns the previous callback.
71+ log_callback_t set_logging_callback(log_callback_t cb);
72+
73+ // Log a message
74+ void log_msg(const component_trace_t origin, const log_level_t level, const char *msg_fmt, ...);
75+
76+ //
77+ // Memory debugging
78+ //
79+
80+ inline void assign_debug_memory(void *dest, std::size_t size_in_bytes)
81+ {
82+#ifndef NDEBUG
83+ const auto debug_memory = int{ 0xda }; // Dante Alighieri's initials
84+ std::memset(dest, debug_memory, size_in_bytes);
85+#endif
86+ }
87+
88+ template<typename T>
89+ void assign_debug_pointer(T **t)
90+ {
91+#ifndef NDEBUG
92+ assert(t != nullptr);
93+ *t = reinterpret_cast<T *>(0xbada110c);
94+#endif
95+ }
96+ }
97+}
98+
99+#endif // _DISVM_SRC_INCLUDE_DEBUG_H_
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+
17+namespace disvm
18+{
19+ // Read in the module at the supplied path
20+ std::unique_ptr<runtime::vm_module_t> read_module(const char *path);
21+
22+ // Read in a module from the supplied stream
23+ std::unique_ptr<runtime::vm_module_t> read_module(std::istream &data);
24+
25+ template <typename C>
26+ using create_vm_interface_callback_t = std::function<std::unique_ptr<C>(vm_t &)>;
27+
28+ // Virtual machine
29+ class vm_t final
30+ {
31+ public: // static
32+ static const auto root_vm_thread_id = uint32_t{ 0 };
33+
34+ public:
35+ explicit vm_t(
36+ create_vm_interface_callback_t<runtime::vm_scheduler_t> create_scheduler = nullptr,
37+ create_vm_interface_callback_t<runtime::vm_garbage_collector_t> create_gc = nullptr);
38+
39+ explicit vm_t(uint32_t thread_pool_size, uint32_t thread_quanta);
40+
41+ ~vm_t();
42+
43+ public:
44+ // Begin execution of the module at the supplied path
45+ const runtime::vm_thread_t& exec(const char *path);
46+
47+ // Begin execution of the supplied module
48+ const runtime::vm_thread_t& exec(std::unique_ptr<runtime::vm_module_t> entry_module);
49+
50+ // Begin execution of a child thread in the supplied module at the indicated program counter
51+ const runtime::vm_thread_t& fork(
52+ const runtime::vm_thread_t &parent,
53+ runtime::vm_module_ref_t &module_ref,
54+ const runtime::vm_frame_t &initial_frame,
55+ runtime::vm_pc_t initial_pc);
56+
57+ // Load the module at the supplied path into the virtual machine
58+ std::shared_ptr<const runtime::vm_module_t> load_module(const char *path);
59+
60+ // Access the scheduler control for this VM.
61+ runtime::vm_scheduler_control_t &get_scheduler_control() const;
62+
63+ // Access the garbage collector for this VM.
64+ runtime::vm_garbage_collector_t &get_garbage_collector() const;
65+
66+ // Spin and sleep until the VM is idle, then return.
67+ // Idle is defined as no vm threads executing, scheduled to be executed, or blocked.
68+ void spin_sleep_till_idle(std::chrono::milliseconds sleep_interval) const;
69+
70+ private:
71+ // Loaded module reference
72+ class loaded_module_t final
73+ {
74+ public:
75+ loaded_module_t(std::unique_ptr<runtime::vm_string_t> origin, std::shared_ptr<const runtime::vm_module_t> module);
76+ loaded_module_t(const loaded_module_t &) = delete;
77+ loaded_module_t &operator=(const loaded_module_t &) = delete;
78+
79+ loaded_module_t(loaded_module_t &&);
80+ ~loaded_module_t();
81+
82+ // Origin of module
83+ std::unique_ptr<runtime::vm_string_t> origin;
84+
85+ // Weak reference to a loaded module
86+ std::weak_ptr<const runtime::vm_module_t> module;
87+ };
88+
89+ using loaded_modules_t = std::forward_list<loaded_module_t>;
90+ std::mutex _modules_lock;
91+ loaded_modules_t _modules;
92+
93+ std::unique_ptr<runtime::vm_scheduler_t> _scheduler;
94+ std::unique_ptr<runtime::vm_garbage_collector_t> _gc;
95+ };
96+}
97+
98+#endif // _DISVM_SRC_INCLUDE_DISVM_H_
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+
15+namespace disvm
16+{
17+ namespace runtime
18+ {
19+ class vm_user_exception : public std::runtime_error
20+ {
21+ public:
22+ explicit vm_user_exception(const char *message)
23+ : std::runtime_error(message)
24+ { }
25+ };
26+
27+ class vm_system_exception : public std::runtime_error
28+ {
29+ public:
30+ explicit vm_system_exception(const char *message)
31+ : std::runtime_error(message)
32+ { }
33+ };
34+
35+ class vm_module_exception : public vm_system_exception
36+ {
37+ public:
38+ explicit vm_module_exception(const char *message)
39+ : vm_system_exception(message)
40+ { }
41+ };
42+
43+ class module_reader_exception final : public vm_module_exception
44+ {
45+ public:
46+ explicit module_reader_exception()
47+ : vm_module_exception("Invalid module")
48+ { }
49+
50+ explicit module_reader_exception(const char *message)
51+ : vm_module_exception(message)
52+ { }
53+ };
54+
55+ class divide_by_zero final : public vm_user_exception
56+ {
57+ public:
58+ explicit divide_by_zero()
59+ : vm_user_exception("Divide by 0")
60+ { }
61+ };
62+
63+ class invalid_utf8 : public vm_user_exception
64+ {
65+ public:
66+ explicit invalid_utf8()
67+ : vm_user_exception("Invalid UTF-8 string")
68+ { }
69+ };
70+
71+ class dereference_nil final : public vm_user_exception
72+ {
73+ public:
74+ explicit dereference_nil()
75+ : vm_user_exception("Dereference of nil value")
76+ , operation{ nullptr }
77+ { }
78+
79+ explicit dereference_nil(const char *operation)
80+ : vm_user_exception("Dereference of nil value")
81+ , operation{ operation }
82+ { }
83+
84+ const char *operation;
85+ };
86+
87+ class type_violation : public vm_user_exception
88+ {
89+ public:
90+ explicit type_violation()
91+ : vm_user_exception("Inconsistent types in operation")
92+ { }
93+ };
94+
95+ class out_of_range_memory final : public vm_user_exception
96+ {
97+ public:
98+ explicit out_of_range_memory()
99+ : vm_user_exception("Out of range access")
100+ , invalid_value{ 0 }
101+ , range_defined{ false }
102+ , valid_max{ 0 }
103+ , valid_min{ 0 }
104+ { }
105+
106+ explicit out_of_range_memory(word_t valid_min, word_t valid_max, word_t invalid_valid)
107+ : vm_user_exception("Out of range access")
108+ , invalid_value{ invalid_valid }
109+ , range_defined{ true }
110+ , valid_max{ valid_max }
111+ , valid_min{ valid_min }
112+ { }
113+
114+ const bool range_defined;
115+ const word_t valid_min;
116+ const word_t valid_max;
117+ const word_t invalid_value;
118+ };
119+
120+ class unhandled_user_exception final : public vm_user_exception
121+ {
122+ public:
123+ explicit unhandled_user_exception(const char *exception_id, const char *module_name, vm_pc_t current_pc)
124+ : vm_user_exception("Unhandled user exception")
125+ , exception_id{ exception_id }
126+ , module_name{ module_name }
127+ , program_counter{ current_pc }
128+ { }
129+
130+ const char *exception_id;
131+ const char *module_name;
132+ const vm_pc_t program_counter;
133+ };
134+ }
135+}
136+
137+#endif // _DISVM_SRC_INCLUDE_EXCEPTIONS_H_
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+
13+namespace disvm
14+{
15+ namespace format
16+ {
17+ using operand_t = int32_t;
18+ static_assert(sizeof(operand_t) == sizeof(runtime::word_t), "Operand and word should be the same size");
19+ static_assert(sizeof(operand_t) == sizeof(runtime::vm_pc_t), "Operand and VM program counter type should be the same size");
20+
21+ struct magic_number_constants final
22+ {
23+ static const auto xmagic = operand_t{ 819248 }; // Normal module (0xC8030)
24+ static const auto smagic = operand_t{ 923426 }; // Signed module (0xE1722)
25+ };
26+ }
27+}
28+
29+#endif // _DISVM_SRC_INCLUDE_MODULE_READER_H_
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+
12+namespace disvm
13+{
14+ // Instruction codes for the Dis VM
15+ enum class opcode_t
16+ {
17+ invalid = 0,
18+ alt,
19+ nbalt,
20+ goto_,
21+ call,
22+ frame,
23+ spawn,
24+ runt,
25+ load,
26+ mcall,
27+ mspawn,
28+ mframe,
29+ ret,
30+ jmp,
31+ casew,
32+ exit,
33+ new_,
34+ newa,
35+ newcb,
36+ newcw,
37+ newcf,
38+ newcp,
39+ newcm,
40+ newcmp,
41+ send,
42+ recv,
43+ consb,
44+ consw,
45+ consp,
46+ consf,
47+ consm,
48+ consmp = 0x1f,
49+ headb,
50+ headw,
51+ headp,
52+ headf,
53+ headm,
54+ headmp,
55+ tail,
56+ lea,
57+ indx,
58+ movp,
59+ movm,
60+ movmp,
61+ movb,
62+ movw,
63+ movf,
64+ cvtbw,
65+ cvtwb,
66+ cvtfw,
67+ cvtwf,
68+ cvtca,
69+ cvtac,
70+ cvtwc,
71+ cvtcw,
72+ cvtfc,
73+ cvtcf,
74+ addb,
75+ addw,
76+ addf,
77+ subb,
78+ subw,
79+ subf,
80+ mulb = 0x3f,
81+ mulw,
82+ mulf,
83+ divb,
84+ divw,
85+ divf,
86+ modw,
87+ modb,
88+ andb,
89+ andw,
90+ orb,
91+ orw,
92+ xorb,
93+ xorw,
94+ shlb,
95+ shlw,
96+ shrb,
97+ shrw,
98+ insc,
99+ indc,
100+ addc,
101+ lenc,
102+ lena,
103+ lenl,
104+ beqb,
105+ bneb,
106+ bltb,
107+ bleb,
108+ bgtb,
109+ bgeb,
110+ beqw,
111+ bnew,
112+ bltw = 0x5f,
113+ blew,
114+ bgtw,
115+ bgew,
116+ beqf,
117+ bnef,
118+ bltf,
119+ blef,
120+ bgtf,
121+ bgef,
122+ beqc,
123+ bnec,
124+ bltc,
125+ blec,
126+ bgtc,
127+ bgec,
128+ slicea,
129+ slicela,
130+ slicec,
131+ indw,
132+ indf,
133+ indb,
134+ negf,
135+ movl,
136+ addl,
137+ subl,
138+ divl,
139+ modl,
140+ mull,
141+ andl,
142+ orl,
143+ xorl,
144+ shll = 0x7f,
145+ shrl,
146+ bnel,
147+ bltl,
148+ blel,
149+ bgtl,
150+ bgel,
151+ beql,
152+ cvtlf,
153+ cvtfl,
154+ cvtlw,
155+ cvtwl,
156+ cvtlc,
157+ cvtcl,
158+ headl,
159+ consl,
160+ newcl,
161+ casec,
162+ indl,
163+ movpc,
164+ tcmp,
165+ mnewz,
166+ cvtrf,
167+ cvtfr,
168+ cvtws,
169+ cvtsw,
170+ lsrw,
171+ lsrl,
172+ eclr, // not used
173+ newz,
174+ newaz,
175+ raise,
176+ casel,
177+ mulx,
178+ divx,
179+ cvtxx,
180+ mulx0,
181+ divx0,
182+ cvtxx0,
183+ mulx1,
184+ divx1,
185+ cvtxx1,
186+ cvtfx,
187+ cvtxf,
188+ expw,
189+ expl,
190+ expf,
191+ self,
192+
193+ // markers
194+ first_opcode = invalid,
195+ last_opcode = self
196+ };
197+}
198+
199+#endif // _DISVM_SRC_INCLUDE_OPCODES_H_
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+
22+namespace disvm
23+{
24+ // VM forward declation
25+ class vm_t;
26+
27+ namespace runtime
28+ {
29+ //
30+ // VM primitive data type definitions
31+ //
32+
33+ using byte_t = uint8_t;
34+ using short_word_t = int16_t;
35+ using word_t = int32_t;
36+ using short_real_t = float;
37+ using real_t = double;
38+ using big_t = int64_t;
39+ using pointer_t = std::size_t *;
40+ using vm_pc_t = word_t;
41+
42+ //
43+ // VM memory management
44+ //
45+
46+ // VM forward declation
47+ class vm_alloc_t;
48+
49+ // Finalizer callback for allocations.
50+ using vm_alloc_instance_finalizer_t = void(*)(vm_alloc_t *);
51+
52+ // VM type description
53+ class type_descriptor_t final
54+ {
55+ public: // static
56+ static vm_alloc_instance_finalizer_t no_finalizer;
57+
58+ // Create a type descriptor for type of the supplied size and pointers based on the supplied map.
59+ static std::shared_ptr<const type_descriptor_t> create(const word_t size);
60+ static std::shared_ptr<const type_descriptor_t> create(const word_t size, const std::vector<byte_t> &pointer_map);
61+ static std::shared_ptr<const type_descriptor_t> create(
62+ const word_t size,
63+ const word_t pointer_map_length,
64+ const byte_t *pointer_map,
65+ vm_alloc_instance_finalizer_t finalizer = type_descriptor_t::no_finalizer);
66+
67+ private:
68+ type_descriptor_t(word_t size_in_bytes, word_t map_in_bytes, const byte_t * pointer_map, vm_alloc_instance_finalizer_t finalizer);
69+
70+ public:
71+ type_descriptor_t(const type_descriptor_t&) = delete;
72+ type_descriptor_t& operator=(const type_descriptor_t&) = delete;
73+
74+ const word_t size_in_bytes;
75+ const word_t map_in_bytes;
76+ const byte_t * const pointer_map;
77+ const vm_alloc_instance_finalizer_t finalizer;
78+ };
79+
80+ // Access to type descriptors for VM intrinsic types.
81+ class intrinsic_type_desc final
82+ {
83+ private:
84+ friend vm_t;
85+ static std::atomic_bool is_initialized;
86+ static void initialize();
87+
88+ public:
89+ // Templated type descriptor getter
90+ template<typename IntrinsicType>
91+ static std::shared_ptr<const type_descriptor_t> type();
92+ };
93+
94+ // Type used to indicate allocated memory should be zeroed out.
95+ struct zero_memory_t
96+ { };
97+
98+ // VM memory allocation
99+ class vm_alloc_t
100+ {
101+ public: // static
102+ static const zero_memory_t zero_memory;
103+
104+ static void *operator new(std::size_t sz);
105+ static void operator delete(void *ptr);
106+
107+ static vm_alloc_t *allocate(std::shared_ptr<const type_descriptor_t> td);
108+ static vm_alloc_t *allocate(std::shared_ptr<const type_descriptor_t> td, const zero_memory_t &);
109+ static vm_alloc_t *copy(const vm_alloc_t &other);
110+ static vm_alloc_t *from_allocation(pointer_t allocation);
111+
112+ template <typename T>
113+ static T *from_allocation(pointer_t allocation)
114+ {
115+ auto alloc_inst = vm_alloc_t::from_allocation(allocation);
116+ assert(alloc_inst == nullptr || alloc_inst->alloc_type == intrinsic_type_desc::type<T>());
117+ return dynamic_cast<T *>(alloc_inst);
118+ }
119+
120+ protected:
121+ vm_alloc_t(std::shared_ptr<const type_descriptor_t> td);
122+
123+ public:
124+ virtual ~vm_alloc_t();
125+
126+ std::size_t add_ref();
127+ std::size_t release();
128+ std::size_t get_ref_count() const;
129+
130+ std::shared_ptr<const type_descriptor_t> alloc_type;
131+
132+ // Reserved for use by the garbage collector.
133+ // This should not be accessed by any other component.
134+ pointer_t gc_reserved;
135+
136+ public:
137+ pointer_t allocation() const;
138+
139+ private:
140+ std::atomic<std::size_t> _ref_count;
141+ };
142+
143+ //
144+ // VM intrinsic type definitions
145+ //
146+
147+ // Type to contain the maximum UTF-8 character value
148+ using rune_t = uint32_t;
149+
150+ // VM string type
151+ class vm_string_t final : public vm_alloc_t
152+ {
153+ public: //static
154+ static std::shared_ptr<const type_descriptor_t> type_desc();
155+
156+ // Compare two string objects
157+ static int compare(const vm_string_t *s1, const vm_string_t *s2);
158+
159+ public:
160+ vm_string_t();
161+ vm_string_t(std::size_t encoded_str_len, const uint8_t *encoded_str);
162+ vm_string_t(const vm_string_t &s1, const vm_string_t &s2);
163+ vm_string_t(const vm_string_t &other, word_t begin_index, word_t end_index);
164+ vm_string_t(const vm_string_t &) = delete;
165+ ~vm_string_t();
166+
167+ // Returns this string.
168+ vm_string_t &append(const vm_string_t &other);
169+
170+ word_t get_length() const;
171+
172+ rune_t get_rune(word_t index) const;
173+
174+ void set_rune(word_t index, rune_t value);
175+
176+ int compare_to(const vm_string_t *s) const;
177+
178+ const char *str() const;
179+
180+ private:
181+ word_t _length;
182+ word_t _length_max;
183+ mutable std::mutex _encoded_str_lock;
184+ mutable char *_encoded_str;
185+ mutable union string_memory_t
186+ {
187+ uint8_t local[16]; // Optimization for short strings
188+ uint8_t *alloc;
189+ } _mem;
190+
191+ uint8_t _character_size; // 1 byte (ASCII) or 4 bytes (multi-byte)
192+ bool _is_alloc;
193+ };
194+
195+ // VM array type
196+ class vm_array_t final : public vm_alloc_t
197+ {
198+ public: //static
199+ static std::shared_ptr<const type_descriptor_t> type_desc();
200+
201+ public:
202+ vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t count);
203+ vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t count, const zero_memory_t&);
204+ vm_array_t(vm_array_t *original, word_t begin_index, word_t count);
205+ vm_array_t(const vm_string_t *s);
206+ ~vm_array_t();
207+
208+ std::shared_ptr<const type_descriptor_t> get_element_type() const;
209+
210+ word_t get_length() const;
211+
212+ pointer_t at(word_t index) const;
213+
214+ template<typename T>
215+ T& at(word_t index) const
216+ {
217+ return *reinterpret_cast<T *>(at(index));
218+ }
219+
220+ // Copy the contents of the source array into this array starting at index
221+ void copy_from(const vm_array_t &source, word_t this_begin_index);
222+
223+ private:
224+ std::shared_ptr<const type_descriptor_t> _element_type;
225+ vm_array_t *_original;
226+ word_t _length;
227+ byte_t *_arr;
228+ };
229+
230+ // VM list type
231+ class vm_list_t final : public vm_alloc_t
232+ {
233+ public: //static
234+ static std::shared_ptr<const type_descriptor_t> type_desc();
235+
236+ public:
237+ vm_list_t(std::shared_ptr<const type_descriptor_t> td, vm_list_t *tail = nullptr);
238+ ~vm_list_t();
239+
240+ std::shared_ptr<const type_descriptor_t> get_element_type() const;
241+
242+ word_t get_length() const;
243+
244+ vm_list_t* get_tail() const;
245+
246+ vm_list_t* drop_tail();
247+
248+ // Returns a pointer to the value
249+ pointer_t value() const;
250+
251+ private:
252+ std::shared_ptr<const type_descriptor_t> _element_type;
253+ vm_list_t *_tail;
254+
255+ // Utilizing a union so that all value types can
256+ // be stored without allocating any extra memory.
257+ mutable union element_memory_t
258+ {
259+ pointer_t alloc;
260+ uint64_t local;
261+ } _mem;
262+
263+ const bool _is_alloc;
264+ };
265+
266+ // VM forward declation
267+ class vm_channel_t;
268+
269+ // VM request mutex
270+ struct vm_request_mutex_t
271+ {
272+ vm_request_mutex_t();
273+
274+ std::mutex pending_request_lock;
275+ bool pending_request;
276+ };
277+
278+ // VM channel request
279+ class vm_channel_request_t final
280+ {
281+ public:
282+ // Create a request using the ID of the requesting thread and an associated request mutex.
283+ vm_channel_request_t(uint32_t thread_id, vm_request_mutex_t &request_mutex);
284+
285+ uint32_t thread_id;
286+
287+ // Callback used to indicate the request was handled in the supplied channel.
288+ // This is only called in the event the request was not immediately serviced.
289+ std::function<void(const vm_channel_t &)> request_handled_callback;
290+
291+ // Data of the request
292+ pointer_t data;
293+
294+ std::reference_wrapper<vm_request_mutex_t> request_mutex;
295+ };
296+
297+ // VM channel type
298+ class vm_channel_t final : public vm_alloc_t
299+ {
300+ public: //static
301+ static std::shared_ptr<const type_descriptor_t> type_desc();
302+
303+ public:
304+ vm_channel_t(
305+ std::shared_ptr<const type_descriptor_t> td,
306+ std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer,
307+ word_t buffer_len);
308+ ~vm_channel_t();
309+
310+ std::shared_ptr<const type_descriptor_t> get_data_type() const;
311+
312+ // Send data into the channel.
313+ // Returns true if the data was sent, otherwise request is queued and false returned.
314+ bool send_data(vm_channel_request_t &send_request);
315+
316+ // Receive data from the channel.
317+ // Returns true if data was received, otherwise request is queued and false returned.
318+ bool receive_data(vm_channel_request_t &receive_request);
319+
320+ // Cancel an outstanding request from a thread.
321+ void cancel_request(uint32_t thread_id);
322+
323+ private:
324+ // Try to send data into the channel buffer.
325+ // Returns true if data was sent to buffer, otherwise false.
326+ bool try_send_to_buffer(vm_channel_request_t &send_request);
327+
328+ // Try to receive data from the channel buffer.
329+ // Returns true if data was received from buffer, otherwise false.
330+ bool try_receive_from_buffer(vm_channel_request_t &receive_request);
331+
332+ private:
333+ std::mutex _data_lock;
334+ std::shared_ptr<const type_descriptor_t> _data_type;
335+ std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> _data_transfer;
336+
337+ vm_array_t *_data_buffer;
338+ uint32_t _data_buffer_head; // Next element to remove from buffer
339+ uint32_t _data_buffer_count; // Count of elements in buffer
340+
341+ using request_queue_t = std::deque<vm_channel_request_t>;
342+ request_queue_t _data_senders;
343+ request_queue_t _data_receivers;
344+ };
345+
346+ // Alt channel entry.
347+ static_assert(sizeof(word_t) == sizeof(pointer_t), "Alt channel stack layout will be invalid.");
348+ struct vm_alt_channel_stack_layout_t
349+ {
350+ word_t alloc;
351+ word_t data;
352+ };
353+
354+ // Alt channel layout as defined by the specification for the 'alt' instruction.
355+ struct vm_alt_stack_layout_t
356+ {
357+ word_t send_count;
358+ word_t receive_count;
359+ vm_alt_channel_stack_layout_t channels[1];
360+ };
361+
362+ // Runtime constants
363+ struct runtime_constants
364+ {
365+ static const auto nil = std::size_t{ 0 };
366+
367+ static const auto invalid_program_counter = vm_pc_t{ ~0 };
368+
369+ static const auto default_register_count = std::size_t{ 4 };
370+ static const auto default_register_size_bytes = sizeof(word_t);
371+
372+ // See VM frame definition
373+ static const auto fixed_point_register_1_frame_offset = std::size_t{ default_register_count * default_register_size_bytes };
374+ static const auto fixed_point_register_2_frame_offset = fixed_point_register_1_frame_offset + (2 * default_register_size_bytes);
375+ };
376+
377+ // Addressing mode for middle instruction data
378+ enum class address_mode_middle_t
379+ {
380+ none = 0,
381+ small_immediate = 1,
382+ small_offset_indirect_fp = 2,
383+ small_offset_indirect_mp = 3,
384+ };
385+
386+ // Middle instruction data
387+ struct middle_data_t
388+ {
389+ address_mode_middle_t mode;
390+ word_t register1;
391+ };
392+
393+ // Addressing mode for source and destination instruction data
394+ enum class address_mode_t
395+ {
396+ offset_indirect_mp = 0,
397+ offset_indirect_fp = 1,
398+ immediate = 2,
399+ none = 3,
400+ offset_double_indirect_mp = 4,
401+ offset_double_indirect_fp = 5,
402+ reserved_1 = 6,
403+ reserved_2 = 7,
404+ };
405+
406+ // Source and destination instruction data
407+ struct inst_data_generic_t
408+ {
409+ address_mode_t mode;
410+ word_t register1;
411+ word_t register2;
412+ };
413+
414+ using src_data_t = inst_data_generic_t;
415+ using dest_data_t = inst_data_generic_t;
416+
417+ // VM forward declation
418+ class vm_registers_t;
419+
420+ // Instruction execution operation
421+ using vm_exec_t = void(*)(vm_registers_t &, vm_t &);
422+
423+ // Opcode execution operation
424+ struct vm_exec_op_t
425+ {
426+ opcode_t opcode;
427+ src_data_t source;
428+ middle_data_t middle;
429+ dest_data_t destination;
430+ };
431+
432+ // VM Instruction (code section of module)
433+ union vm_instruction_t
434+ {
435+ vm_exec_op_t op;
436+ vm_exec_t native;
437+ };
438+
439+ // Flags that dictate the runtime operations of the module
440+ // [SPEC] Values past 'shared_module' are not fully documented.
441+ enum class runtime_flags_t
442+ {
443+ must_compile = 1 << 0,
444+ dont_compile = 1 << 1,
445+ share_module = 1 << 2, // [TODO] Respect this setting
446+ reserved_1 = 1 << 3, // DYNMOD
447+ has_import_deprecated = 1 << 4,
448+ has_handler = 1 << 5,
449+ has_import = 1 << 6,
450+
451+ builtin = 1 << 7,
452+ };
453+
454+ // Module header
455+ struct header_t
456+ {
457+ word_t magic_number;
458+ struct
459+ {
460+ word_t length;
461+ std::unique_ptr<byte_t[]> signature;
462+ } Signature;
463+
464+ runtime_flags_t runtime_flag;
465+ word_t stack_extent;
466+ word_t code_size;
467+ word_t data_size;
468+ word_t type_size;
469+ word_t export_size;
470+ vm_pc_t entry_pc;
471+ word_t entry_type; // Stack frame type
472+ };
473+
474+ // Export function entry
475+ struct export_function_t
476+ {
477+ export_function_t() = default;
478+ export_function_t(export_function_t &&);
479+
480+ export_function_t(const export_function_t &) = delete;
481+ export_function_t &operator=(const export_function_t &) = delete;
482+
483+ ~export_function_t();
484+
485+ vm_pc_t pc;
486+ word_t frame_type;
487+ word_t sig;
488+ std::unique_ptr<vm_string_t> name;
489+ };
490+
491+ // Import function entry
492+ struct import_function_t
493+ {
494+ import_function_t() = default;
495+ import_function_t(import_function_t &&);
496+
497+ import_function_t(const import_function_t &) = delete;
498+ import_function_t &operator=(const import_function_t &) = delete;
499+
500+ ~import_function_t();
501+
502+ word_t sig;
503+ std::unique_ptr<vm_string_t> name;
504+ };
505+
506+ // Import table for module
507+ struct import_vm_module_t
508+ {
509+ import_vm_module_t() = default;
510+ import_vm_module_t(import_vm_module_t &&);
511+
512+ import_vm_module_t(const import_vm_module_t &) = delete;
513+ import_vm_module_t &operator=(const import_vm_module_t &) = delete;
514+
515+ ~import_vm_module_t() = default;
516+
517+ std::vector<import_function_t> functions;
518+ };
519+
520+ // Exception entry for handler
521+ struct exception_t
522+ {
523+ exception_t() = default;
524+ exception_t(exception_t &&);
525+
526+ exception_t(const exception_t &) = delete;
527+ exception_t &operator=(const exception_t &) = delete;
528+
529+ ~exception_t();
530+
531+ vm_pc_t pc;
532+ std::unique_ptr<vm_string_t> name;
533+ };
534+
535+ // Exception handler
536+ struct handler_t
537+ {
538+ handler_t() = default;
539+ handler_t(handler_t &&);
540+
541+ handler_t(const handler_t &) = delete;
542+ handler_t &operator=(const handler_t &) = delete;
543+
544+ ~handler_t() = default;
545+
546+ // Byte offset in the handling frame the exception should be placed.
547+ word_t exception_offset;
548+ vm_pc_t begin_pc;
549+ vm_pc_t end_pc;
550+
551+ // Number of cases in exception table where the caught exception is of exception type.
552+ // See exceptions in Limbo language for details.
553+ word_t exception_type_count;
554+
555+ std::shared_ptr<const type_descriptor_t> type_desc;
556+ std::vector<exception_t> exception_table;
557+ };
558+
559+ using code_section_t = std::vector<vm_instruction_t>;
560+ using type_section_map_t = std::vector<std::shared_ptr<const type_descriptor_t>>;
561+ using export_section_t = std::unordered_multimap<word_t, const export_function_t>;
562+ using import_section_t = std::vector<import_vm_module_t>;
563+ using handler_section_t = std::vector<handler_t>;
564+
565+ // VM module
566+ struct vm_module_t
567+ {
568+ vm_module_t() = default;
569+ vm_module_t(const vm_module_t &) = delete;
570+ vm_module_t &operator=(const vm_module_t &) = delete;
571+
572+ ~vm_module_t();
573+
574+ // Contains a magic number, digital signature, runtime flags, sizes of the other sections, and a description of the entry point
575+ header_t header;
576+
577+ // Describes a sequence of instructions for the virtual machine
578+ code_section_t code_section;
579+
580+ // template module pointer
581+ std::unique_ptr<vm_alloc_t> original_mp;
582+
583+ // Type descriptors describing the layout of pointers within data types
584+ type_section_map_t type_section;
585+
586+ // Module name
587+ std::unique_ptr<vm_string_t> module_name;
588+
589+ // List of functions exported by this module
590+ export_section_t export_section;
591+
592+ // List of all functions imported from other modules
593+ import_section_t import_section;
594+
595+ // Lists all exception handlers declared in the module
596+ handler_section_t handler_section;
597+ };
598+
599+ // Reference to a function in an imported module.
600+ struct vm_module_function_ref_t
601+ {
602+ vm_pc_t entry_pc;
603+ word_t frame_type;
604+ };
605+
606+ // Reference to a module
607+ class vm_module_ref_t final : public vm_alloc_t
608+ {
609+ public: //static
610+ static std::shared_ptr<const type_descriptor_t> type_desc();
611+
612+ public:
613+ vm_module_ref_t(std::shared_ptr<const vm_module_t> module);
614+ vm_module_ref_t(std::shared_ptr<const vm_module_t> module, const import_vm_module_t &imports);
615+ ~vm_module_ref_t();
616+
617+ std::shared_ptr<const vm_module_t> module;
618+ vm_alloc_t *mp_base;
619+ const code_section_t &code_section;
620+ const type_section_map_t &type_section;
621+
622+ bool is_builtin_module() const;
623+ const vm_module_function_ref_t& get_function_ref(word_t index) const;
624+
625+ private:
626+ const bool _builtin_module;
627+ std::unique_ptr<vm_array_t> _function_refs;
628+ };
629+
630+ // VM stack frame
631+ class vm_frame_t final
632+ {
633+ public:
634+ vm_frame_t(std::shared_ptr<const type_descriptor_t> &frame_type);
635+ ~vm_frame_t();
636+
637+ std::shared_ptr<const type_descriptor_t> frame_type;
638+
639+ // Copy the frame contents
640+ void copy_frame_contents(const vm_frame_t &other);
641+
642+ // Pointer to the base of the frame.
643+ pointer_t base() const;
644+
645+ template<typename T>
646+ T& base() const
647+ {
648+ return *reinterpret_cast<T *>(base());
649+ }
650+
651+ vm_pc_t &prev_pc() const;
652+ vm_frame_t *&prev_frame() const;
653+
654+ // This is only set if this frame represents a module transition.
655+ vm_module_ref_t *&prev_module_ref() const;
656+
657+ // Fixed point register positions in frame
658+ // [SPEC] These are not defined in the vm spec.
659+
660+ // 'Stmp' in libinterp/xec.c
661+ pointer_t fixed_point_register_1() const;
662+
663+ // 'Dtmp' in libinterp/xec.c
664+ pointer_t fixed_point_register_2() const;
665+ };
666+
667+ // Callback for stack walk.
668+ // Returning 'false' during the stack walk will terminate the walk.
669+ // The second parameter is reserved for internal use by the VM and should not be manipulated.
670+ using vm_stack_walk_callback_t = std::function<bool(const vm_frame_t *f, const void *reserved)>;
671+
672+ // VM stack
673+ class vm_stack_t final
674+ {
675+ public:
676+ vm_stack_t(std::size_t stack_extent);
677+ ~vm_stack_t();
678+
679+ // Allocate a new frame instance.
680+ vm_frame_t *alloc_frame(std::shared_ptr<const type_descriptor_t> frame_type);
681+
682+ // Push the latest allocated frame as the new top frame.
683+ vm_frame_t *push_frame();
684+
685+ // Remove the top frame and return the next frame in the stack.
686+ vm_frame_t *pop_frame();
687+
688+ // Return the current top frame.
689+ vm_frame_t *peek_frame() const;
690+
691+ // Walk the stack getting a callback for each frame.
692+ // This function is __not__ thread safe.
693+ void walk_stack(vm_stack_walk_callback_t callback) const;
694+
695+ private:
696+ std::unique_ptr<vm_alloc_t> _mem;
697+ };
698+
699+ // Walk the supplied stack and faulting module find the handler, handling stack frame, and program counter for the supplied exception ID.
700+ // If an exception handler cannot be found an 'unhandled_user_exception' exception will be thrown.
701+ std::tuple<const handler_t *, vm_frame_t *, vm_pc_t> find_exception_handler(
702+ const vm_stack_t &stack,
703+ const vm_module_ref_t &faulting_module,
704+ const vm_pc_t faulting_pc,
705+ const vm_string_t &exception_id);
706+
707+ // VM thread states
708+ enum class vm_thread_state_t
709+ {
710+ unknown = 0,
711+
712+ blocked_in_alt, // blocked in alt instruction
713+ blocked_sending, // blocked waiting to send
714+ blocked_receiving, // blocked waiting to receive
715+
716+ debug, // thread is running under a debugger
717+ ready, // ready to run
718+ running,
719+
720+ release, // interpreter released - executing non-vm instructions
721+
722+ exiting, // exit instruction
723+ empty_stack,
724+
725+ broken, // thread crashed
726+ };
727+
728+ // Forward declaration
729+ class vm_thread_t;
730+
731+ // VM registers
732+ class vm_registers_t
733+ {
734+ public:
735+ vm_registers_t(
736+ const vm_thread_t &thread,
737+ vm_module_ref_t &entry);
738+ ~vm_registers_t();
739+
740+ const vm_thread_t &thread;
741+ vm_stack_t stack; // Frame pointer access (FP)
742+ vm_pc_t pc; // Program counter (Index into module code section)
743+ vm_alloc_t *mp_base; // Module data base pointer (MP)
744+ vm_module_ref_t *module_ref; // Module reference
745+ vm_request_mutex_t request_mutex;
746+ vm_thread_state_t current_thread_state;
747+ pointer_t src;
748+ pointer_t mid;
749+ pointer_t dest;
750+ };
751+
752+ // VM thread
753+ class vm_thread_t final : public vm_alloc_t
754+ {
755+ public: //static
756+ static std::shared_ptr<const type_descriptor_t> type_desc();
757+
758+ public:
759+ vm_thread_t(
760+ vm_module_ref_t &entry,
761+ uint32_t parent_thread_id);
762+ vm_thread_t(
763+ vm_module_ref_t &entry,
764+ const vm_frame_t &initial_frame,
765+ vm_pc_t start_pc,
766+ uint32_t parent_thread_id);
767+
768+ ~vm_thread_t();
769+
770+ vm_thread_state_t execute(vm_t &virtual_machine, uint32_t quanta);
771+
772+ vm_thread_state_t get_state() const;
773+
774+ uint32_t get_thread_id() const;
775+
776+ uint32_t get_parent_thread_id() const;
777+
778+ const vm_registers_t& get_registers() const;
779+
780+ private:
781+ const uint32_t _thread_id;
782+ const uint32_t _parent_thread_id;
783+ vm_registers_t _registers;
784+ };
785+
786+ //
787+ // VM interfaces
788+ //
789+
790+ // VM thread scheduler control interface
791+ class vm_scheduler_control_t
792+ {
793+ public:
794+ virtual ~vm_scheduler_control_t() = 0;
795+
796+ // Indicates the scheduler should enqueue the blocked thread with the supplied ID.
797+ // There is no indication if this function failed or succeeded.
798+ virtual void enqueue_blocked_thread(uint32_t thread_id) = 0;
799+
800+ // Get all VM threads
801+ virtual std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const = 0;
802+ };
803+
804+ // VM thread scheduler interface
805+ class vm_scheduler_t
806+ {
807+ public:
808+ virtual ~vm_scheduler_t() = 0;
809+
810+ // Returns true if there are no vm threads executing, scheduled to be executed, or blocked.
811+ virtual bool is_idle() const = 0;
812+
813+ // Get controller
814+ virtual vm_scheduler_control_t &get_controller() const = 0;
815+
816+ // Schedule the supplied thread for execution
817+ virtual const vm_thread_t& schedule_thread(std::unique_ptr<vm_thread_t> thread) = 0;
818+ };
819+
820+ // VM garbage collector interface
821+ class vm_garbage_collector_t
822+ {
823+ public:
824+ // The supplied allocation is guaranteed to be valid for the lifetime of the callback.
825+ using vm_alloc_callback_t = std::function<void(const vm_alloc_t *)>;
826+
827+ public:
828+ virtual ~vm_garbage_collector_t() = 0;
829+
830+ // Supply an allocation for the collector to track.
831+ // Note that it is assumed the collector implementation will increment the reference count on the supplied alloc.
832+ virtual void track_allocation(vm_alloc_t *alloc) = 0;
833+
834+ // Enumerate allocations that are being tracked by the collector.
835+ // See callback type description.
836+ virtual void enum_tracked_allocations(vm_alloc_callback_t callback) const = 0;
837+
838+ // Called by the scheduler to inform the collector it should attempt to free resources.
839+ // All VM threads are guaranteed to be idle when called.
840+ // Returns true if the collection algorithm was run, otherwise false.
841+ virtual bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) = 0;
842+ };
843+ }
844+}
845+
846+#endif // _DISVM_SRC_INCLUDE_RUNTIME_H_
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+
13+namespace disvm
14+{
15+ namespace runtime
16+ {
17+ namespace utf8
18+ {
19+ // As defined in http://tools.ietf.org/html/rfc3629
20+ struct constants
21+ {
22+ static const rune_t multi_byte_value_mask = 0x3f;
23+
24+ static const rune_t max_codepoint_ascii = 0x7f;
25+ static const rune_t max_codepoint_2_byte = 0x7ff;
26+ static const rune_t max_codepoint_3_byte = 0xffff;
27+ static const rune_t max_codepoint_4_byte = 0x10ffff;
28+
29+ static const rune_t max_supported_codepoint = max_codepoint_4_byte;
30+ };
31+
32+ // Compute the number of code points in the supplied string
33+ // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
34+ std::size_t count_codepoints(const uint8_t *str, const std::size_t max_len);
35+
36+ enum class decode_state
37+ {
38+ accept = 0,
39+ reject = 12
40+ };
41+
42+ // Decode a byte at a time to form a codepoint.
43+ // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
44+ decode_state decode_step(decode_state &state, rune_t &codepoint, uint8_t byte);
45+
46+ // Decode the next codepoint in the supplied string.
47+ // Returns number of characters in string consumed.
48+ std::size_t decode(const uint8_t *str, rune_t &codepoint);
49+
50+ // Encode the rune into the supplied buffer.
51+ // Returns updated pointer in the buffer or null on error.
52+ uint8_t *encode(const rune_t rune, uint8_t *str);
53+ }
54+ }
55+}
56+
57+#endif // _DISVM_SRC_INCLUDE_UTF8_H_
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+
13+namespace disvm
14+{
15+ namespace util
16+ {
17+ template<typename Flags>
18+ inline bool has_flag(const Flags flags, const Flags flag)
19+ {
20+ return (static_cast<std::underlying_type_t<Flags>>(flags) & static_cast<std::underlying_type_t<Flags>>(flag)) != 0;
21+ }
22+
23+ template <typename T1, typename T2>
24+ inline bool are_equal(const T1 &l, const T2 &r)
25+ {
26+ return (l == r);
27+ }
28+
29+ template <typename T1, typename T2, typename... Others>
30+ inline bool are_equal(const T1 &l, const T2 &r, const Others &... args)
31+ {
32+ return (l == r) && are_equal(r, args...);
33+ }
34+ }
35+}
36+
37+// Adopted from 'winnt.h' in the Windows SDK
38+#define DEFINE_ENUM_FLAG_OPERATORS(TYPE) \
39+inline TYPE operator | (TYPE a, TYPE b) { return static_cast<TYPE>(static_cast<std::underlying_type_t<TYPE>>(a) | static_cast<std::underlying_type_t<TYPE>>(b)); } \
40+inline TYPE &operator |= (TYPE &a, TYPE b) { a = a | b; return a; } \
41+inline TYPE operator & (TYPE a, TYPE b) { return static_cast<TYPE>(static_cast<std::underlying_type_t<TYPE>>(a) & static_cast<std::underlying_type_t<TYPE>>(b)); } \
42+inline TYPE &operator &= (TYPE &a, TYPE b) { a = a & b; return a; } \
43+inline TYPE operator ^ (TYPE a, TYPE b) { return static_cast<TYPE>(static_cast<std::underlying_type_t<TYPE>>(a) ^ static_cast<std::underlying_type_t<TYPE>>(b)); } \
44+inline TYPE &operator ^= (TYPE &a, TYPE b) { a = a ^ b; return a; } \
45+inline TYPE operator ~ (TYPE a) { return static_cast<TYPE>(~static_cast<std::underlying_type_t<TYPE>>(a)); }
46+
47+#endif // _DISVM_SRC_INCLUDE_UTILS_H_
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+
18+namespace disvm
19+{
20+ namespace runtime
21+ {
22+ class global_alloc_lock_t final
23+ {
24+ public:
25+ global_alloc_lock_t(const global_alloc_lock_t &) = delete;
26+ global_alloc_lock_t& operator=(const global_alloc_lock_t &) = delete;
27+
28+ global_alloc_lock_t(global_alloc_lock_t &&);
29+
30+ ~global_alloc_lock_t();
31+
32+ private:
33+ bool _owner;
34+ global_alloc_lock_t(bool owner);
35+ friend global_alloc_lock_t get_global_alloc_lock();
36+ };
37+
38+ // Take the global allocation lock for the VM heap.
39+ // While this lock is taken all further allocations will block.
40+ global_alloc_lock_t get_global_alloc_lock();
41+
42+ // Allocate memory from the VM heap
43+ void *alloc_memory(std::size_t amount_in_bytes);
44+
45+ template<typename T>
46+ T *alloc_memory(std::size_t amount_in_bytes = sizeof(T))
47+ {
48+ return reinterpret_cast<T *>(alloc_memory(amount_in_bytes));
49+ }
50+
51+ // Allocate memory from the VM heap and initialize to 0.
52+ void *calloc_memory(std::size_t amount_in_bytes);
53+
54+ template<typename T>
55+ T *calloc_memory(std::size_t amount_in_bytes = sizeof(T))
56+ {
57+ return reinterpret_cast<T *>(calloc_memory(amount_in_bytes));
58+ }
59+
60+ // Free memory on the VM heap
61+ void free_memory(void *memory);
62+
63+ // Initialize supplied memory based on the type descriptor
64+ void init_memory(const type_descriptor_t &type_desc, void *data);
65+
66+ // Destroy supplied memory based on the type descriptor
67+ void destroy_memory(const type_descriptor_t &type_desc, void *data);
68+
69+ // Increment the ref count of all points in the supplied memory allocation
70+ void inc_ref_count_in_memory(const type_descriptor_t &type_desc, void *data);
71+
72+ // Decrement the ref count and if 0 free the allocation.
73+ void dec_ref_count_and_free(vm_alloc_t *alloc);
74+
75+ using pointer_field_callback_t = std::function<void(pointer_t)>;
76+
77+ // The callback will be called on all non-null pointer fields.
78+ inline void enum_pointer_fields(const type_descriptor_t &type_desc, void *data, pointer_field_callback_t callback)
79+ {
80+ assert(data != nullptr && callback != nullptr);
81+ auto memory_word_len = std::size_t{ type_desc.size_in_bytes / sizeof(word_t) };
82+ if (memory_word_len == 0)
83+ return;
84+
85+ auto memory = reinterpret_cast<word_t *>(data);
86+ assert((memory_word_len / sizeof(word_t)) <= static_cast<std::size_t>(type_desc.size_in_bytes)); // type desc is in bytes convert len to bytes from words.
87+ for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8)
88+ {
89+ const auto words8 = type_desc.pointer_map[i];
90+ if (words8 != 0)
91+ {
92+ auto flags = std::bitset<sizeof(words8) * 8>{ words8 };
93+
94+ // Enumerating the flags in reverse order so memory access is linear.
95+ if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0]));
96+ if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1]));
97+ if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2]));
98+ if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3]));
99+ if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4]));
100+ if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5]));
101+ if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6]));
102+ if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7]));
103+ }
104+ }
105+ }
106+ }
107+}
108+
109+#endif // _DISVM_SRC_INCLUDE_VM_MEMORY_H_
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+
13+int main(int argc, char* argv[])
14+{
15+ auto module = "C:\\Users\\linuxuser27\\source\\disvm\\basic_limbo_tests.dis";
16+ //auto module = "C:\\inferno\\dis\\limbo.dis";
17+ //auto module = "C:\\Users\\linuxuser27\\source\\disvm\\empty.dis";
18+
19+ if (argc >= 2)
20+ module = argv[1];
21+
22+ clock_t t = ::clock();
23+
24+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::memory, true);
25+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::module, true);
26+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::exception, true);
27+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::scheduler, true);
28+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::channel, true);
29+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::addressing, true);
30+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::stack, true);
31+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::thread, true);
32+ //disvm::debug::set_component_tracing(disvm::debug::component_trace_t::garbage_collector, true);
33+ disvm::debug::set_logging_callback([](const disvm::debug::component_trace_t, const disvm::debug::log_level_t l, const char *msg, std::va_list ls) { std::vprintf(msg, ls); });
34+
35+ int c = 1;
36+ //for (;;)
37+ {
38+ ::printf("####### VM test loop (%d) #######\n", c++);
39+ disvm::vm_t v{ 1, 2048 };
40+
41+ for (auto i = 0; i < 1; ++i)
42+ v.exec(module);
43+
44+ v.spin_sleep_till_idle(std::chrono::milliseconds(10));
45+ }
46+
47+ t = std::clock() - t;
48+ std::printf("\n\n%d msec.\n", t);
49+
50+ return 0;
51+}
52+
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+
12+using namespace disvm;
13+using namespace disvm::runtime;
14+
15+std::shared_ptr<const type_descriptor_t> vm_array_t::type_desc()
16+{
17+ return intrinsic_type_desc::type<vm_array_t>();
18+}
19+
20+vm_array_t::vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t count)
21+ : vm_alloc_t(vm_array_t::type_desc())
22+ , _arr{ nullptr }
23+ , _element_type{ td }
24+ , _length{ count }
25+ , _original{ nullptr }
26+{
27+ assert(_element_type != nullptr);
28+ assert(_length >= 0);
29+ _arr = alloc_memory<byte_t>(_length * _element_type->size_in_bytes);
30+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: %d\n", _length);
31+
32+ // Initialize the array
33+ auto arr_local = _arr;
34+ const auto array_len = _length;
35+ const auto &element_type = *_element_type;
36+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "begin: init: elements: %d\n", array_len);
37+ for (word_t i = 0; i < array_len; ++i)
38+ {
39+ init_memory(element_type, arr_local);
40+ arr_local += element_type.size_in_bytes;
41+ }
42+
43+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "end: init: elements: %d\n", array_len);
44+}
45+
46+vm_array_t::vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t count, const zero_memory_t&)
47+ : vm_alloc_t(vm_array_t::type_desc())
48+ , _arr{ nullptr }
49+ , _element_type{ td }
50+ , _length{ count }
51+ , _original{ nullptr }
52+{
53+ assert(_element_type != nullptr);
54+ assert(_length >= 0);
55+ _arr = calloc_memory<byte_t>(_length * _element_type->size_in_bytes);
56+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "initz: vm array: %d\n", _length);
57+}
58+
59+vm_array_t::vm_array_t(vm_array_t *original, word_t begin_index, word_t count)
60+ : vm_alloc_t(vm_array_t::type_desc())
61+ , _arr{ nullptr }
62+ , _element_type{ original->get_element_type() }
63+ , _length{ count }
64+ , _original{ original }
65+{
66+ assert(_original != nullptr);
67+ assert(0 <= begin_index && begin_index < _original->get_length());
68+ assert(_length >= 0 && (count - begin_index) < _original->get_length());
69+
70+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
71+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: slice: %d %d\n", begin_index, _length);
72+
73+ // Set the data to reference the original array
74+ _arr = _original->_arr + (begin_index * _element_type->size_in_bytes);
75+
76+ // Check if this is a slice of a slice.
77+ if (_original->_original != nullptr)
78+ {
79+ auto actual_original = _original->_original;
80+ dec_ref_count_and_free(_original);
81+
82+ _original = actual_original;
83+ _original->add_ref();
84+ }
85+}
86+
87+vm_array_t::vm_array_t(const vm_string_t *s)
88+ : vm_alloc_t(vm_array_t::type_desc())
89+ , _arr{ nullptr }
90+ , _element_type{ intrinsic_type_desc::type<byte_t>() }
91+ , _length{ 0 }
92+ , _original{ nullptr }
93+{
94+ if (s != nullptr && s->get_length() > 0)
95+ {
96+ auto str = s->str();
97+
98+ // Ignoring the null terminator.
99+ _length = std::strlen(str);
100+ _arr = alloc_memory<byte_t>(_length);
101+ static_assert(sizeof(byte_t) == sizeof(char), "String characters should be byte size");
102+
103+ std::memcpy(_arr, str, _length);
104+ }
105+
106+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
107+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: vm string: %d\n", _length);
108+}
109+
110+vm_array_t::~vm_array_t()
111+{
112+ if (_original != nullptr)
113+ {
114+ // If the array is a sliced (based off of another array) then the
115+ // inner pointer simply points into the original array.
116+ debug::assign_debug_pointer(&_arr);
117+
118+ dec_ref_count_and_free(_original);
119+ debug::assign_debug_pointer(&_original);
120+
121+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm array: slice\n");
122+ }
123+ else
124+ {
125+ // Free the array
126+ auto arr_local = _arr;
127+ const auto array_len = _length;
128+ const auto &element_type = *_element_type;
129+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "begin: destroy: elements: %d\n", array_len);
130+ for (word_t i = 0; i < array_len; ++i)
131+ {
132+ destroy_memory(element_type, arr_local);
133+ arr_local += element_type.size_in_bytes;
134+ }
135+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "end: destroy: elements: %d\n", array_len);
136+
137+ // Free array memory
138+ free_memory(_arr);
139+
140+ debug::assign_debug_pointer(&_arr);
141+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm array\n");
142+ }
143+
144+}
145+
146+std::shared_ptr<const type_descriptor_t> vm_array_t::get_element_type() const
147+{
148+ return _element_type;
149+}
150+
151+word_t vm_array_t::get_length() const
152+{
153+ return _length;
154+}
155+
156+pointer_t vm_array_t::at(word_t index) const
157+{
158+ if (index < 0 || _length <= index)
159+ throw out_of_range_memory{ 0, _length - 1, index };
160+
161+ return reinterpret_cast<pointer_t>(_arr + (index * _element_type->size_in_bytes));
162+}
163+
164+void vm_array_t::copy_from(const vm_array_t &source, word_t this_begin_index)
165+{
166+ if (this_begin_index < 0 || _length < (this_begin_index + source._length))
167+ throw out_of_range_memory{};
168+
169+ if (source._element_type != _element_type)
170+ throw type_violation{};
171+
172+ auto dest_arr = _arr + (this_begin_index * _element_type->size_in_bytes);
173+
174+ // Increment all pointers if we have reference types.
175+ if (_element_type->map_in_bytes != 0)
176+ {
177+ assert(false && "Not implemented");
178+ //auto &type_desc = *_element_type;
179+ //auto source_array_element = source->_arr;
180+ //for (word_t i = 0; i < source->_length; ++i)
181+ //{
182+ // source_array_element += (i * type_desc.size_in_bytes);
183+ // inc_ref_count_in_alloc(type_desc, source_array_element);
184+ //}
185+ }
186+
187+ std::memcpy(dest_arr, source._arr, (source._length * _element_type->size_in_bytes));
188+}
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+
16+namespace disvm
17+{
18+ namespace util
19+ {
20+ class buffered_reader_t
21+ {
22+ private: // static
23+ static const std::size_t buffer_size = 64 * 1024; // 64k is an optimization for the Windows filesystem manager
24+
25+ public:
26+ buffered_reader_t(std::istream &stream)
27+ : _buffer(buffered_reader_t::buffer_size)
28+ , _current_index{ 0 }
29+ , _current_size{ 0 }
30+ , _stream{ stream }
31+ {
32+ assert(!stream.fail());
33+ }
34+
35+ buffered_reader_t(const buffered_reader_t&) = delete;
36+ buffered_reader_t& operator=(const buffered_reader_t&) = delete;
37+
38+ // Get the next 'n' number of bytes.
39+ // Returns the number of bytes retrieved.
40+ uint32_t get_next_bytes(uint32_t bytes_requested, uint8_t *buffer)
41+ {
42+ if (bytes_requested == 0)
43+ return 0;
44+
45+ assert(buffer != nullptr);
46+
47+ auto bytes_requested_acc = uint32_t{0};
48+ std::size_t amount_in_buffer;
49+
50+ for (;;)
51+ {
52+ bool success = check_buffer_content(amount_in_buffer);
53+ if (!success || amount_in_buffer == 0)
54+ return 0;
55+
56+ // Buffer contains requested amount
57+ if (bytes_requested <= amount_in_buffer)
58+ {
59+ std::memcpy(buffer, (_buffer.data() + _current_index), bytes_requested);
60+ _current_index += bytes_requested;
61+ bytes_requested_acc += bytes_requested;
62+ return bytes_requested_acc;
63+ }
64+ else
65+ {
66+ // Buffer does not contain all the data requested
67+ std::memcpy(buffer, (_buffer.data() + _current_index), amount_in_buffer);
68+ _current_index += amount_in_buffer;
69+
70+ // Subtract the bytes consumed
71+ bytes_requested -= amount_in_buffer;
72+
73+ bytes_requested_acc += amount_in_buffer;
74+ buffer += amount_in_buffer;
75+ }
76+ }
77+ }
78+
79+ // Get the next byte in the buffer.
80+ // Returns success and the byte.
81+ std::tuple<bool, uint8_t> get_next_byte()
82+ {
83+ std::size_t amount_in_buffer;
84+ bool success = check_buffer_content(amount_in_buffer);
85+ if (!success || amount_in_buffer == 0)
86+ return std::make_tuple(false, 0);
87+
88+ return std::make_tuple(true, _buffer[_current_index++]);
89+ }
90+
91+ private:
92+ bool check_buffer_content(std::size_t &amount_in_buffer)
93+ {
94+ // Signed so checking for underflow is easier.
95+ amount_in_buffer = 0;
96+ auto amount_in_buffer_local = _current_size - _current_index;
97+ if (amount_in_buffer_local <= 0)
98+ {
99+ _current_index = 0;
100+ _current_size = 0;
101+
102+ if (_stream.fail())
103+ return false;
104+
105+ _stream.read(_buffer.data(), buffered_reader_t::buffer_size);
106+ _current_size = _stream.fail() ? static_cast<std::size_t>(_stream.gcount()) : buffered_reader_t::buffer_size;
107+
108+ // Reset amount in buffer
109+ amount_in_buffer_local = buffered_reader_t::buffer_size;
110+ }
111+
112+ amount_in_buffer = amount_in_buffer_local;
113+ return true;
114+ }
115+
116+ private:
117+ std::istream &_stream;
118+ std::size_t _current_index;
119+ std::size_t _current_size;
120+ std::vector<char> _buffer;
121+ };
122+ }
123+}
124+
125+#endif // _DISVM_SRC_VM_BUFFERED_READER_H_
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+
17+using namespace disvm::runtime;
18+using namespace disvm::runtime::builtin;
19+
20+namespace
21+{
22+ std::atomic_bool builtin_modules_initialized;
23+
24+ std::mutex builtin_modules_lock;
25+ std::forward_list<std::shared_ptr<vm_module_t>> builtin_modules;
26+}
27+
28+// Declare initializers for built-in modules.
29+extern void Sysmodinit(void);
30+
31+void disvm::runtime::builtin::initialize_builtin_modules()
32+{
33+ auto is_false = bool{ false };
34+ if (!builtin_modules_initialized.compare_exchange_strong(is_false, true))
35+ return;
36+
37+ // Add built-in module initialization calls here.
38+ // e.g. Sysmodinit()
39+
40+ Sysmodinit();
41+}
42+
43+void disvm::runtime::builtin::register_module_exports(const char *name, word_t table_length, const vm_runtab_t *module_runtime_table)
44+{
45+ assert(name != nullptr && (module_runtime_table != nullptr || table_length == 0) && 0 <= table_length);
46+ assert(name[0] == BUILTIN_MODULE_PREFIX_CHAR && "Built-in modules should have the \"" BUILTIN_MODULE_PREFIX_STR "\" prefix");
47+
48+ auto new_builtin = std::make_shared<vm_module_t>();
49+
50+ // Define the module header
51+ auto &header = new_builtin->header;
52+ header.magic_number = format::magic_number_constants::xmagic;
53+ header.Signature.length = word_t{ 0 };
54+ header.runtime_flag = runtime_flags_t::builtin;
55+
56+ header.stack_extent = 0;
57+ header.code_size = table_length;
58+ header.data_size = 0;
59+ header.type_size = table_length; // 1 for each function frame type
60+ header.export_size = table_length;
61+ header.entry_pc = runtime_constants::invalid_program_counter;
62+ header.entry_type = -1;
63+
64+ new_builtin->module_name = std::make_unique<vm_string_t>(::strlen(name), reinterpret_cast<const uint8_t *>(name));
65+
66+ // Fill out code, type, and export sections
67+ for (auto i = word_t{ 0 }; i < table_length; ++i)
68+ {
69+ const auto &ex = module_runtime_table[i];
70+
71+ // Type
72+ auto frame_type = type_descriptor_t::create(ex.frame_size, ex.frame_pointer_count, ex.frame_pointer_map);
73+ new_builtin->type_section.push_back(std::move(frame_type));
74+
75+ // Export
76+ auto item = export_function_t{};
77+ item.pc = i;
78+ item.frame_type = i;
79+ item.sig = static_cast<word_t>(ex.sig);
80+ item.name = std::make_unique<vm_string_t>(std::strlen(ex.name), reinterpret_cast<const uint8_t *>(ex.name));
81+
82+ const auto sig = item.sig;
83+ new_builtin->export_section.emplace(sig, std::move(item));
84+
85+ // Instruction
86+ auto vm_instr = vm_instruction_t{};
87+ vm_instr.native = ex.fn;
88+ new_builtin->code_section.push_back(std::move(vm_instr));
89+ }
90+
91+ {
92+ std::lock_guard<std::mutex> lock{ builtin_modules_lock };
93+
94+#ifndef NDEBUG
95+ for (const auto &m : builtin_modules)
96+ assert(0 != std::strcmp(m->module_name->str(), name) && "Built-in module with matching name already exists");
97+#endif
98+
99+ builtin_modules.push_front(std::move(new_builtin));
100+ }
101+}
102+
103+std::shared_ptr<vm_module_t> disvm::runtime::builtin::get_builtin_module(const char *name)
104+{
105+ std::lock_guard<std::mutex> lock{ builtin_modules_lock };
106+ for (const auto &m : builtin_modules)
107+ {
108+ if (0 == std::strcmp(m->module_name->str(), name))
109+ return m;
110+ }
111+
112+ throw vm_module_exception{ "Unknown built-in module" };
113+}
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+
12+using namespace disvm;
13+using namespace disvm::runtime;
14+
15+vm_request_mutex_t::vm_request_mutex_t()
16+ : pending_request{ false }
17+{ }
18+
19+vm_channel_request_t::vm_channel_request_t(uint32_t thread_id, vm_request_mutex_t &request_mutex)
20+ : data{ nullptr }
21+ , request_mutex{ request_mutex }
22+ , thread_id{ thread_id }
23+{ }
24+
25+std::shared_ptr<const type_descriptor_t> vm_channel_t::type_desc()
26+{
27+ return intrinsic_type_desc::type<vm_channel_t>();
28+}
29+
30+vm_channel_t::vm_channel_t(
31+ std::shared_ptr<const type_descriptor_t> td,
32+ std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer,
33+ word_t buffer_len)
34+ : vm_alloc_t(vm_channel_t::type_desc())
35+ , _data_buffer{ nullptr }
36+ , _data_buffer_head{ 0 }
37+ , _data_buffer_count{ 0 }
38+ , _data_transfer{ transfer }
39+ , _data_type{ td }
40+{
41+ if (buffer_len < 0)
42+ throw out_of_range_memory{};
43+
44+ if (buffer_len > 0)
45+ _data_buffer = new vm_array_t{ td, buffer_len };
46+
47+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm channel\n");
48+}
49+
50+vm_channel_t::~vm_channel_t()
51+{
52+ dec_ref_count_and_free(_data_buffer);
53+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm channel\n");
54+}
55+
56+std::shared_ptr<const type_descriptor_t> vm_channel_t::get_data_type() const
57+{
58+ return _data_type;
59+}
60+
61+bool vm_channel_t::send_data(vm_channel_request_t &send_request)
62+{
63+ assert(send_request.request_handled_callback != nullptr && send_request.data != nullptr);
64+
65+ std::lock(_data_lock, send_request.request_mutex.get().pending_request_lock);
66+ std::unique_lock<std::mutex> data_lock{ _data_lock, std::adopt_lock };
67+ std::lock_guard<std::mutex> curr_req_lock{ send_request.request_mutex.get().pending_request_lock, std::adopt_lock };
68+
69+ if (!send_request.request_mutex.get().pending_request)
70+ return false;
71+
72+ // Assert the sender cannot also be a receiver in the channel
73+ assert(std::end(_data_receivers) == std::find_if(
74+ std::begin(_data_receivers),
75+ std::end(_data_receivers),
76+ [&send_request](const vm_channel_request_t &req) { return req.thread_id == send_request.thread_id; }));
77+
78+ auto pending_req_lock = std::unique_lock<std::mutex>{};
79+ for (;;)
80+ {
81+ if (_data_receivers.empty())
82+ {
83+ // Check if there is room in the channel buffer
84+ if (_data_buffer != nullptr)
85+ {
86+ if (try_send_to_buffer(send_request))
87+ return true;
88+ }
89+
90+ if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>())
91+ debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: send: queue: %d\n", send_request.thread_id);
92+
93+ _data_senders.emplace_back(std::move(send_request));
94+ return false;
95+ }
96+
97+ auto &receiver_maybe = _data_receivers.front();
98+
99+ // Take lock and check the pending request status
100+ pending_req_lock = std::unique_lock<std::mutex>{ receiver_maybe.request_mutex.get().pending_request_lock };
101+ if (receiver_maybe.request_mutex.get().pending_request)
102+ {
103+ // Indicate both request mutexes as non-pending
104+ send_request.request_mutex.get().pending_request = false;
105+ receiver_maybe.request_mutex.get().pending_request = false;
106+ break;
107+ }
108+
109+ pending_req_lock.unlock();
110+ _data_receivers.pop_front();
111+ }
112+
113+ // Remove the receiver from the list
114+ auto receiver = std::move(_data_receivers.front());
115+ _data_receivers.pop_front();
116+
117+ // Transfer data
118+ _data_transfer(receiver.data, send_request.data, _data_type.get());
119+
120+ // Release channel and pending request lock after data transfer
121+ pending_req_lock.unlock();
122+ data_lock.unlock();
123+
124+ if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>())
125+ debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: send: %d %d\n", send_request.thread_id, receiver.thread_id);
126+
127+ receiver.request_handled_callback(*this);
128+
129+ return true;
130+}
131+
132+bool vm_channel_t::receive_data(vm_channel_request_t &receive_request)
133+{
134+ assert(receive_request.request_handled_callback != nullptr && receive_request.data != nullptr);
135+
136+ std::lock(_data_lock, receive_request.request_mutex.get().pending_request_lock);
137+ std::unique_lock<std::mutex> data_lock{ _data_lock, std::adopt_lock };
138+ std::lock_guard<std::mutex> curr_req_lock{ receive_request.request_mutex.get().pending_request_lock, std::adopt_lock };
139+
140+ if (!receive_request.request_mutex.get().pending_request)
141+ return false;
142+
143+ // Assert the receiver cannot also be a sender in the channel
144+ assert(std::end(_data_senders) == std::find_if(
145+ std::begin(_data_senders),
146+ std::end(_data_senders),
147+ [&receive_request](const vm_channel_request_t &req) { return req.thread_id == receive_request.thread_id; }));
148+
149+ auto pending_req_lock = std::unique_lock<std::mutex>{};
150+ for (;;)
151+ {
152+ if (_data_senders.empty())
153+ {
154+ // Check if there is data in the channel buffer
155+ if (_data_buffer != nullptr)
156+ {
157+ if (try_receive_from_buffer(receive_request))
158+ return true;
159+ }
160+
161+ if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>())
162+ debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: receive: queue: %d\n", receive_request.thread_id);
163+
164+ _data_receivers.emplace_back(std::move(receive_request));
165+ return false;
166+ }
167+
168+ auto &sender_maybe = _data_senders.front();
169+
170+ // Take lock and check the pending request status
171+ pending_req_lock = std::unique_lock<std::mutex>{ sender_maybe.request_mutex.get().pending_request_lock };
172+ if (sender_maybe.request_mutex.get().pending_request)
173+ {
174+ // Indicate both request mutexes as non-pending
175+ receive_request.request_mutex.get().pending_request = false;
176+ sender_maybe.request_mutex.get().pending_request = false;
177+ break;
178+ }
179+
180+ pending_req_lock.unlock();
181+ _data_senders.pop_front();
182+ }
183+
184+ // Remove the sender from the list
185+ auto sender = std::move(_data_senders.front());
186+ _data_senders.pop_front();
187+
188+ // Check if there is data in the channel buffer. This is being
189+ // done to avoid starving threads that found a full buffer when
190+ // attempting to satisfy a request.
191+ if (_data_buffer != nullptr && try_receive_from_buffer(receive_request))
192+ {
193+ // Data was taken from the buffer, so the current pending sender
194+ // can now use the empty slot.
195+ auto did_send = try_send_to_buffer(sender);
196+ assert(did_send && "Should be room in buffer");
197+ }
198+ else
199+ {
200+ // Transfer data since there wasn't anything in the buffer
201+ _data_transfer(receive_request.data, sender.data, _data_type.get());
202+
203+ if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>())
204+ debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: receive: %d %d\n", receive_request.thread_id, sender.thread_id);
205+ }
206+
207+ // Release channel and pending request lock after data transfer
208+ pending_req_lock.unlock();
209+ data_lock.unlock();
210+
211+ sender.request_handled_callback(*this);
212+
213+ return true;
214+}
215+
216+void vm_channel_t::cancel_request(uint32_t thread_id)
217+{
218+ std::lock_guard<std::mutex> lock{ _data_lock };
219+
220+ // Create a predicate function based on the input
221+ auto request_predicate = [thread_id](const vm_channel_request_t &req) { return req.thread_id == thread_id; };
222+
223+ auto receiver_iter = std::find_if(std::begin(_data_receivers), std::end(_data_receivers), request_predicate);
224+ if (receiver_iter != std::end(_data_receivers))
225+ {
226+ _data_receivers.erase(receiver_iter);
227+ }
228+ else
229+ {
230+ auto sender_iter = std::find_if(std::begin(_data_senders), std::end(_data_senders), request_predicate);
231+ if (sender_iter == std::end(_data_senders))
232+ return;
233+
234+ _data_senders.erase(sender_iter);
235+ }
236+
237+ if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>())
238+ debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: cancel: request: %d\n", thread_id);
239+}
240+
241+bool vm_channel_t::try_send_to_buffer(vm_channel_request_t &send_request)
242+{
243+ assert(_data_buffer != nullptr);
244+
245+ const auto buffer_length = static_cast<uint32_t>(_data_buffer->get_length());
246+ if (_data_buffer_count == buffer_length)
247+ return false;
248+
249+ auto next_buffer_index = _data_buffer_head + _data_buffer_count;
250+ if (next_buffer_index >= buffer_length)
251+ next_buffer_index -= buffer_length;
252+
253+ _data_buffer_count++;
254+
255+ auto dest = _data_buffer->at(next_buffer_index);
256+
257+ // Transfer data to buffer
258+ _data_transfer(dest, send_request.data, _data_type.get());
259+ send_request.request_mutex.get().pending_request = false;
260+
261+ if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>())
262+ debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: send: buffer: %d\n", send_request.thread_id);
263+
264+ return true;
265+}
266+
267+bool vm_channel_t::try_receive_from_buffer(vm_channel_request_t &receive_request)
268+{
269+ assert(_data_buffer != nullptr);
270+
271+ if (_data_buffer_count == 0)
272+ return false;
273+
274+ auto src = _data_buffer->at(_data_buffer_head);
275+ _data_buffer_head++;
276+
277+ const auto buffer_length = _data_buffer->get_length();
278+ if (_data_buffer_head == buffer_length)
279+ _data_buffer_head = 0;
280+
281+ _data_buffer_count--;
282+
283+ // Transfer data from buffer
284+ _data_transfer(receive_request.data, src, _data_type.get());
285+
286+ receive_request.request_mutex.get().pending_request = false;
287+
288+ if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>())
289+ debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "channel: receive: buffer: %d\n", receive_request.thread_id);
290+
291+ return true;
292+}
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+
11+namespace
12+{
13+ std::mutex log_mutex;
14+ disvm::debug::log_callback_t log_callback = nullptr;
15+}
16+
17+std::array<bool, static_cast<std::size_t>(disvm::debug::component_trace_t::last) + 1> disvm::debug::trace_enabled = {};
18+
19+disvm::debug::log_callback_t disvm::debug::set_logging_callback(log_callback_t cb)
20+{
21+ std::lock_guard<std::mutex> lock{ log_mutex };
22+ auto tmp = log_callback;
23+ log_callback = cb;
24+ return tmp;
25+}
26+
27+void disvm::debug::log_msg(const component_trace_t origin, const log_level_t level, const char *msg_fmt, ...)
28+{
29+ if (log_callback == nullptr)
30+ return;
31+
32+ if (!is_component_tracing_enabled(origin))
33+ return;
34+
35+ std::va_list args;
36+ va_start(args, msg_fmt);
37+
38+ {
39+ std::lock_guard<std::mutex> lock{ log_mutex };
40+ if (log_callback != nullptr)
41+ log_callback(origin, level, msg_fmt, args);
42+ }
43+
44+ va_end(args);
45+}
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+
22+using namespace disvm;
23+using namespace disvm::runtime;
24+
25+namespace
26+{
27+ //
28+ // Private VM primitive data type definitions
29+ //
30+
31+ using uword_t = uint32_t; // unsigned word_t
32+ using ubig_t = uint64_t; // unsigned big_t
33+
34+ template<typename ValueType>
35+ ValueType& vt_ref(pointer_t vt)
36+ {
37+ assert(vt != nullptr);
38+ return *reinterpret_cast<ValueType *>(vt);
39+ }
40+
41+ template<typename AllocType>
42+ typename std::enable_if<std::is_base_of<vm_alloc_t, AllocType>::value, AllocType *>::type
43+ at_val(pointer_t at)
44+ {
45+ assert(at != nullptr);
46+
47+ return vm_alloc_t::from_allocation<AllocType>(*reinterpret_cast<pointer_t *>(at));
48+ }
49+
50+ template<>
51+ vm_alloc_t *at_val<vm_alloc_t>(pointer_t at)
52+ {
53+ assert(at != nullptr);
54+
55+ return vm_alloc_t::from_allocation(*reinterpret_cast<pointer_t *>(at));
56+ }
57+
58+ pointer_t &pt_ref(pointer_t pt)
59+ {
60+ assert(pt != nullptr);
61+ return *reinterpret_cast<pointer_t *>(pt);
62+ }
63+
64+#define EXEC_DECL(inst_name) void inst_name(vm_registers_t &r, vm_t& vm)
65+
66+ EXEC_DECL(notimpl) { assert(false && "Instruction not implemented"); throw vm_system_exception{ "Instruction not implemented" }; }
67+
68+ EXEC_DECL(invalid) { throw vm_system_exception{ "Illegal Dis VM instruction" }; }
69+
70+ // [SPEC] Documented as 'Clear exception stack' and defined to be used at the discretion of the VM implementation.
71+ // Implemented as a 'spare slot' in Inferno
72+ EXEC_DECL(eclr) { assert(false && "The 'eclr' instruction is not expected to be used"); }
73+
74+ //
75+ // Bit-wise operations
76+ //
77+
78+ EXEC_DECL(andb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) & vt_ref<byte_t>(r.mid); }
79+ EXEC_DECL(andw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) & vt_ref<word_t>(r.mid); }
80+ EXEC_DECL(andl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) & vt_ref<big_t>(r.mid); }
81+ EXEC_DECL(orb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) | vt_ref<byte_t>(r.mid); }
82+ EXEC_DECL(orw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) | vt_ref<word_t>(r.mid); }
83+ EXEC_DECL(orl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) | vt_ref<big_t>(r.mid); }
84+ EXEC_DECL(xorb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) ^ vt_ref<byte_t>(r.mid); }
85+ EXEC_DECL(xorw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) ^ vt_ref<word_t>(r.mid); }
86+ EXEC_DECL(xorl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) ^ vt_ref<big_t>(r.mid); }
87+
88+ //
89+ // Bit-shift operations
90+ //
91+
92+ EXEC_DECL(shlb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.mid) << vt_ref<byte_t>(r.src); }
93+ EXEC_DECL(shlw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.mid) << vt_ref<word_t>(r.src); }
94+ EXEC_DECL(shll) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.mid) << vt_ref<big_t>(r.src); }
95+ EXEC_DECL(shrb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.mid) >> vt_ref<byte_t>(r.src); }
96+ EXEC_DECL(shrw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.mid) >> vt_ref<word_t>(r.src); }
97+ EXEC_DECL(shrl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.mid) >> vt_ref<big_t>(r.src); }
98+ EXEC_DECL(lsrw) { vt_ref<word_t>(r.dest) = vt_ref<uword_t>(r.mid) >> vt_ref<word_t>(r.src); }
99+ EXEC_DECL(lsrl) { vt_ref<big_t>(r.dest) = vt_ref<ubig_t>(r.mid) >> vt_ref<big_t>(r.src); }
100+
101+ //
102+ // Conversion operations
103+ //
104+
105+ EXEC_DECL(cvtbw) { vt_ref<word_t>(r.dest) = std::numeric_limits<byte_t>::max() & vt_ref<byte_t>(r.src); }
106+ EXEC_DECL(cvtwb) { vt_ref<byte_t>(r.dest) = std::numeric_limits<byte_t>::max() & vt_ref<word_t>(r.src); }
107+ EXEC_DECL(cvtwl) { vt_ref<big_t>(r.dest) = std::numeric_limits<word_t>::max() & vt_ref<word_t>(r.src); }
108+ EXEC_DECL(cvtlw) { vt_ref<word_t>(r.dest) = std::numeric_limits<word_t>::max() & vt_ref<big_t>(r.src); }
109+ EXEC_DECL(cvtrf) { vt_ref<short_real_t>(r.dest) = static_cast<short_real_t>(vt_ref<real_t>(r.src)); }
110+ EXEC_DECL(cvtfr) { vt_ref<real_t>(r.dest) = static_cast<real_t>(vt_ref<short_real_t>(r.src)); }
111+ EXEC_DECL(cvtws) { vt_ref<short_word_t>(r.dest) = std::numeric_limits<short_word_t>::max() & vt_ref<word_t>(r.src); }
112+ EXEC_DECL(cvtsw) { vt_ref<word_t>(r.dest) = std::numeric_limits<short_word_t>::max() & vt_ref<short_word_t>(r.src); }
113+ EXEC_DECL(cvtlf) { vt_ref<real_t>(r.dest) = static_cast<real_t>(vt_ref<big_t>(r.src)); }
114+ EXEC_DECL(cvtfl)
115+ {
116+ auto f = vt_ref<real_t>(r.src);
117+ f = f < 0 ? (f - 0.5) : (f + 0.5);
118+ vt_ref<big_t>(r.dest) = static_cast<big_t>(f);
119+ }
120+
121+ EXEC_DECL(cvtxf) { vt_ref<real_t>(r.dest) = static_cast<real_t>(vt_ref<word_t>(r.src)) * vt_ref<real_t>(r.mid); }
122+ EXEC_DECL(cvtfx)
123+ {
124+ auto f = vt_ref<real_t>(r.src) * vt_ref<real_t>(r.mid);
125+ f = f < 0 ? (f - 0.5) : (f + 0.5);
126+ vt_ref<word_t>(r.dest) = static_cast<word_t>(f);
127+ }
128+
129+ EXEC_DECL(cvtwf) { vt_ref<real_t>(r.dest) = static_cast<real_t>(vt_ref<word_t>(r.src)); }
130+ EXEC_DECL(cvtfw)
131+ {
132+ auto f = vt_ref<real_t>(r.src);
133+ f = f < 0 ? (f - 0.5) : (f + 0.5);
134+ vt_ref<word_t>(r.dest) = static_cast<word_t>(f);
135+ }
136+
137+ EXEC_DECL(cvtxx)
138+ {
139+ const auto p = vt_ref<word_t>(r.mid);
140+ auto res = static_cast<big_t>(vt_ref<word_t>(r.src));
141+ if (p >= 0)
142+ res <<= p;
143+ else
144+ res >>= (-p);
145+
146+ vt_ref<word_t>(r.dest) = static_cast<word_t>(res);
147+ }
148+
149+ EXEC_DECL(cvtca)
150+ {
151+ dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest));
152+
153+ auto str = at_val<vm_string_t>(r.src);
154+ auto new_array = new vm_array_t(str);
155+ pt_ref(r.dest) = new_array->allocation();
156+ }
157+
158+ EXEC_DECL(cvtac)
159+ {
160+ dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest));
161+
162+ auto str = pointer_t{};
163+ auto arr = at_val<vm_array_t>(r.src);
164+ if (arr != nullptr)
165+ {
166+ if (arr->get_element_type() != intrinsic_type_desc::type<byte_t>())
167+ throw vm_user_exception{ "Invalid array element type for string conversion" };
168+
169+ auto new_string = new vm_string_t(arr->get_length(), reinterpret_cast<uint8_t *>(arr->at(0)));
170+ str = new_string->allocation();
171+ }
172+
173+ pt_ref(r.dest) = str;
174+ }
175+
176+ EXEC_DECL(cvtcw)
177+ {
178+ auto result = word_t{ 0 };
179+ auto str = at_val<vm_string_t>(r.src);
180+ if (str != nullptr)
181+ result = static_cast<word_t>(::strtol(str->str(), nullptr, 10));
182+
183+ vt_ref<word_t>(r.dest) = result;
184+ }
185+
186+ EXEC_DECL(cvtwc)
187+ {
188+ // String represents the largest possible string from a word.
189+ auto buffer = std::array<char, sizeof("�2147483648")>{};
190+
191+ const auto w = vt_ref<word_t>(r.src);
192+ const auto len = ::sprintf(buffer.data(), "%d", w);
193+ assert(static_cast<std::size_t>(len) <= buffer.size());
194+
195+ auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data()));
196+ auto str = at_val<vm_string_t>(r.dest);
197+ dec_ref_count_and_free(str);
198+
199+ pt_ref(r.dest) = new_string->allocation();
200+ }
201+
202+ EXEC_DECL(cvtcf)
203+ {
204+ auto result = real_t{ 0.0 };
205+ auto str = at_val<vm_string_t>(r.src);
206+ if (str != nullptr)
207+ result = static_cast<real_t>(::strtod(str->str(), nullptr));
208+
209+ vt_ref<real_t>(r.dest) = result;
210+ }
211+
212+ EXEC_DECL(cvtfc)
213+ {
214+ // String represents the largest possible string from a double with scientific notation.
215+ auto buffer = std::array<char, sizeof("-2.2250738585072014e-308")>{};
216+
217+ const auto real = vt_ref<real_t>(r.src);
218+ const auto len = ::sprintf(buffer.data(), "%g", real);
219+ assert(static_cast<std::size_t>(len) <= buffer.size());
220+
221+ auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data()));
222+
223+ auto str = at_val<vm_string_t>(r.dest);
224+ pt_ref(r.dest) = new_string->allocation();
225+ dec_ref_count_and_free(str);
226+ }
227+
228+ EXEC_DECL(cvtcl)
229+ {
230+ auto result = big_t{ 0 };
231+ auto str = at_val<vm_string_t>(r.src);
232+ if (str != nullptr)
233+ result = static_cast<big_t>(::strtoll(str->str(), nullptr, 10));
234+
235+ vt_ref<big_t>(r.dest) = result;
236+ }
237+
238+ EXEC_DECL(cvtlc)
239+ {
240+ // String represents the largest possible string from a 64-bit integer.
241+ auto buffer = std::array<char, sizeof("-9223372036854775808")>{};
242+
243+ const auto b = vt_ref<big_t>(r.src);
244+ const auto len = ::sprintf(buffer.data(), "%lld", b);
245+ assert(static_cast<std::size_t>(len) <= buffer.size());
246+
247+ auto new_string = new vm_string_t(len, reinterpret_cast<uint8_t *>(buffer.data()));
248+
249+ auto str = at_val<vm_string_t>(r.dest);
250+ pt_ref(r.dest) = new_string->allocation();
251+ dec_ref_count_and_free(str);
252+ }
253+
254+ //
255+ // Arithmetic operations
256+ //
257+
258+ EXEC_DECL(negf) { vt_ref<real_t>(r.dest) = -vt_ref<real_t>(r.src); }
259+ EXEC_DECL(addb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) + vt_ref<byte_t>(r.mid); }
260+ EXEC_DECL(addw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) + vt_ref<word_t>(r.mid); }
261+ EXEC_DECL(addl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) + vt_ref<big_t>(r.mid); }
262+ EXEC_DECL(addf) { vt_ref<real_t>(r.dest) = vt_ref<real_t>(r.src) + vt_ref<real_t>(r.mid); }
263+ EXEC_DECL(subb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.mid) - vt_ref<byte_t>(r.src); }
264+ EXEC_DECL(subw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.mid) - vt_ref<word_t>(r.src); }
265+ EXEC_DECL(subl) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.mid) - vt_ref<big_t>(r.src); }
266+ EXEC_DECL(subf) { vt_ref<real_t>(r.dest) = vt_ref<real_t>(r.mid) - vt_ref<real_t>(r.src); }
267+ EXEC_DECL(mulb) { vt_ref<byte_t>(r.dest) = vt_ref<byte_t>(r.src) * vt_ref<byte_t>(r.mid); }
268+ EXEC_DECL(mulw) { vt_ref<word_t>(r.dest) = vt_ref<word_t>(r.src) * vt_ref<word_t>(r.mid); }
269+ EXEC_DECL(mull) { vt_ref<big_t>(r.dest) = vt_ref<big_t>(r.src) * vt_ref<big_t>(r.mid); }
270+ EXEC_DECL(mulf) { vt_ref<real_t>(r.dest) = vt_ref<real_t>(r.src) * vt_ref<real_t>(r.mid); }
271+
272+ template<typename PrimitiveType>
273+ void _div(vm_registers_t &r)
274+ {
275+ const auto denom = vt_ref<PrimitiveType>(r.src);
276+ if (denom == PrimitiveType{ 0 })
277+ throw divide_by_zero{};
278+
279+ vt_ref<PrimitiveType>(r.dest) = vt_ref<PrimitiveType>(r.mid) / denom;
280+ }
281+
282+ EXEC_DECL(divb) { _div<byte_t>(r); }
283+ EXEC_DECL(divw) { _div<word_t>(r); }
284+ EXEC_DECL(divf) { _div<real_t>(r); }
285+ EXEC_DECL(divl) { _div<big_t>(r); }
286+
287+ template<typename PrimitiveType>
288+ void _mod(vm_registers_t &r)
289+ {
290+ const auto denom = vt_ref<PrimitiveType>(r.src);
291+ if (denom == PrimitiveType{ 0 })
292+ throw divide_by_zero{};
293+
294+ vt_ref<PrimitiveType>(r.dest) = vt_ref<PrimitiveType>(r.mid) % denom;
295+ }
296+
297+ EXEC_DECL(modb) { _mod<byte_t>(r); }
298+ EXEC_DECL(modw) { _mod<word_t>(r); }
299+ EXEC_DECL(modl) { _mod<big_t>(r); }
300+
301+ // [SPEC] 'Exponent' op code
302+ // Undocumented: (http://www.vitanuova.com/inferno/papers/dis.html)
303+ // Limbo reference: (http://www.vitanuova.com/inferno/papers/addendum.pdf)
304+ //
305+ // Inferno implementation: (libinterp/xec.c)
306+ // src1 = power
307+ // src2 = base
308+ //
309+ // exp[w|l|f] src1 src2 dst
310+ // dst = src2 'raised to' src1
311+ //
312+ // Result type matches the type of base. Power should be type 'word'.
313+ template<typename PrimitiveType>
314+ void _exp(vm_registers_t &r)
315+ {
316+ auto base = vt_ref<PrimitiveType>(r.mid);
317+ auto power = vt_ref<word_t>(r.src);
318+
319+ auto inverse = false;
320+ if (power < 0)
321+ {
322+ power = (-power);
323+ inverse = true;
324+ }
325+
326+ auto result = PrimitiveType{ 1 };
327+ for (;;)
328+ {
329+ if ((power & 1) != 0)
330+ result *= base;
331+
332+ power >>= 1;
333+ if (power == 0)
334+ break;
335+
336+ base *= base;
337+ }
338+
339+ if (inverse)
340+ result = (1 / result);
341+
342+ vt_ref<PrimitiveType>(r.dest) = result;
343+ }
344+
345+ EXEC_DECL(expw) { _exp<word_t>(r); }
346+ EXEC_DECL(expl) { _exp<big_t>(r); }
347+ EXEC_DECL(expf) { _exp<real_t>(r); }
348+
349+ // [SPEC] 'Fixed point' op codes
350+ // Undocumented: (http://www.vitanuova.com/inferno/papers/dis.html)
351+ // Limbo reference: (http://www.vitanuova.com/inferno/papers/addendum.pdf)
352+ EXEC_DECL(mulx)
353+ {
354+ const auto m = big_t{ vt_ref<word_t>(r.mid) };
355+ const auto s = big_t{ vt_ref<word_t>(r.src) };
356+ auto result = m * s;
357+
358+ const auto p = *reinterpret_cast<word_t *>(r.stack.peek_frame()->fixed_point_register_2());
359+ if (p >= 0)
360+ result <<= p;
361+ else
362+ result >>= (-p);
363+
364+ vt_ref<word_t>(r.dest) = static_cast<word_t>(result);
365+ }
366+
367+ EXEC_DECL(divx)
368+ {
369+ auto denom = big_t{ vt_ref<word_t>(r.mid) };
370+ const auto p = *reinterpret_cast<word_t *>(r.stack.peek_frame()->fixed_point_register_2());
371+ if (p >= 0)
372+ denom <<= p;
373+ else
374+ denom >>= (-p);
375+
376+ const auto numerator = big_t{ vt_ref<word_t>(r.src) };
377+ auto result = denom / numerator;
378+
379+ vt_ref<word_t>(r.dest) = static_cast<word_t>(result);
380+ }
381+
382+ //
383+ // Move operations
384+ //
385+
386+ template<typename PrimitiveType>
387+ void _mov_(pointer_t dest, pointer_t src, const type_descriptor_t *)
388+ {
389+ vt_ref<PrimitiveType>(dest) = vt_ref<PrimitiveType>(src);
390+ }
391+
392+ template<>
393+ void _mov_<vm_alloc_t>(pointer_t dest, pointer_t src, const type_descriptor_t *)
394+ {
395+ auto alloc_prev = at_val<vm_alloc_t>(dest);
396+ dec_ref_count_and_free(alloc_prev);
397+
398+ auto alloc = at_val<vm_alloc_t>(src);
399+ if (alloc != nullptr)
400+ {
401+ alloc->add_ref();
402+ pt_ref(dest) = alloc->allocation();
403+ }
404+ else
405+ {
406+ pt_ref(dest) = nullptr;
407+ }
408+ }
409+
410+ EXEC_DECL(movb) { _mov_<byte_t>(r.dest, r.src, nullptr); }
411+ EXEC_DECL(movw) { _mov_<word_t>(r.dest, r.src, nullptr); }
412+ EXEC_DECL(movf) { _mov_<real_t>(r.dest, r.src, nullptr); }
413+ EXEC_DECL(movl) { _mov_<big_t>(r.dest, r.src, nullptr); }
414+ EXEC_DECL(movp) { _mov_<vm_alloc_t>(r.dest, r.src, nullptr); }
415+
416+ EXEC_DECL(movm)
417+ {
418+ assert(r.src != nullptr && r.dest != nullptr);
419+
420+ std::memmove(r.dest, r.src, vt_ref<word_t>(r.mid));
421+ }
422+
423+ EXEC_DECL(movmp)
424+ {
425+ auto type_id = vt_ref<word_t>(r.mid);
426+ const auto &types = r.module_ref->type_section;
427+ assert(0 <= type_id && type_id < static_cast<word_t>(types.size()));
428+ auto type = types[type_id];
429+
430+ disvm::runtime::inc_ref_count_in_memory(*type, r.src);
431+
432+ // Decrement allocated pointers in the destination.
433+ destroy_memory(*type, r.dest);
434+
435+ assert(r.src != nullptr && r.dest != nullptr);
436+ std::memmove(r.dest, r.src, type->size_in_bytes);
437+ }
438+
439+ //
440+ // Compare and Branch operations
441+ //
442+
443+ EXEC_DECL(bltb) { if (vt_ref<byte_t>(r.src) < vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
444+ EXEC_DECL(bgtb) { if (vt_ref<byte_t>(r.src) > vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
445+ EXEC_DECL(bleb) { if (vt_ref<byte_t>(r.src) <= vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
446+ EXEC_DECL(bgeb) { if (vt_ref<byte_t>(r.src) >= vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
447+ EXEC_DECL(beqb) { if (vt_ref<byte_t>(r.src) == vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
448+ EXEC_DECL(bneb) { if (vt_ref<byte_t>(r.src) != vt_ref<byte_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
449+
450+ EXEC_DECL(bltw) { if (vt_ref<word_t>(r.src) < vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
451+ EXEC_DECL(bgtw) { if (vt_ref<word_t>(r.src) > vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
452+ EXEC_DECL(blew) { if (vt_ref<word_t>(r.src) <= vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
453+ EXEC_DECL(bgew) { if (vt_ref<word_t>(r.src) >= vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
454+ EXEC_DECL(beqw) { if (vt_ref<word_t>(r.src) == vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
455+ EXEC_DECL(bnew) { if (vt_ref<word_t>(r.src) != vt_ref<word_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
456+
457+ EXEC_DECL(bltf) { if (vt_ref<real_t>(r.src) < vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
458+ EXEC_DECL(bgtf) { if (vt_ref<real_t>(r.src) > vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
459+ EXEC_DECL(blef) { if (vt_ref<real_t>(r.src) <= vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
460+ EXEC_DECL(bgef) { if (vt_ref<real_t>(r.src) >= vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
461+ EXEC_DECL(beqf) { if (vt_ref<real_t>(r.src) == vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
462+ EXEC_DECL(bnef) { if (vt_ref<real_t>(r.src) != vt_ref<real_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
463+
464+ EXEC_DECL(bltl) { if (vt_ref<big_t>(r.src) < vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
465+ EXEC_DECL(bgtl) { if (vt_ref<big_t>(r.src) > vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
466+ EXEC_DECL(blel) { if (vt_ref<big_t>(r.src) <= vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
467+ EXEC_DECL(bgel) { if (vt_ref<big_t>(r.src) >= vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
468+ EXEC_DECL(beql) { if (vt_ref<big_t>(r.src) == vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
469+ EXEC_DECL(bnel) { if (vt_ref<big_t>(r.src) != vt_ref<big_t>(r.mid)) r.pc = vt_ref<vm_pc_t>(r.dest); }
470+
471+ // Define case table constants
472+ // [SPEC]Table format is partially defined in Dis VM specification
473+ // [element count] ([low value (inc)][high value (exc)][program counter])* [fallback program counter]
474+ struct case_op_constants
475+ {
476+ static const auto table_length_index = std::size_t{ 0 };
477+ static const auto first_entry_index = std::size_t{ 1 };
478+ static const auto entry_low_index = std::size_t{ 0 };
479+ static const auto entry_high_index = std::size_t{ 1 };
480+ static const auto entry_pc_index = std::size_t{ 2 };
481+ static const auto entry_length = std::size_t{ 3 };
482+ };
483+
484+ template<typename PrimitiveType>
485+ void _case(vm_registers_t &r)
486+ {
487+ const auto value = vt_ref<PrimitiveType>(r.src);
488+
489+ assert(r.dest != nullptr);
490+ const auto table = reinterpret_cast<const PrimitiveType *>(r.dest);
491+ auto entry_count = table[case_op_constants::table_length_index];
492+ assert(entry_count < std::numeric_limits<word_t>::max());
493+
494+ // Initialize the target PC with the fallback value.
495+ // This value is located after all entries in the table.
496+ auto target_pc = table[case_op_constants::first_entry_index + (entry_count * case_op_constants::entry_length)];
497+
498+ // Move to first entry
499+ auto current_entry = table + case_op_constants::first_entry_index;
500+
501+ // Find the matching table entry
502+ for (; 0 < entry_count; --entry_count)
503+ {
504+ // Check if value is in range
505+ if (value < current_entry[case_op_constants::entry_low_index] || current_entry[case_op_constants::entry_high_index] <= value)
506+ {
507+ current_entry += case_op_constants::entry_length;
508+ continue;
509+ }
510+
511+ target_pc = current_entry[case_op_constants::entry_pc_index];
512+ break;
513+ }
514+
515+ assert(0 <= static_cast<std::size_t>(target_pc) && static_cast<std::size_t>(target_pc) < r.module_ref->code_section.size());
516+ r.pc = static_cast<vm_pc_t>(target_pc);
517+ }
518+
519+ EXEC_DECL(casew) { _case<word_t>(r); }
520+ EXEC_DECL(casel) { _case<big_t>(r); }
521+
522+ // [SPEC] The documentation for the 'casec' instruction is a bit sparse
523+ // The full semantics are the case table should have string cases lexically
524+ // ordered (i.e. e[n] < e[n + 1]).
525+ EXEC_DECL(casec)
526+ {
527+ const auto value = at_val<vm_string_t>(r.src);
528+
529+ assert(r.dest != nullptr);
530+ const auto table = reinterpret_cast<const word_t *>(r.dest);
531+ const auto entry_count = table[case_op_constants::table_length_index];
532+
533+ // Initialize the target PC with the fallback value.
534+ // This value is located after all entries in the table.
535+ auto target_pc = table[case_op_constants::first_entry_index + (entry_count * case_op_constants::entry_length)];
536+
537+ // Move to first entry
538+ auto current_entry = table + case_op_constants::first_entry_index;
539+
540+ // Find the matching table entry
541+ for (auto entry = word_t{ 0 }; entry < entry_count; ++entry)
542+ {
543+ auto string_low = at_val<vm_string_t>(reinterpret_cast<pointer_t>(const_cast<word_t *>(current_entry + case_op_constants::entry_low_index)));
544+
545+ // Compare the value against the low string
546+ int result = vm_string_t::compare(value, string_low);
547+ if (result != 0)
548+ {
549+ // If the value compares less than the low string then skip the high string.
550+ if (result < 0)
551+ {
552+ current_entry += case_op_constants::entry_length;
553+ continue;
554+ }
555+
556+ // Compare the value against the high string
557+ auto string_high = at_val<vm_string_t>(reinterpret_cast<pointer_t>(const_cast<word_t *>(current_entry + case_op_constants::entry_high_index)));
558+ if (string_high == nullptr || vm_string_t::compare(value, string_high) != 0)
559+ {
560+ current_entry += case_op_constants::entry_length;
561+ continue;
562+ }
563+ }
564+
565+ target_pc = current_entry[case_op_constants::entry_pc_index];
566+ break;
567+ }
568+
569+ r.pc = target_pc;
570+ }
571+
572+ //
573+ // String operations
574+ //
575+
576+ EXEC_DECL(bltc) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) < 0) r.pc = vt_ref<vm_pc_t>(r.dest); }
577+ EXEC_DECL(bgtc) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) > 0) r.pc = vt_ref<vm_pc_t>(r.dest); }
578+ EXEC_DECL(blec) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) <= 0) r.pc = vt_ref<vm_pc_t>(r.dest); }
579+ EXEC_DECL(bgec) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) >= 0) r.pc = vt_ref<vm_pc_t>(r.dest); }
580+ EXEC_DECL(beqc) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) == 0) r.pc = vt_ref<vm_pc_t>(r.dest); }
581+ EXEC_DECL(bnec) { if (vm_string_t::compare(at_val<vm_string_t>(r.src), at_val<vm_string_t>(r.mid)) != 0) r.pc = vt_ref<vm_pc_t>(r.dest); }
582+
583+ EXEC_DECL(lenc)
584+ {
585+ auto len = word_t{ 0 };
586+ auto str = at_val<vm_string_t>(r.src);
587+ if (str != nullptr)
588+ len = str->get_length();
589+
590+ vt_ref<word_t>(r.dest) = len;
591+ }
592+
593+ EXEC_DECL(indc)
594+ {
595+ auto str = at_val<vm_string_t>(r.src);
596+ if (str == nullptr)
597+ throw dereference_nil{ "Index string" };
598+
599+ const auto index = vt_ref<word_t>(r.mid);
600+ const auto rune = str->get_rune(index);
601+
602+ vt_ref<rune_t>(r.dest) = rune;
603+ }
604+
605+ // [SPEC] 'Insert Character' op code
606+ // Documented: (http://www.vitanuova.com/inferno/papers/dis.html)
607+ // insc src1 src2 dst
608+ // src1[src2] = dst
609+ //
610+ // Inferno implementation: (libinterp/string.c)
611+ // insc src1 src2 dst
612+ // dst[src2] = src1
613+ EXEC_DECL(insc)
614+ {
615+ auto rune = vt_ref<rune_t>(r.src);
616+ auto index = vt_ref<word_t>(r.mid);
617+ auto str = at_val<vm_string_t>(r.dest);
618+ if (str == nullptr)
619+ str = new vm_string_t();
620+ else if (str->get_ref_count() > 1)
621+ str = new vm_string_t(*str, 0, str->get_length());
622+
623+ // Set the rune at the supplied index.
624+ str->set_rune(index, rune);
625+
626+ auto old_str = at_val<vm_string_t>(r.dest);
627+ if (str != old_str)
628+ {
629+ pt_ref(r.dest) = str->allocation();
630+ dec_ref_count_and_free(old_str);
631+ }
632+ }
633+
634+ vm_string_t *_addc(vm_string_t *s1, vm_string_t *s2, /* in out */ bool &append_to_s1)
635+ {
636+ if (s1 == nullptr)
637+ {
638+ if (s2 == nullptr)
639+ return new vm_string_t();
640+
641+ return new vm_string_t(*s2, 0, s2->get_length());
642+ }
643+
644+ // There are other references to this string so we will not alter it.
645+ if (s1->get_ref_count() > 1)
646+ append_to_s1 = false;
647+
648+ if (s2 == nullptr)
649+ {
650+ if (append_to_s1)
651+ return s1;
652+
653+ return new vm_string_t(*s1, 0, s1->get_length());
654+ }
655+
656+ return (append_to_s1) ? &(s1->append(*s2)) : new vm_string_t(*s1, *s2);
657+ }
658+
659+ // [SPEC] 'Concat strings' op code
660+ // Documented: (http://www.vitanuova.com/inferno/papers/dis.html)
661+ // addc src1 src2 dst
662+ // dst = src1 + src2
663+ //
664+ // Inferno implementation: (libinterp/string.c)
665+ // addc src1 src2 dst
666+ // dst = src2 + src1
667+ EXEC_DECL(addc)
668+ {
669+ auto s1 = at_val<vm_string_t>(r.mid);
670+ auto s2 = at_val<vm_string_t>(r.src);
671+ auto dest_maybe = at_val<vm_string_t>(r.dest);
672+
673+ // If the source is the same as the destination and the
674+ // destination is not null, append the string. This can
675+ // change during the concat process.
676+ auto append_to_source = dest_maybe != nullptr && s1 == dest_maybe;
677+ auto str = _addc(s1, s2, append_to_source);
678+
679+ if (!append_to_source)
680+ {
681+ pt_ref(r.dest) = str->allocation();
682+ dec_ref_count_and_free(dest_maybe);
683+ }
684+ }
685+
686+ EXEC_DECL(slicec)
687+ {
688+ const auto start = vt_ref<word_t>(r.src);
689+ const auto end = vt_ref<word_t>(r.mid);
690+ auto str = at_val<vm_string_t>(r.dest);
691+
692+ if (str != nullptr)
693+ {
694+ auto new_string = new vm_string_t(*str, start, end);
695+ pt_ref(r.dest) = new_string->allocation();
696+ }
697+ else if (start == 0 && end == 0)
698+ {
699+ pt_ref(r.dest) = nullptr;
700+ }
701+ else
702+ {
703+ throw dereference_nil{ "Slice a nil string" };
704+ }
705+
706+ dec_ref_count_and_free(str);
707+ }
708+
709+ //
710+ // Array operations
711+ //
712+
713+ EXEC_DECL(lena)
714+ {
715+ auto len = word_t{ 0 };
716+ auto arr = at_val<vm_array_t>(r.src);
717+ if (arr != nullptr)
718+ len = arr->get_length();
719+
720+ vt_ref<word_t>(r.dest) = len;
721+ }
722+
723+ EXEC_DECL(slicea)
724+ {
725+ const auto begin_index = vt_ref<word_t>(r.src);
726+ const auto end_index = vt_ref<word_t>(r.mid);
727+ const auto length = end_index - begin_index;
728+
729+ auto arr = at_val<vm_array_t>(r.dest);
730+ if (arr == nullptr)
731+ {
732+ if (length == 0)
733+ return;
734+
735+ throw vm_user_exception{ "Slice of empty array is invalid" };
736+ }
737+ else if (begin_index < 0 || arr->get_length() < end_index || length < 0)
738+ {
739+ throw out_of_range_memory{};
740+ }
741+
742+ auto new_array = new vm_array_t(arr, begin_index, length);
743+
744+ if (arr->alloc_type->map_in_bytes > 0)
745+ vm.get_garbage_collector().track_allocation(new_array);
746+
747+ pt_ref(r.dest) = new_array->allocation();
748+ }
749+
750+ EXEC_DECL(slicela)
751+ {
752+ auto src_arr = at_val<vm_array_t>(r.src);
753+ if (src_arr == nullptr)
754+ return;
755+
756+ auto dest_arr = at_val<vm_array_t>(r.dest);
757+ if (dest_arr == nullptr)
758+ throw dereference_nil{ "Slice assignment" };
759+
760+ const auto begin_index = vt_ref<word_t>(r.mid);
761+ dest_arr->copy_from(*src_arr, begin_index);
762+ }
763+
764+ void _index_in(vm_registers_t &r)
765+ {
766+ auto arr = at_val<vm_array_t>(r.src);
767+ if (arr == nullptr)
768+ throw dereference_nil{ "Indexing into array" };
769+
770+ auto index = vt_ref<word_t>(r.dest);
771+ if (index < 0 || arr->get_length() <= index)
772+ throw out_of_range_memory{ 0, arr->get_length(), index };
773+
774+ // Return the address, not the value
775+ pt_ref(r.mid) = arr->at(index);
776+ }
777+
778+ EXEC_DECL(indb) { _index_in(r); }
779+ EXEC_DECL(indw) { _index_in(r); }
780+ EXEC_DECL(indf) { _index_in(r); }
781+ EXEC_DECL(indl) { _index_in(r); }
782+ EXEC_DECL(indx) { _index_in(r); }
783+
784+ //
785+ // List operations
786+ //
787+
788+ EXEC_DECL(lenl)
789+ {
790+ auto len = word_t{ 0 };
791+ auto list = at_val<vm_list_t>(r.src);
792+ if (list != nullptr)
793+ {
794+ assert(list->alloc_type == vm_list_t::type_desc());
795+ len = list->get_length();
796+ }
797+
798+ vt_ref<word_t>(r.dest) = len;
799+ }
800+
801+ template<typename PrimitiveType>
802+ void _cons(vm_registers_t &r)
803+ {
804+ auto tail_maybe = at_val<vm_list_t>(r.dest);
805+ if (tail_maybe != nullptr)
806+ tail_maybe->release();
807+
808+ auto new_list = new vm_list_t(intrinsic_type_desc::type<PrimitiveType>(), tail_maybe);
809+
810+ auto value = vt_ref<PrimitiveType>(r.src);
811+ vt_ref<PrimitiveType>(new_list->value()) = value;
812+
813+ pt_ref(r.dest) = new_list->allocation();
814+ }
815+
816+ EXEC_DECL(consb) { _cons<byte_t>(r); }
817+ EXEC_DECL(consw) { _cons<word_t>(r); }
818+ EXEC_DECL(consf) { _cons<real_t>(r); }
819+ EXEC_DECL(consl) { _cons<big_t>(r); }
820+
821+ EXEC_DECL(consp)
822+ {
823+ auto tail_maybe = at_val<vm_list_t>(r.dest);
824+ if (tail_maybe != nullptr)
825+ tail_maybe->release();
826+
827+ // Create a new list
828+ auto new_list = new vm_list_t(intrinsic_type_desc::type<pointer_t>(), tail_maybe);
829+ auto alloc = at_val<vm_alloc_t>(r.src);
830+ if (alloc != nullptr)
831+ {
832+ alloc->add_ref();
833+ pt_ref(new_list->value()) = alloc->allocation();
834+
835+ vm.get_garbage_collector().track_allocation(new_list);
836+ }
837+ else
838+ {
839+ pt_ref(new_list->value()) = nullptr;
840+ }
841+
842+ // Return the list
843+ pt_ref(r.dest) = new_list->allocation();
844+ }
845+
846+ EXEC_DECL(consmp)
847+ {
848+ auto type_id = vt_ref<word_t>(r.mid);
849+ const auto &types = r.module_ref->type_section;
850+ assert(0 <= type_id && type_id < static_cast<word_t>(types.size()));
851+ auto type = types[type_id];
852+
853+ // Increment the ref count for element content
854+ inc_ref_count_in_memory(*type, r.src);
855+
856+ auto tail_maybe = at_val<vm_list_t>(r.dest);
857+ if (tail_maybe != nullptr)
858+ tail_maybe->release();
859+
860+ // Create a new list
861+ auto new_list = new vm_list_t(type, tail_maybe);
862+ auto destination = new_list->value();
863+ std::memmove(destination, r.src, type->size_in_bytes);
864+
865+ if (type->map_in_bytes > 0)
866+ vm.get_garbage_collector().track_allocation(new_list);
867+
868+ // Return the list
869+ pt_ref(r.dest) = new_list->allocation();
870+ }
871+
872+ template<typename PrimitiveType>
873+ void _head(vm_registers_t &r)
874+ {
875+ auto head = at_val<vm_list_t>(r.src);
876+ if (head == nullptr)
877+ throw dereference_nil{ "Head of list" };
878+
879+ assert(head->get_element_type() == intrinsic_type_desc::type<PrimitiveType>());
880+ auto head_value = head->value();
881+ vt_ref<PrimitiveType>(r.dest) = vt_ref<PrimitiveType>(head_value);
882+ }
883+
884+ EXEC_DECL(headb) { _head<byte_t>(r); }
885+ EXEC_DECL(headw) { _head<word_t>(r); }
886+ EXEC_DECL(headf) { _head<real_t>(r); }
887+ EXEC_DECL(headl) { _head<big_t>(r); }
888+
889+ EXEC_DECL(headp)
890+ {
891+ auto list = at_val<vm_list_t>(r.src);
892+ if (list == nullptr)
893+ throw dereference_nil{ "Head of list" };
894+
895+ r.src = list->value();
896+ movp(r, vm);
897+ }
898+
899+ EXEC_DECL(headmp)
900+ {
901+ auto list = at_val<vm_list_t>(r.src);
902+ if (list == nullptr)
903+ throw dereference_nil{ "Head of list" };
904+
905+ r.src = list->value();
906+ movmp(r, vm);
907+ }
908+
909+ EXEC_DECL(tail)
910+ {
911+ auto list = at_val<vm_list_t>(r.src);
912+ if (list == nullptr)
913+ throw dereference_nil{ "Tail of list" };
914+
915+ assert(list->alloc_type == vm_list_t::type_desc());
916+ auto tail = pointer_t{};
917+
918+ auto tail_maybe = list->get_tail();
919+ if (tail_maybe != nullptr)
920+ tail = tail_maybe->allocation();
921+
922+ // Use the address of the local variable.
923+ r.src = reinterpret_cast<pointer_t>(&tail);
924+ movp(r, vm);
925+ }
926+
927+ //
928+ // Channel operations
929+ //
930+
931+ namespace
932+ {
933+ void _channel_movm(pointer_t dest, pointer_t src, const type_descriptor_t *type)
934+ {
935+ assert(type != nullptr);
936+ std::memmove(dest, src, type->size_in_bytes);
937+ }
938+
939+ void _channel_movmp(pointer_t dest, pointer_t src, const type_descriptor_t *type)
940+ {
941+ assert(type != nullptr);
942+ destroy_memory(*type, dest);
943+
944+ inc_ref_count_in_memory(*type, src);
945+ std::memmove(dest, src, type->size_in_bytes);
946+ }
947+
948+ // [SPEC] 'Create new channel' op codes
949+ // Documented: (http://www.vitanuova.com/inferno/papers/dis.html)
950+ // As documented the src2 register is not used, however the Inferno
951+ // implementation does use the src2 register. It is checked against
952+ // the dst register and if they are not equal the src2 register
953+ // contains the internal buffer size of the channel.
954+ //
955+ // See Inferno implementation: (libinterp/xec.c)
956+ void _newc_(
957+ vm_registers_t &r,
958+ const vm_t &vm,
959+ std::shared_ptr<const type_descriptor_t> type_desc,
960+ std::function<void(pointer_t, pointer_t, const type_descriptor_t *)> transfer)
961+ {
962+ auto buffer_len = word_t{ 0 };
963+ if (r.mid != r.dest)
964+ {
965+ buffer_len = vt_ref<word_t>(r.mid);
966+ if (buffer_len < 0)
967+ throw out_of_range_memory{};
968+ }
969+
970+ dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest));
971+ auto new_channel = new vm_channel_t{ type_desc, transfer, buffer_len };
972+
973+ if (type_desc->map_in_bytes > 0)
974+ vm.get_garbage_collector().track_allocation(new_channel);
975+
976+ pt_ref(r.dest) = new_channel->allocation();
977+ }
978+ }
979+
980+ EXEC_DECL(newcb) { _newc_(r, vm, intrinsic_type_desc::type<byte_t>(), _mov_<byte_t>); }
981+ EXEC_DECL(newcw) { _newc_(r, vm, intrinsic_type_desc::type<word_t>(), _mov_<word_t>); }
982+ EXEC_DECL(newcf) { _newc_(r, vm, intrinsic_type_desc::type<real_t>(), _mov_<real_t>); }
983+ EXEC_DECL(newcl) { _newc_(r, vm, intrinsic_type_desc::type<big_t>(), _mov_<big_t>); }
984+ EXEC_DECL(newcp) { _newc_(r, vm, intrinsic_type_desc::type<pointer_t>(), _mov_<vm_alloc_t>); }
985+
986+ EXEC_DECL(newcm)
987+ {
988+ auto memory_size = vt_ref<word_t>(r.src);
989+ auto channel_data_type = type_descriptor_t::create(memory_size);
990+
991+ _newc_(r, vm, channel_data_type, _channel_movm);
992+ }
993+
994+ EXEC_DECL(newcmp)
995+ {
996+ auto type_id = vt_ref<word_t>(r.src);
997+ const auto &types = r.module_ref->type_section;
998+ assert(0 <= type_id && type_id < static_cast<word_t>(types.size()));
999+ auto type = types[type_id];
1000+
1001+ _newc_(r, vm, type, _channel_movmp);
1002+ }
1003+
1004+ EXEC_DECL(send)
1005+ {
1006+ auto channel = at_val<vm_channel_t>(r.dest);
1007+ if (channel == nullptr)
1008+ throw dereference_nil{ "Send to channel" };
1009+
1010+ const auto current_thread_id = r.thread.get_thread_id();
1011+
1012+ assert(!r.request_mutex.pending_request && "There should be no pending request for the thread");
1013+ r.request_mutex.pending_request = true;
1014+
1015+ assert(r.current_thread_state == vm_thread_state_t::running);
1016+ r.current_thread_state = vm_thread_state_t::blocked_sending;
1017+
1018+ // Setup the request
1019+ auto request = vm_channel_request_t{ current_thread_id, r.request_mutex };
1020+ request.data = r.src;
1021+ request.request_handled_callback = [&vm, &r, current_thread_id](const vm_channel_t &)
1022+ {
1023+ assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_sending);
1024+ vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id);
1025+ };
1026+
1027+ if (channel->send_data(request))
1028+ r.current_thread_state = vm_thread_state_t::running;
1029+ }
1030+
1031+ EXEC_DECL(recv)
1032+ {
1033+ auto channel = at_val<vm_channel_t>(r.src);
1034+ if (channel == nullptr)
1035+ throw dereference_nil{ "Receive from channel" };
1036+
1037+ const auto current_thread_id = r.thread.get_thread_id();
1038+
1039+ assert(!r.request_mutex.pending_request && "There should be no pending request for the thread");
1040+ r.request_mutex.pending_request = true;
1041+
1042+ assert(r.current_thread_state == vm_thread_state_t::running);
1043+ r.current_thread_state = vm_thread_state_t::blocked_receiving;
1044+
1045+ // Setup the request
1046+ auto request = vm_channel_request_t { current_thread_id, r.request_mutex };
1047+ request.data = r.dest;
1048+ request.request_handled_callback = [&vm, &r, current_thread_id](const vm_channel_t &)
1049+ {
1050+ assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_receiving);
1051+ vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id);
1052+ };
1053+
1054+ if (channel->receive_data(request))
1055+ r.current_thread_state = vm_thread_state_t::running;
1056+ }
1057+
1058+ namespace
1059+ {
1060+ std::minstd_rand alt_channel_selector;
1061+
1062+ word_t cancel_outstanding_channel_requests(uint32_t request_thread_id, vm_alt_stack_layout_t *alt, const vm_channel_t &handled_channel)
1063+ {
1064+ auto alloc = handled_channel.allocation();
1065+
1066+ auto handled_index = word_t{ -1 };
1067+ const auto channel_count = (alt->send_count + alt->receive_count);
1068+ for (auto i = word_t{ 0 }; i < channel_count; ++i)
1069+ {
1070+ auto ch_pointer = reinterpret_cast<pointer_t>(alt->channels[i].alloc);
1071+ if (ch_pointer == alloc)
1072+ {
1073+ assert(handled_index == -1 && "Index should only be set once");
1074+ handled_index = i;
1075+ continue;
1076+ }
1077+
1078+ auto ch = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&ch_pointer));
1079+ ch->cancel_request(request_thread_id);
1080+ }
1081+
1082+ assert(handled_index != -1 && "Index should be set");
1083+ return handled_index;
1084+ }
1085+
1086+ void exec_alt(vm_registers_t &r, const vm_t &vm, const bool blocking)
1087+ {
1088+ auto alt = reinterpret_cast<vm_alt_stack_layout_t *>(r.src);
1089+ assert(alt != nullptr);
1090+
1091+ const auto current_thread_id = r.thread.get_thread_id();
1092+
1093+ assert(!r.request_mutex.pending_request && "There should be no pending request for the thread");
1094+ r.request_mutex.pending_request = true;
1095+
1096+ assert(r.current_thread_state == vm_thread_state_t::running);
1097+ r.current_thread_state = vm_thread_state_t::blocked_in_alt;
1098+
1099+ // Create request callback
1100+ auto alt_request_handled_callback = [&vm, &r, alt, current_thread_id](const vm_channel_t &handled_channel)
1101+ {
1102+ // Cancel all non-handled requests and determine the
1103+ // index of the channel that handled this request.
1104+ auto handled_index = cancel_outstanding_channel_requests(current_thread_id, alt, handled_channel);
1105+
1106+ // Set the index of the channel that handled the request
1107+ vt_ref<word_t>(r.dest) = handled_index;
1108+
1109+ assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_in_alt);
1110+ vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id);
1111+ };
1112+
1113+ const auto channel_count = (alt->send_count + alt->receive_count);
1114+ assert(channel_count > 0);
1115+
1116+ // Generate a random channel index sequence.
1117+ auto random_idx_sequence = std::vector<word_t>(channel_count);
1118+ std::iota(std::begin(random_idx_sequence), std::end(random_idx_sequence), 0);
1119+ std::shuffle(std::begin(random_idx_sequence), std::end(random_idx_sequence), alt_channel_selector);
1120+
1121+ auto requests_queued = uint32_t{ 0 };
1122+ auto handled = bool{};
1123+ for (auto rnd_idx : random_idx_sequence)
1124+ {
1125+ auto &channel_stack_layout = alt->channels[rnd_idx];
1126+ if (channel_stack_layout.alloc == runtime_constants::nil)
1127+ throw dereference_nil{ "Alt channel" };
1128+
1129+ auto channel = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&channel_stack_layout.alloc));
1130+
1131+ auto request = vm_channel_request_t{ current_thread_id, r.request_mutex };
1132+ request.data = reinterpret_cast<pointer_t>(channel_stack_layout.data);
1133+ request.request_handled_callback = alt_request_handled_callback;
1134+
1135+ if (rnd_idx < alt->send_count)
1136+ handled = channel->send_data(request);
1137+ else
1138+ handled = channel->receive_data(request);
1139+
1140+ if (handled)
1141+ {
1142+ // Check if requests need to be handled
1143+ if (requests_queued > 0)
1144+ {
1145+ auto handled_index = cancel_outstanding_channel_requests(current_thread_id, alt, *channel);
1146+ assert(handled_index == rnd_idx);
1147+ }
1148+
1149+ vt_ref<word_t>(r.dest) = rnd_idx;
1150+ r.current_thread_state = vm_thread_state_t::running;
1151+ return;
1152+ }
1153+ else
1154+ {
1155+ requests_queued++;
1156+ }
1157+ }
1158+
1159+ // If this is a non-blocking alt request and we are here no channels
1160+ // were immediately able to handle the request. Try to lock the request
1161+ // so we can cancel it in all channels.
1162+ if (!blocking && r.request_mutex.pending_request_lock.try_lock())
1163+ {
1164+ std::lock_guard<std::mutex> lock{ r.request_mutex.pending_request_lock, std::adopt_lock };
1165+ // Check if a channel handled the request between the initial queueing and now.
1166+ if (r.request_mutex.pending_request)
1167+ {
1168+ r.request_mutex.pending_request = false;
1169+
1170+ if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>())
1171+ debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "alt: request: non-blocking\n");
1172+
1173+ // Cancel request in all channels.
1174+ for (auto i = word_t{ 0 }; i < channel_count; ++i)
1175+ {
1176+ auto ch_pointer = reinterpret_cast<pointer_t>(alt->channels[i].alloc);
1177+ auto ch = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&ch_pointer));
1178+ ch->cancel_request(current_thread_id);
1179+ }
1180+
1181+ // Index of a non-blocking request is the final index of the alt.
1182+ vt_ref<word_t>(r.dest) = channel_count;
1183+ r.current_thread_state = vm_thread_state_t::running;
1184+ }
1185+ }
1186+ }
1187+ }
1188+
1189+ EXEC_DECL(alt)
1190+ {
1191+ exec_alt(r, vm, true /* blocking */);
1192+ }
1193+
1194+ EXEC_DECL(nbalt)
1195+ {
1196+ exec_alt(r, vm, false /* blocking */);
1197+ }
1198+
1199+ //
1200+ // Dynamic allocation operations
1201+ //
1202+
1203+ namespace
1204+ {
1205+ void new_instance(vm_registers_t &r, const vm_t &vm, const bool zero_memory)
1206+ {
1207+ const auto type_id = vt_ref<word_t>(r.src);
1208+ const auto &types = r.module_ref->type_section;
1209+ assert(0 <= type_id && type_id < static_cast<word_t>(types.size()));
1210+
1211+ dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest));
1212+
1213+ auto type_desc = types[type_id];
1214+ auto new_alloc = !zero_memory ? vm_alloc_t::allocate(type_desc) : vm_alloc_t::allocate(type_desc, vm_alloc_t::zero_memory);
1215+
1216+ if (type_desc->map_in_bytes > 0)
1217+ vm.get_garbage_collector().track_allocation(new_alloc);
1218+
1219+ pt_ref(r.dest) = new_alloc->allocation();
1220+ }
1221+ }
1222+
1223+ EXEC_DECL(new_)
1224+ {
1225+ new_instance(r, vm, false /* zero memory */);
1226+ }
1227+
1228+ EXEC_DECL(newz)
1229+ {
1230+ new_instance(r, vm, true /* zero memory */);
1231+ }
1232+
1233+ EXEC_DECL(mnewz)
1234+ {
1235+ auto target_module = at_val<vm_module_ref_t>(r.src);
1236+ if (target_module == nullptr)
1237+ throw vm_user_exception{ "Module not loaded" };
1238+
1239+ const auto type_id = vt_ref<word_t>(r.mid);
1240+ const auto &types = target_module->type_section;
1241+ assert(0 <= type_id && type_id < static_cast<word_t>(types.size()));
1242+
1243+ dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest));
1244+
1245+ auto type_desc = types[type_id];
1246+ auto new_alloc = vm_alloc_t::allocate(type_desc, vm_alloc_t::zero_memory);
1247+
1248+ if (type_desc->map_in_bytes > 0)
1249+ vm.get_garbage_collector().track_allocation(new_alloc);
1250+
1251+ pt_ref(r.dest) = new_alloc->allocation();
1252+ }
1253+
1254+ namespace
1255+ {
1256+ void new_array(vm_registers_t &r, const vm_t &vm, const bool zero_memory)
1257+ {
1258+ assert(r.module_ref != nullptr);
1259+ const auto &types = r.module_ref->type_section;
1260+
1261+ const auto type_id = vt_ref<word_t>(r.mid);
1262+ assert(0 <= type_id && type_id < static_cast<word_t>(types.size()));
1263+ auto type_desc = types[type_id];
1264+
1265+ dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest));
1266+
1267+ const auto array_size = vt_ref<word_t>(r.src);
1268+ auto new_array = !zero_memory ? new vm_array_t(type_desc, array_size) : new vm_array_t(type_desc, array_size, vm_alloc_t::zero_memory);
1269+
1270+ if (type_desc->map_in_bytes > 0)
1271+ vm.get_garbage_collector().track_allocation(new_array);
1272+
1273+ pt_ref(r.dest) = new_array->allocation();
1274+ }
1275+ }
1276+
1277+ EXEC_DECL(newa)
1278+ {
1279+ new_array(r, vm, false /* zero memory */);
1280+ }
1281+
1282+ EXEC_DECL(newaz)
1283+ {
1284+ new_array(r, vm, true /* zero memory */);
1285+ }
1286+
1287+ EXEC_DECL(tcmp)
1288+ {
1289+ auto s = at_val<vm_alloc_t>(r.src);
1290+ auto d = at_val<vm_alloc_t>(r.dest);
1291+
1292+ if (s == nullptr)
1293+ return;
1294+
1295+ if (d == nullptr || s->alloc_type != d->alloc_type)
1296+ throw type_violation{};
1297+ }
1298+
1299+ //
1300+ // Stack operations
1301+ //
1302+
1303+ // [SPEC] 'runt' op code
1304+ // Undocumented: (http://www.vitanuova.com/inferno/papers/dis.html)
1305+ //
1306+ // Inferno implementation: (libinterp/xec.c)
1307+ // No operands and appears to be a type of NOP instruction.
1308+ EXEC_DECL(runt) { /* NOP */ }
1309+
1310+ EXEC_DECL(lea) { pt_ref(r.dest) = r.src; }
1311+
1312+ EXEC_DECL(frame)
1313+ {
1314+ const auto &types = r.module_ref->type_section;
1315+
1316+ const auto frame_type_id = vt_ref<word_t>(r.src);
1317+ assert(0 <= frame_type_id && frame_type_id < static_cast<word_t>(types.size()));
1318+ const auto frame_type = types[frame_type_id];
1319+
1320+ pt_ref(r.dest) = r.stack.alloc_frame(frame_type)->base();
1321+ }
1322+
1323+ EXEC_DECL(mframe)
1324+ {
1325+ const auto target_module = at_val<vm_module_ref_t>(r.src);
1326+ if (target_module == nullptr)
1327+ throw vm_user_exception{ "Module not loaded" };
1328+
1329+ const auto &types = target_module->type_section;
1330+
1331+ // Get the function ref index for this module.
1332+ const auto function_id = vt_ref<word_t>(r.mid);
1333+ const auto &function_ref = target_module->get_function_ref(function_id);
1334+
1335+ const auto frame_type_id = function_ref.frame_type;
1336+ assert(0 <= frame_type_id && frame_type_id < static_cast<word_t>(types.size()));
1337+ const auto frame_type = types[frame_type_id];
1338+
1339+ pt_ref(r.dest) = r.stack.alloc_frame(frame_type)->base();
1340+ }
1341+
1342+ EXEC_DECL(ret)
1343+ {
1344+ const auto current_top_frame = r.stack.peek_frame();
1345+ assert(current_top_frame != nullptr);
1346+ r.pc = current_top_frame->prev_pc();
1347+
1348+ if (current_top_frame->prev_module_ref() != nullptr)
1349+ {
1350+ dec_ref_count_and_free(r.module_ref);
1351+ r.module_ref = current_top_frame->prev_module_ref();
1352+ current_top_frame->prev_module_ref() = nullptr;
1353+
1354+ dec_ref_count_and_free(r.mp_base);
1355+ r.mp_base = r.module_ref->mp_base;
1356+ }
1357+
1358+ r.stack.pop_frame();
1359+
1360+ if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>())
1361+ debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "exit: function\n");
1362+ }
1363+
1364+ EXEC_DECL(call)
1365+ {
1366+ auto top_frame = r.stack.push_frame();
1367+ top_frame->prev_pc() = r.pc;
1368+
1369+#ifndef NDEBUG
1370+ // Validate the stack state
1371+ const auto frame_base_pointer = pt_ref(r.src);
1372+ assert(frame_base_pointer != nullptr);
1373+ assert(top_frame->base() == frame_base_pointer);
1374+#endif
1375+
1376+ r.pc = vt_ref<vm_pc_t>(r.dest);
1377+
1378+ debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "enter: function\n");
1379+ }
1380+
1381+ EXEC_DECL(mcall)
1382+ {
1383+ auto target_module = at_val<vm_module_ref_t>(r.dest);
1384+ if (target_module == nullptr)
1385+ throw vm_user_exception{ "Module not loaded" };
1386+
1387+ // Get the pc for the function call into this module
1388+ const auto export_id = vt_ref<word_t>(r.mid);
1389+ const auto &function_ref = target_module->get_function_ref(export_id);
1390+ const auto function_pc = function_ref.entry_pc;
1391+ assert(static_cast<std::size_t>(function_pc) < target_module->code_section.size());
1392+
1393+ // Push the next frame
1394+ auto top_frame = r.stack.push_frame();
1395+
1396+#ifndef NDEBUG
1397+ // Validate the stack state
1398+ const auto frame_base_pointer = pt_ref(r.src);
1399+ assert(frame_base_pointer != nullptr);
1400+ assert(top_frame->base() == frame_base_pointer);
1401+#endif
1402+
1403+ // Store current state
1404+ top_frame->prev_pc() = r.pc;
1405+ top_frame->prev_module_ref() = r.module_ref;
1406+
1407+ if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>())
1408+ {
1409+ debug::log_msg(
1410+ debug::component_trace_t::stack,
1411+ debug::log_level_t::debug,
1412+ "enter: function: inter-module: %d >>%s<< %d %s\n",
1413+ export_id,
1414+ target_module->module->module_name->str(),
1415+ function_pc,
1416+ (target_module->is_builtin_module() ? "true" : "false"));
1417+ }
1418+
1419+ // Set registers for execution
1420+ r.module_ref = target_module;
1421+ r.module_ref->add_ref();
1422+ r.mp_base = r.module_ref->mp_base;
1423+ r.pc = function_pc;
1424+
1425+ if (r.module_ref->is_builtin_module())
1426+ {
1427+ // Execute the built-in function, then return
1428+ const auto &inst = r.module_ref->code_section[function_pc];
1429+ assert(r.mp_base == nullptr && "Built-in modules shouldn't have module data (MP register)");
1430+
1431+ r.current_thread_state = vm_thread_state_t::release;
1432+ inst.native(r, vm);
1433+ ret(r, vm);
1434+ r.current_thread_state = vm_thread_state_t::running;
1435+ }
1436+ else
1437+ {
1438+ r.mp_base->add_ref();
1439+ }
1440+ }
1441+
1442+ EXEC_DECL(jmp) { r.pc = vt_ref<vm_pc_t>(r.dest); }
1443+
1444+ EXEC_DECL(goto_)
1445+ {
1446+ auto pc_index = vt_ref<word_t>(r.src);
1447+
1448+ assert(r.dest != nullptr);
1449+ auto pc_table = reinterpret_cast<vm_pc_t *>(r.dest);
1450+
1451+ r.pc = pc_table[pc_index];
1452+ }
1453+
1454+ //
1455+ // Thread operations
1456+ //
1457+
1458+ EXEC_DECL(spawn)
1459+ {
1460+ // Arguments to the spawned thread are passed on the current
1461+ // thread's stack in a new frame.
1462+ auto spawned_frame = r.stack.push_frame();
1463+
1464+#ifndef NDEBUG
1465+ // Validate the stack state
1466+ const auto frame_base_pointer = pt_ref(r.src);
1467+ assert(frame_base_pointer != nullptr);
1468+ assert(spawned_frame->base() == frame_base_pointer);
1469+#endif
1470+
1471+ const auto starting_pc = vt_ref<vm_pc_t>(r.dest);
1472+
1473+ assert(r.module_ref != nullptr);
1474+ vm.fork(r.thread, *r.module_ref, *spawned_frame, starting_pc);
1475+
1476+ // After the new thread is forked pop off the argument passing frame.
1477+ r.stack.pop_frame();
1478+ }
1479+
1480+ EXEC_DECL(mspawn)
1481+ {
1482+ // Arguments to the spawned thread are passed on the current
1483+ // thread's stack in a new frame.
1484+ auto spawned_frame = r.stack.push_frame();
1485+
1486+#ifndef NDEBUG
1487+ // Validate the stack state
1488+ const auto frame_base_pointer = pt_ref(r.src);
1489+ assert(frame_base_pointer != nullptr);
1490+ assert(spawned_frame->base() == frame_base_pointer);
1491+#endif
1492+
1493+ const auto import_function_id = vt_ref<word_t>(r.mid);
1494+ auto target_module_ref = at_val<vm_module_ref_t>(r.dest);
1495+ assert(target_module_ref != nullptr);
1496+
1497+ const auto &function_ref = target_module_ref->get_function_ref(import_function_id);
1498+
1499+ vm.fork(r.thread, *target_module_ref, *spawned_frame, function_ref.entry_pc);
1500+
1501+ // After the new thread is forked pop off the argument passing frame.
1502+ r.stack.pop_frame();
1503+ }
1504+
1505+ EXEC_DECL(exit)
1506+ {
1507+ assert(false && "test failure");
1508+ r.current_thread_state = vm_thread_state_t::exiting;
1509+ }
1510+
1511+ //
1512+ // Exception operations
1513+ //
1514+
1515+ EXEC_DECL(raise)
1516+ {
1517+ auto e = at_val<vm_alloc_t>(r.src);
1518+ if (e == nullptr)
1519+ throw dereference_nil{ "Raise exception" };
1520+
1521+ const vm_string_t *exception_id;
1522+
1523+ // Determine if a 'string' or 'exception' type was raised.
1524+ if (e->alloc_type == vm_string_t::type_desc())
1525+ {
1526+ // String
1527+ exception_id = dynamic_cast<vm_string_t *>(e);
1528+
1529+ if (debug::is_component_tracing_enabled<debug::component_trace_t::exception>())
1530+ debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "raise: string: >>%s<<\n", exception_id->str());
1531+ }
1532+ else
1533+ {
1534+ // [SPEC] The exception format is not formally specified.
1535+ // The first pointer in the exception is a string type
1536+ // that is used to uniquely identify the exception type.
1537+ // The remainder of the type is user-defined.
1538+ auto content = e->allocation();
1539+ exception_id = at_val<vm_string_t>(content);
1540+
1541+ if (debug::is_component_tracing_enabled<debug::component_trace_t::exception>())
1542+ debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "raise: exception: >>%s<<\n", exception_id->str());
1543+ }
1544+
1545+ const handler_t *handler;
1546+ vm_frame_t *target_frame;
1547+ vm_pc_t handler_pc;
1548+
1549+ // The faulting pc is minus one since the VM has already incremented the counter.
1550+ const auto faulting_pc = r.pc - 1;
1551+
1552+ assert(r.module_ref != nullptr);
1553+ std::tie(handler, target_frame, handler_pc) = find_exception_handler(r.stack, *r.module_ref, faulting_pc, *exception_id);
1554+ assert(target_frame != nullptr);
1555+
1556+ // Now that we know the exception has a handler, take a reference.
1557+ e->add_ref();
1558+
1559+ if (target_frame != r.stack.peek_frame())
1560+ {
1561+ while (target_frame != r.stack.pop_frame())
1562+ {
1563+ // Remove frames from the stack until the target frame is found.
1564+ }
1565+ }
1566+
1567+ // Re-initialize the current frame
1568+ if (handler->type_desc != nullptr)
1569+ {
1570+ destroy_memory(*handler->type_desc, target_frame->base());
1571+ init_memory(*handler->type_desc, target_frame->base());
1572+ }
1573+
1574+ // Set the exception at the appropriate frame offset
1575+ auto frame_pointer = reinterpret_cast<uint8_t *>(target_frame->base());
1576+ auto exception_destination = reinterpret_cast<pointer_t>(frame_pointer + handler->exception_offset);
1577+
1578+ // Destroy any data at this location.
1579+ auto current_value = at_val<vm_alloc_t>(exception_destination);
1580+ dec_ref_count_and_free(current_value);
1581+
1582+ // Set the exception destination.
1583+ pt_ref(exception_destination) = e->allocation();
1584+
1585+ r.pc = handler_pc;
1586+ }
1587+
1588+ //
1589+ // Module operations
1590+ //
1591+
1592+ EXEC_DECL(load)
1593+ {
1594+ // Validate module loading module.
1595+ auto importing_module = r.module_ref->module;
1596+ if (!util::has_flag(importing_module->header.runtime_flag, runtime_flags_t::has_import))
1597+ throw vm_user_exception{ "Invalid importing module" };
1598+
1599+ auto str = at_val<vm_string_t>(r.src);
1600+ assert(str != nullptr);
1601+
1602+ dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest));
1603+ pt_ref(r.dest) = nullptr;
1604+
1605+ auto imported_module = std::shared_ptr<const vm_module_t>{};
1606+
1607+ // Handle self loading module optimization.
1608+ if (::strcmp(str->str(), "$self") == 0)
1609+ {
1610+ imported_module = r.module_ref->module;
1611+ }
1612+ else
1613+ {
1614+ try
1615+ {
1616+ imported_module = vm.load_module(str->str());
1617+ }
1618+ catch (const vm_module_exception &e)
1619+ {
1620+ debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "exception: load: >>%s<<\n", e.what());
1621+ assert(false && "Failure reading module");
1622+ return;
1623+ }
1624+ }
1625+
1626+ auto module_import_index = vt_ref<uint32_t>(r.mid);
1627+ const auto &imports = importing_module->import_section;
1628+
1629+ assert(module_import_index < imports.size());
1630+ const auto &function_imports = imports[module_import_index];
1631+
1632+ auto entry_module_ref = new vm_module_ref_t{ imported_module, function_imports };
1633+ pt_ref(r.dest) = entry_module_ref->allocation();
1634+ }
1635+}
1636+
1637+const vm_exec_t disvm::runtime::vm_exec_table[static_cast<std::size_t>(opcode_t::last_opcode) + 1] =
1638+{
1639+ invalid,
1640+ alt,
1641+ nbalt,
1642+ goto_,
1643+ call,
1644+ frame,
1645+ spawn,
1646+ runt,
1647+ load,
1648+ mcall,
1649+ mspawn,
1650+ mframe,
1651+ ret,
1652+ jmp,
1653+ casew,
1654+ exit,
1655+ new_,
1656+ newa,
1657+ newcb,
1658+ newcw,
1659+ newcf,
1660+ newcp,
1661+ newcm,
1662+ newcmp,
1663+ send,
1664+ recv,
1665+ consb,
1666+ consw,
1667+ consp,
1668+ consf,
1669+ notimpl, // consm,
1670+ consmp,
1671+ headb,
1672+ headw,
1673+ headp,
1674+ headf,
1675+ notimpl, // headm,
1676+ headmp,
1677+ tail,
1678+ lea,
1679+ indx,
1680+ movp,
1681+ movm,
1682+ movmp,
1683+ movb,
1684+ movw,
1685+ movf,
1686+ cvtbw,
1687+ cvtwb,
1688+ cvtfw,
1689+ cvtwf,
1690+ cvtca,
1691+ cvtac,
1692+ cvtwc,
1693+ cvtcw,
1694+ cvtfc,
1695+ cvtcf,
1696+ addb,
1697+ addw,
1698+ addf,
1699+ subb,
1700+ subw,
1701+ subf,
1702+ mulb,
1703+ mulw,
1704+ mulf,
1705+ divb,
1706+ divw,
1707+ divf,
1708+ modw,
1709+ modb,
1710+ andb,
1711+ andw,
1712+ orb,
1713+ orw,
1714+ xorb,
1715+ xorw,
1716+ shlb,
1717+ shlw,
1718+ shrb,
1719+ shrw,
1720+ insc,
1721+ indc,
1722+ addc,
1723+ lenc,
1724+ lena,
1725+ lenl,
1726+ beqb,
1727+ bneb,
1728+ bltb,
1729+ bleb,
1730+ bgtb,
1731+ bgeb,
1732+ beqw,
1733+ bnew,
1734+ bltw,
1735+ blew,
1736+ bgtw,
1737+ bgew,
1738+ beqf,
1739+ bnef,
1740+ bltf,
1741+ blef,
1742+ bgtf,
1743+ bgef,
1744+ beqc,
1745+ bnec,
1746+ bltc,
1747+ blec,
1748+ bgtc,
1749+ bgec,
1750+ slicea,
1751+ slicela,
1752+ slicec,
1753+ indw,
1754+ indf,
1755+ indb,
1756+ negf,
1757+ movl,
1758+ addl,
1759+ subl,
1760+ divl,
1761+ modl,
1762+ mull,
1763+ andl,
1764+ orl,
1765+ xorl,
1766+ shll,
1767+ shrl,
1768+ bnel,
1769+ bltl,
1770+ blel,
1771+ bgtl,
1772+ bgel,
1773+ beql,
1774+ cvtlf,
1775+ cvtfl,
1776+ cvtlw,
1777+ cvtwl,
1778+ cvtlc,
1779+ cvtcl,
1780+ headl,
1781+ consl,
1782+ newcl,
1783+ casec,
1784+ indl,
1785+ notimpl, // movpc,
1786+ tcmp,
1787+ mnewz,
1788+ cvtrf,
1789+ cvtfr,
1790+ cvtws,
1791+ cvtsw,
1792+ lsrw,
1793+ lsrl,
1794+ eclr,
1795+ newz,
1796+ newaz,
1797+ raise,
1798+ casel,
1799+ mulx,
1800+ divx,
1801+ cvtxx,
1802+ notimpl, // mulx0,
1803+ notimpl, // divx0,
1804+ notimpl, // cvtxx0,
1805+ notimpl, // mulx1,
1806+ notimpl, // divx1,
1807+ notimpl, // cvtxx1,
1808+ cvtfx,
1809+ cvtxf,
1810+ expw,
1811+ expl,
1812+ expf,
1813+ notimpl, // self,
1814+};
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+
12+namespace disvm
13+{
14+ namespace runtime
15+ {
16+ // VM instruction execution table
17+ extern const vm_exec_t vm_exec_table[];
18+ }
19+}
20+
21+#endif // _DISVM_SRC_VM_EXECUTION_TABLE_H_
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+
15+using namespace disvm;
16+using namespace disvm::runtime;
17+
18+// Empty destructors for vm garbage collector 'interfaces'
19+vm_garbage_collector_t::~vm_garbage_collector_t()
20+{
21+}
22+
23+default_garbage_collector_t::default_garbage_collector_t(vm_t &vm)
24+ : _collection_epoch{ 0 }
25+ , _vm{ vm }
26+{
27+}
28+
29+default_garbage_collector_t::~default_garbage_collector_t()
30+{
31+ std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };
32+ for (auto a : _tracking_allocs)
33+ dec_ref_count_and_free(a);
34+}
35+
36+namespace
37+{
38+ enum class gc_colour_t
39+ {
40+ white,
41+ grey,
42+ black,
43+ };
44+
45+ const auto current_colours = std::array<gc_colour_t, 3>{ gc_colour_t::white, gc_colour_t::grey, gc_colour_t::black };
46+ const auto sweeper_colours = std::array<gc_colour_t, 3>{ gc_colour_t::grey, gc_colour_t::black, gc_colour_t::white };
47+
48+ gc_colour_t get_current_colour(const std::size_t epoch)
49+ {
50+ return current_colours[epoch % current_colours.size()];
51+ }
52+
53+ gc_colour_t get_sweeper_colour(const std::size_t epoch)
54+ {
55+ return sweeper_colours[epoch % sweeper_colours.size()];
56+ }
57+
58+ gc_colour_t get_gc_colour(const vm_alloc_t *a)
59+ {
60+ return *reinterpret_cast<const gc_colour_t *>(&a->gc_reserved);
61+ }
62+
63+ void set_gc_colour(vm_alloc_t *a, const gc_colour_t c)
64+ {
65+ a->gc_reserved = reinterpret_cast<pointer_t>(c);
66+ }
67+}
68+
69+void default_garbage_collector_t::track_allocation(vm_alloc_t *alloc)
70+{
71+ assert(alloc != nullptr);
72+
73+ if (debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>())
74+ debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: track: %#" PRIxPTR "\n", alloc);
75+
76+ alloc->add_ref();
77+ set_gc_colour(alloc, get_current_colour(_collection_epoch));
78+
79+ std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };
80+ _tracking_allocs.push_back(alloc);
81+}
82+
83+void default_garbage_collector_t::enum_tracked_allocations(vm_alloc_callback_t callback) const
84+{
85+ if (callback == nullptr)
86+ throw vm_system_exception{ "Callback should not be null" };
87+
88+ std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };
89+ for (auto a : _tracking_allocs)
90+ callback(a);
91+}
92+
93+namespace
94+{
95+ void mark(const std::vector<std::shared_ptr<const vm_thread_t>> &threads, const gc_colour_t curr_colour)
96+ {
97+ std::queue<vm_alloc_t *> allocs;
98+
99+ auto add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs](pointer_t p) -> void
100+ {
101+ auto a = vm_alloc_t::from_allocation(p);
102+ allocs.push(a);
103+ } };
104+
105+ // Get roots from threads
106+ for (const auto &t : threads)
107+ {
108+ const auto &r = t->get_registers();
109+
110+ // Current MP
111+ allocs.push(r.mp_base);
112+
113+ // Traverse the stack for roots
114+ for (auto frame = r.stack.peek_frame(); frame != nullptr; frame = frame->prev_frame())
115+ {
116+ // Previous MP
117+ if (frame->prev_module_ref() != nullptr)
118+ {
119+ auto prev_mp = frame->prev_module_ref()->mp_base;
120+ allocs.push(prev_mp);
121+ }
122+
123+ if (frame->frame_type->map_in_bytes == 0)
124+ continue;
125+
126+ enum_pointer_fields(*frame->frame_type, frame->base(), add_pointer_to_queue);
127+ }
128+ }
129+
130+ const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>();
131+ if (log_enabled)
132+ {
133+ debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: roots found: %" PRIuPTR "\n", allocs.size());
134+
135+ // If logging is enabled update the callback with logging
136+ add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs](pointer_t p) -> void
137+ {
138+ auto a = vm_alloc_t::from_allocation(p);
139+ debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "\tref: %#" PRIxPTR "\n", a);
140+ allocs.push(a);
141+ } };
142+ }
143+
144+ // Traverse the graph
145+ while (!allocs.empty())
146+ {
147+ auto curr = allocs.front();
148+ allocs.pop();
149+
150+ if (log_enabled)
151+ debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: mark: alloc: %#" PRIxPTR "\n", curr);
152+
153+ if (curr->alloc_type->map_in_bytes > 0)
154+ enum_pointer_fields(*curr->alloc_type, curr->allocation(), add_pointer_to_queue);
155+
156+ set_gc_colour(curr, curr_colour);
157+ }
158+ }
159+
160+ void sweep(std::deque<vm_alloc_t *> &allocs, const gc_colour_t sweeper_colour)
161+ {
162+ auto allocs_local = std::move(allocs);
163+
164+ auto freed = uint32_t{ 0 };
165+ for (auto a : allocs_local)
166+ {
167+ const auto colour = get_gc_colour(a);
168+ if (colour == sweeper_colour)
169+ {
170+ dec_ref_count_and_free(a);
171+ freed++;
172+ continue;
173+ }
174+
175+ allocs.push_back(a);
176+ }
177+
178+ if (debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>())
179+ debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: sweep: %u\n", freed);
180+ }
181+}
182+
183+bool default_garbage_collector_t::collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads)
184+{
185+ // No need to lock the actual memory allocator since the contract
186+ // for this function call indicates all VM threads should be blocked.
187+ std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };
188+
189+ if (threads.empty() || _tracking_allocs.empty())
190+ return false;
191+
192+ const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>();
193+ if (log_enabled)
194+ debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: begin: collect\n");
195+
196+ const auto curr_colour = get_current_colour(_collection_epoch);
197+ mark(threads, curr_colour);
198+
199+ const auto sweeper_colour = get_sweeper_colour(_collection_epoch);
200+ sweep(_tracking_allocs, sweeper_colour);
201+
202+ if (log_enabled)
203+ debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: end: collect\n");
204+
205+ ++_collection_epoch;
206+ return true;
207+}
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+
16+namespace disvm
17+{
18+ namespace runtime
19+ {
20+ // Default garbage collector
21+ class default_garbage_collector_t final : public vm_garbage_collector_t
22+ {
23+ public:
24+ default_garbage_collector_t(vm_t &vm);
25+
26+ ~default_garbage_collector_t();
27+
28+ public: // vm_garbage_collector_t
29+ void track_allocation(vm_alloc_t *alloc) override final;
30+
31+ void enum_tracked_allocations(vm_alloc_callback_t callback) const override final;
32+
33+ bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) override final;
34+
35+ private:
36+ vm_t &_vm;
37+
38+ std::size_t _collection_epoch;
39+ mutable std::mutex _tracking_allocs_lock;
40+ std::deque<vm_alloc_t *> _tracking_allocs;
41+ };
42+ }
43+}
44+
45+#endif // _DISVM_SRC_VM_GARBAGE_COLLECTOR_H_
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+
11+using namespace disvm;
12+using namespace disvm::runtime;
13+
14+std::shared_ptr<const type_descriptor_t> vm_list_t::type_desc()
15+{
16+ return intrinsic_type_desc::type<vm_list_t>();
17+}
18+
19+vm_list_t::vm_list_t(std::shared_ptr<const type_descriptor_t> td, vm_list_t *tail)
20+ : vm_alloc_t(vm_list_t::type_desc())
21+ , _element_type{ td }
22+ , _is_alloc{ sizeof(_mem) < td->size_in_bytes }
23+ , _mem{}
24+ , _tail{ tail }
25+{
26+ assert(_element_type != nullptr);
27+ if (_tail != nullptr)
28+ {
29+ assert(tail->get_element_type() == _element_type);
30+ _tail->add_ref();
31+ }
32+
33+ // If the default list element storage is not enough, allocate more memory.
34+ if (_is_alloc)
35+ {
36+ _mem.alloc = static_cast<pointer_t>(alloc_memory(_element_type->size_in_bytes));
37+ init_memory(*_element_type, _mem.alloc);
38+ }
39+ else
40+ {
41+ init_memory(*_element_type, &_mem.local);
42+ }
43+
44+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm list: new\n");
45+}
46+
47+vm_list_t::~vm_list_t()
48+{
49+ // Remove tail
50+ dec_ref_count_and_free(_tail);
51+ _tail = nullptr;
52+
53+ // Destroy and free the list element, if default element size was surpassed.
54+ if (_is_alloc)
55+ {
56+ destroy_memory(*_element_type, _mem.alloc);
57+ free_memory(_mem.alloc);
58+ }
59+ else
60+ {
61+ destroy_memory(*_element_type, &_mem.local);
62+ }
63+
64+ debug::assign_debug_pointer(&_mem.alloc);
65+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm list\n");
66+}
67+
68+std::shared_ptr<const type_descriptor_t> vm_list_t::get_element_type() const
69+{
70+ return _element_type;
71+}
72+
73+word_t vm_list_t::get_length() const
74+{
75+ auto len = word_t{ 1 };
76+ auto list = _tail;
77+ while (list != nullptr)
78+ {
79+ len++;
80+ list = list->get_tail();
81+ }
82+
83+ return len;
84+}
85+
86+vm_list_t *vm_list_t::get_tail() const
87+{
88+ return _tail;
89+}
90+
91+vm_list_t *vm_list_t::drop_tail()
92+{
93+ auto tail = _tail;
94+ _tail = nullptr;
95+ tail->release();
96+ return tail;
97+}
98+
99+pointer_t vm_list_t::value() const
100+{
101+ if (_is_alloc)
102+ return _mem.alloc;
103+ else
104+ return reinterpret_cast<pointer_t>(&_mem.local);
105+}
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+
21+using namespace disvm;
22+using namespace disvm::runtime;
23+using namespace disvm::format;
24+
25+namespace
26+{
27+ // constants used in module processing
28+ struct module_constants
29+ {
30+ static const auto max_type_pointer_count = operand_t{ 128 * 1024 };
31+ static const auto no_error_handle_type_desc = operand_t{ -1 };
32+ static const auto no_entry_pc = vm_pc_t{ -1 };
33+ static const auto vm_module_type_desc_number = std::size_t{ 0 };
34+ static const auto max_module_name_bytes = std::size_t{ 128 }; // Including null
35+ static const auto array_address_stack_size = std::size_t{ 4 };
36+ };
37+
38+ // enumeration for types in the data section
39+ enum class datum_type_t
40+ {
41+ value_bit8 = 1,
42+ value_bit32 = 2,
43+ utf_string = 3,
44+ value_real64 = 4,
45+ array = 5,
46+ set_array = 6,
47+ restore_load_address = 7,
48+ value_bit64 = 8
49+ };
50+
51+ // Read the next operand as defined in the Dis VM specification.
52+ std::tuple<bool, operand_t> read_next_operand(util::buffered_reader_t &reader)
53+ {
54+ auto result = operand_t{ 0 };
55+
56+ // Converting stack allocation to buffer since max size of operand is a machine word
57+ byte_t buffer[sizeof(result)];
58+
59+ if (!reader.get_next_bytes(1, buffer))
60+ return std::make_tuple(false, 0);
61+
62+ switch (buffer[0] & 0xc0)
63+ {
64+ case 0x00:
65+ // 1 byte operand
66+ result = buffer[0];
67+ break;
68+
69+ case 0x40:
70+ // 1 byte operand - Preserve sign
71+ result = buffer[0] | ~0x7f;
72+ break;
73+
74+ case 0x80:
75+ // 2 byte operand
76+ if (!reader.get_next_bytes(1, buffer + 1))
77+ return std::make_tuple(false, 0);
78+
79+ // Preserve sign
80+ if ((buffer[0] & 0x20) != 0)
81+ buffer[0] |= ~0x3f;
82+ else
83+ buffer[0] &= 0x3f;
84+
85+ result = (buffer[0] << 8);
86+ result |= buffer[1];
87+ break;
88+
89+ case 0xc0:
90+ // 4 byte operand
91+ if (!reader.get_next_bytes(3, buffer + 1))
92+ return std::make_tuple(false, 0);
93+
94+ // Preserve sign
95+ if ((buffer[0] & 0x20) != 0)
96+ buffer[0] |= ~0x3f;
97+ else
98+ buffer[0] &= 0x3f;
99+
100+ result = (buffer[0] << 24);
101+ result |= (buffer[1] << 16);
102+ result |= (buffer[2] << 8);
103+ result |= buffer[3];
104+ break;
105+
106+ default:
107+ assert(false && "Should not be possible");
108+ }
109+
110+ return std::make_tuple(true, result);
111+ }
112+
113+ // Read the next word as defined in the Dis VM specification.
114+ std::tuple<bool, word_t> read_next_word(util::buffered_reader_t &reader)
115+ {
116+ auto result = word_t{};
117+
118+ // Converting stack allocation to buffer since max size of operand is a machine word
119+ byte_t buffer[sizeof(result)];
120+
121+ if (!reader.get_next_bytes(sizeof(result), buffer))
122+ return std::make_tuple(false, 0);
123+
124+ result = (buffer[0] << 24);
125+ result |= (buffer[1] << 16);
126+ result |= (buffer[2] << 8);
127+ result |= buffer[3];
128+
129+ return std::make_tuple(true, result);
130+ }
131+
132+ real_t convert_to_real(const operand_t f, const operand_t s)
133+ {
134+ auto u64 = static_cast<uint64_t>(f);
135+ u64 = (u64 << 32) | (static_cast<uint64_t>(s) & 0xffffffff);
136+
137+ auto r = real_t{};
138+ std::memcpy(&r, &u64, sizeof(u64));
139+
140+ static_assert(sizeof(u64) == sizeof(r), "Real should be 64-bits");
141+ return r;
142+ }
143+
144+ bool read_next_string(util::buffered_reader_t &reader, std::vector<uint8_t> &char_buffer)
145+ {
146+ assert(char_buffer.size() == 0);
147+ char_buffer.reserve(1024);
148+
149+ auto success = bool{};
150+ auto value = uint8_t{};
151+ std::tie(success, value) = reader.get_next_byte();
152+ while (success && value != 0)
153+ {
154+ char_buffer.push_back(value);
155+ std::tie(success, value) = reader.get_next_byte();
156+ }
157+
158+ return success;
159+ }
160+
161+ byte_t *read_8byte_segments(util::buffered_reader_t &reader, datum_type_t type, uint32_t segment_count, byte_t *dest)
162+ {
163+ assert(type == datum_type_t::value_real64 || type == datum_type_t::value_bit64);
164+
165+ auto success = bool{};
166+ auto first = word_t{};
167+ auto second = word_t{};
168+ for (auto idx = uint32_t{ 0 }; idx < segment_count; ++idx)
169+ {
170+ std::tie(success, first) = read_next_word(reader);
171+ if (!success) throw module_reader_exception{ "Failed to read first word" };
172+
173+ std::tie(success, second) = read_next_word(reader);
174+ if (!success) throw module_reader_exception{ "Failed to read second word" };
175+
176+ if (type == datum_type_t::value_real64)
177+ {
178+ auto r = convert_to_real(first, second);
179+ *reinterpret_cast<real_t *>(dest) = r;
180+ dest += sizeof(r);
181+ }
182+ else
183+ {
184+ auto b = static_cast<big_t>(first);
185+ b = (b << 32) | (static_cast<big_t>(second) & 0xffffffff);
186+ *reinterpret_cast<big_t *>(dest) = b;
187+ dest += sizeof(b);
188+ }
189+ }
190+
191+ return dest;
192+ }
193+
194+ std::tuple<address_mode_middle_t, address_mode_t, address_mode_t> convert_to_address_mode(uint8_t instr_addr_mode)
195+ {
196+ // Taken from Dis VM specification
197+ // bit 7 6 5 4 3 2 1 0
198+ // m1 m0 s2 s1 s0 d2 d1 d0
199+
200+ static const auto mid_mask = uint8_t{ 0xc0 };
201+ static const auto src_mask = uint8_t{ 0x38 };
202+ static const auto dest_mask = uint8_t{ 0x07 };
203+ assert((mid_mask | src_mask | dest_mask) == std::numeric_limits<uint8_t>::max());
204+
205+ const auto mid = static_cast<address_mode_middle_t>((instr_addr_mode & mid_mask) >> 6);
206+ const auto src = static_cast<address_mode_t>((instr_addr_mode & src_mask) >> 3);
207+ const auto dest = static_cast<address_mode_t>(instr_addr_mode & dest_mask);
208+
209+ return std::make_tuple(mid, src, dest);
210+ }
211+
212+ // See header format definition in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html)
213+ void read_header(util::buffered_reader_t &reader, vm_module_t &modobj)
214+ {
215+ auto success = bool{};
216+ auto &header = modobj.header;
217+
218+ std::tie(success, header.magic_number) = read_next_operand(reader);
219+ if (!success)
220+ throw module_reader_exception{ "Failed to read magic number" };
221+
222+ assert(header.magic_number == magic_number_constants::xmagic || header.magic_number == magic_number_constants::smagic);
223+
224+ // If the object file is signed, read in the signature.
225+ if (header.magic_number == magic_number_constants::smagic)
226+ {
227+ std::tie(success, header.Signature.length) = read_next_operand(reader);
228+ if (!success) throw module_reader_exception{ "Failed to read signature length" };
229+
230+ header.Signature.signature.reset(new byte_t[header.Signature.length]);
231+ const auto bytesRead = reader.get_next_bytes(header.Signature.length, header.Signature.signature.get());
232+ if (bytesRead != header.Signature.length) throw module_reader_exception{ "Failed to read full signature" };
233+ }
234+ else
235+ {
236+ header.Signature.length = word_t{ 0 };
237+ }
238+
239+ auto runtime_flags = operand_t{};
240+ std::tie(success, runtime_flags) = read_next_operand(reader);
241+ if (!success) throw module_reader_exception{ "Failed to read runtime flag" };
242+ header.runtime_flag = static_cast<runtime_flags_t>(runtime_flags);
243+
244+ std::tie(success, header.stack_extent) = read_next_operand(reader);
245+ if (!success) throw module_reader_exception{ "Failed to read stack extent" };
246+
247+ std::tie(success, header.code_size) = read_next_operand(reader);
248+ if (!success) throw module_reader_exception{ "Failed to read code size" };
249+
250+ std::tie(success, header.data_size) = read_next_operand(reader);
251+ if (!success) throw module_reader_exception{ "Failed to read data size" };
252+
253+ std::tie(success, header.type_size) = read_next_operand(reader);
254+ if (!success) throw module_reader_exception{ "Failed to read type size" };
255+
256+ std::tie(success, header.export_size) = read_next_operand(reader);
257+ if (!success) throw module_reader_exception{ "Failed to read export size" };
258+
259+ // [SPEC] If a module does not have an entry program counter it is set to '-1'.
260+ std::tie(success, header.entry_pc) = read_next_operand(reader);
261+ if (!success) throw module_reader_exception{ "Failed to entry pc" };
262+
263+ // [SPEC] If a module does not have an entry program counter the entry type index is set to '-1'.
264+ std::tie(success, header.entry_type) = read_next_operand(reader);
265+ if (!success) throw module_reader_exception{ "Failed to read entry type" };
266+ }
267+
268+ // See code section layout in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html)
269+ void read_code_section(util::buffered_reader_t &reader, vm_module_t &modobj)
270+ {
271+ auto success = bool{};
272+ const auto instruction_count = modobj.header.code_size;
273+
274+ // Allocate memory for the instructions
275+ modobj.code_section.resize(instruction_count);
276+
277+ for (auto c = word_t{ 0 }; c < instruction_count; ++c)
278+ {
279+ // Read in a single instruction
280+ byte_t op_and_addrmode[2] = {};
281+ const auto bytesRead = reader.get_next_bytes(sizeof(op_and_addrmode), op_and_addrmode);
282+ if (bytesRead != sizeof(op_and_addrmode)) throw module_reader_exception{ "Failed to read op code and address mode" };
283+
284+ auto vm_instr = vm_instruction_t{};
285+ auto &instr = vm_instr.op;
286+
287+ instr.opcode = static_cast<opcode_t>(op_and_addrmode[0]);
288+ assert(opcode_t::first_opcode <= instr.opcode && instr.opcode <= opcode_t::last_opcode);
289+
290+ std::tie(instr.middle.mode, instr.source.mode, instr.destination.mode) = convert_to_address_mode(op_and_addrmode[1]);
291+
292+ if (instr.middle.mode != address_mode_middle_t::none)
293+ {
294+ std::tie(success, instr.middle.register1) = read_next_operand(reader);
295+ if (!success) throw module_reader_exception{ "Failed to read middle register" };
296+ }
297+
298+ if (instr.source.mode != address_mode_t::none)
299+ {
300+ std::tie(success, instr.source.register1) = read_next_operand(reader);
301+ if (!success) throw module_reader_exception{ "Failed to read source register 1" };
302+
303+ if (instr.source.mode == address_mode_t::offset_double_indirect_fp ||
304+ instr.source.mode == address_mode_t::offset_double_indirect_mp)
305+ {
306+ std::tie(success, instr.source.register2) = read_next_operand(reader);
307+ if (!success) throw module_reader_exception{ "Failed to read source register 2" };
308+
309+ // Double indirect offsets need to be < 2^16-1.
310+ assert(instr.source.register1 <= std::numeric_limits<uint16_t>::max());
311+ assert(instr.source.register2 <= std::numeric_limits<uint16_t>::max());
312+ }
313+ }
314+
315+ if (instr.destination.mode != address_mode_t::none)
316+ {
317+ std::tie(success, instr.destination.register1) = read_next_operand(reader);
318+ if (!success) throw module_reader_exception{ "Failed to read destination register 1" };
319+
320+ if (instr.destination.mode == address_mode_t::offset_double_indirect_fp ||
321+ instr.destination.mode == address_mode_t::offset_double_indirect_mp)
322+ {
323+ std::tie(success, instr.destination.register2) = read_next_operand(reader);
324+ if (!success) throw module_reader_exception{ "Failed to destination register 2" };
325+
326+ // Double indirect offsets need to be < 2^16-1.
327+ assert(instr.destination.register1 <= std::numeric_limits<uint16_t>::max());
328+ assert(instr.destination.register2 <= std::numeric_limits<uint16_t>::max());
329+ }
330+ }
331+
332+ modobj.code_section[c] = std::move(vm_instr);
333+ }
334+
335+ if (modobj.header.entry_pc != module_constants::no_entry_pc && static_cast<std::size_t>(modobj.header.entry_pc) >= modobj.code_section.size())
336+ throw module_reader_exception{ "Invalid initial program counter value for code section" };
337+ }
338+
339+ // See type section layout in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html)
340+ void read_type_section(util::buffered_reader_t &reader, vm_module_t &modobj)
341+ {
342+ auto success = bool{};
343+ const auto type_count = modobj.header.type_size;
344+
345+ // Allocate memory for the types
346+ modobj.type_section.resize(type_count);
347+
348+ for (auto c = word_t{ 0 }; c < type_count; ++c)
349+ {
350+ // Read in single type
351+ operand_t desc_number;
352+ std::tie(success, desc_number) = read_next_operand(reader);
353+ if (!success) throw module_reader_exception{ "Failed to read type descriptor number" };
354+
355+ operand_t size;
356+ std::tie(success, size) = read_next_operand(reader);
357+ if (!success) throw module_reader_exception{ "Failed to read type size" };
358+
359+ operand_t map_in_bytes;
360+ std::tie(success, map_in_bytes) = read_next_operand(reader);
361+ if (!success) throw module_reader_exception{ "Failed to read type pointer count" };
362+ if (map_in_bytes > module_constants::max_type_pointer_count) throw module_reader_exception{ "Invalid Limbo type" };
363+
364+ auto pointer_map = std::vector<uint8_t>(map_in_bytes);
365+ if (map_in_bytes != 0)
366+ {
367+ const auto bytesRead = reader.get_next_bytes(map_in_bytes, pointer_map.data());
368+ if (bytesRead != map_in_bytes) throw module_reader_exception{ "Failed to read type pointer map" };
369+ }
370+
371+ modobj.type_section[desc_number] = std::move(type_descriptor_t::create(size, pointer_map));
372+ }
373+ }
374+
375+ // See data section layout in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html)
376+ void read_data_section(util::buffered_reader_t &reader, vm_module_t &modobj)
377+ {
378+ if (modobj.header.data_size != 0)
379+ {
380+ auto vm_module_type = modobj.type_section[module_constants::vm_module_type_desc_number];
381+ if (vm_module_type->size_in_bytes != modobj.header.data_size) throw module_reader_exception{ "Invalid type desc for MP" };
382+
383+ // The Dis VM spec does not guarantee the MP segment (i.e. data segment) of the loaded
384+ // module will be initialized to zero, but it is the C/C++ approach so defer to that.
385+ modobj.original_mp.reset(vm_alloc_t::allocate(vm_module_type, vm_alloc_t::zero_memory));
386+ }
387+
388+ auto array_stack = std::array<byte_t *, module_constants::array_address_stack_size>{};
389+ auto array_stack_pointer = std::size_t{ 0 };
390+ auto success = bool{};
391+ auto code = byte_t{};
392+ auto base = reinterpret_cast<byte_t *>(modobj.original_mp->allocation());
393+ for (;;)
394+ {
395+ auto bytesRead = reader.get_next_bytes(sizeof(code), &code);
396+ if (bytesRead != sizeof(code)) throw module_reader_exception{ "Failed to read data item code" };
397+ if (code == 0)
398+ break;
399+
400+ const auto type = static_cast<datum_type_t>((code & 0xf0) >> 4);
401+ auto count = static_cast<uint32_t>(code & 0x0f);
402+ if (count == 0)
403+ {
404+ std::tie(success, count) = read_next_operand(reader);
405+ if (!success) throw module_reader_exception{ "Failed to read long item datum count" };
406+ }
407+
408+ auto byte_offset = operand_t{};
409+ std::tie(success, byte_offset) = read_next_operand(reader);
410+ if (!success) throw module_reader_exception{ "Failed to read offset" };
411+
412+ // Compute the next data destination.
413+ auto data_dest = base + byte_offset;
414+
415+ switch (type)
416+ {
417+ case datum_type_t::value_bit8:
418+ {
419+ auto byte = uint8_t{};
420+ for (auto idx = uint32_t{ 0 }; idx < count; ++idx)
421+ {
422+ std::tie(success, byte) = reader.get_next_byte();
423+ if (!success) throw module_reader_exception{ "Failed to read 1 byte" };
424+
425+ *reinterpret_cast<byte_t *>(data_dest) = byte;
426+ data_dest += sizeof(byte);
427+ }
428+ break;
429+ }
430+ case datum_type_t::value_bit32:
431+ {
432+ auto word = word_t{};
433+ for (auto idx = uint32_t{ 0 }; idx < count; ++idx)
434+ {
435+ word = word_t{ 0 };
436+ std::tie(success, word) = read_next_word(reader);
437+ if (!success) throw module_reader_exception{ "Failed to read word" };
438+
439+ *reinterpret_cast<word_t *>(data_dest) = word;
440+ data_dest += sizeof(word);
441+ }
442+ break;
443+ }
444+ case datum_type_t::utf_string:
445+ {
446+ auto str_buffer = std::vector<uint8_t>(count);
447+ auto bytesRead = reader.get_next_bytes(count, str_buffer.data());
448+ if (bytesRead != count)
449+ throw module_reader_exception{ "Failed to read string data" };
450+
451+ auto new_string = new vm_string_t(str_buffer.size(), str_buffer.data());
452+ *reinterpret_cast<pointer_t *>(data_dest) = new_string->allocation();
453+ break;
454+ }
455+ case datum_type_t::value_real64:
456+ data_dest = read_8byte_segments(reader, datum_type_t::value_real64, count, data_dest);
457+ break;
458+ case datum_type_t::array:
459+ {
460+ auto element_type = word_t{};
461+ std::tie(success, element_type) = read_next_word(reader);
462+ if (!success) throw module_reader_exception{ "Failed to read word" };
463+
464+ if (element_type < 0 || modobj.header.type_size < element_type)
465+ if (!success) throw module_reader_exception{ "Invalid array element type" };
466+
467+ auto element_count = word_t{};
468+ std::tie(success, element_count) = read_next_word(reader);
469+ if (!success) throw module_reader_exception{ "Failed to read word" };
470+
471+ auto new_array = new vm_array_t(modobj.type_section[element_type], element_count);
472+ *reinterpret_cast<pointer_t *>(data_dest) = new_array->allocation();
473+ break;
474+ }
475+ case datum_type_t::set_array:
476+ {
477+ if (data_dest == nullptr) throw module_reader_exception{ "Invalid data type processing" };
478+ auto arr_maybe = vm_alloc_t::from_allocation(*reinterpret_cast<pointer_t *>(data_dest));
479+ assert(arr_maybe != nullptr);
480+
481+ if (arr_maybe->alloc_type != vm_array_t::type_desc())
482+ throw module_reader_exception{ "Data index not an array type" };
483+
484+ auto arr = dynamic_cast<vm_array_t *>(arr_maybe);
485+
486+ auto array_index = word_t{};
487+ std::tie(success, array_index) = read_next_word(reader);
488+ if (!success) throw module_reader_exception{ "Failed to read word" };
489+
490+ if (array_index < 0 || arr->get_length() <= array_index || array_stack_pointer >= module_constants::array_address_stack_size)
491+ throw module_reader_exception{ "Invalid array stack pointer" };
492+
493+ array_stack[array_stack_pointer++] = base;
494+ base = reinterpret_cast<byte_t *>(arr->at(0)) + (array_index * arr->alloc_type->size_in_bytes);
495+
496+ break;
497+ }
498+ case datum_type_t::restore_load_address:
499+ if (array_stack_pointer == 0) throw module_reader_exception{ "Invalid array stack pointer" };
500+
501+ base = array_stack[--array_stack_pointer];
502+ assert(base != nullptr);
503+
504+#ifndef NDEBUG
505+ array_stack[array_stack_pointer] = nullptr;
506+#endif
507+
508+ break;
509+ case datum_type_t::value_bit64:
510+ data_dest = read_8byte_segments(reader, datum_type_t::value_bit64, count, data_dest);
511+ break;
512+ default:
513+ throw module_reader_exception{ "Unknown item data type" };
514+ }
515+ }
516+ }
517+
518+ // See module name in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html)
519+ void read_module_name(util::buffered_reader_t &reader, vm_module_t &modobj)
520+ {
521+ auto string_buffer = std::vector<uint8_t>{};
522+ if (!read_next_string(reader, string_buffer))
523+ throw module_reader_exception{ "Failed to read module name" };
524+
525+ modobj.module_name = std::make_unique<vm_string_t>(string_buffer.size(), string_buffer.data());
526+ assert(modobj.module_name->get_length() <= module_constants::max_module_name_bytes);
527+ }
528+
529+ // See link section in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html)
530+ void read_link_section(util::buffered_reader_t &reader, vm_module_t &modobj)
531+ {
532+ auto success = bool{};
533+ const auto export_count = modobj.header.export_size;
534+
535+ // Allocate memory for the export entries
536+ modobj.export_section.reserve(export_count);
537+
538+ for (auto c = word_t{ 0 }; c < export_count; ++c)
539+ {
540+ // Read in single export item
541+ auto item = export_function_t{};
542+
543+ std::tie(success, item.pc) = read_next_operand(reader);
544+ if (!success) throw module_reader_exception{ "Failed to read linkage pc" };
545+
546+ std::tie(success, item.frame_type) = read_next_operand(reader);
547+ if (!success) throw module_reader_exception{ "Failed to read linkage frame type index" };
548+
549+ std::tie(success, item.sig) = read_next_word(reader);
550+ if (!success) throw module_reader_exception{ "Failed to read linkage signature" };
551+
552+ auto string_buffer = std::vector<uint8_t>{};
553+ if (!read_next_string(reader, string_buffer))
554+ throw module_reader_exception{ "Failed to read linkage name" };
555+
556+ item.name = std::make_unique<vm_string_t>(string_buffer.size(), string_buffer.data());
557+
558+ const auto sig = item.sig;
559+ modobj.export_section.emplace(sig, std::move(item));
560+ }
561+ }
562+
563+ // See import section in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html)
564+ // [SPEC] The specification does not seem to match what is observed in an actual module.
565+ // The function is modeled after the disassembler (dis.b and libinterp/load.c) instead.
566+ void read_import_section(util::buffered_reader_t &reader, vm_module_t &modobj)
567+ {
568+ auto success = bool{};
569+ auto module_import_count = operand_t{};
570+ std::tie(success, module_import_count) = read_next_operand(reader);
571+ if (!success) throw module_reader_exception{ "Failed to read module import count" };
572+
573+ // Allocate memory for module imports
574+ modobj.import_section.reserve(module_import_count);
575+
576+ for (auto c = operand_t{ 0 }; c < module_import_count; ++c)
577+ {
578+ // Read in single module import
579+ import_vm_module_t module{};
580+
581+ auto func_import_count = operand_t{};
582+ std::tie(success, func_import_count) = read_next_operand(reader);
583+ if (!success) throw module_reader_exception{ "Failed to read function import count" };
584+
585+ // Allocate memory for function imports
586+ module.functions.reserve(func_import_count);
587+
588+ for (auto k = operand_t{ 0 }; k < func_import_count; ++k)
589+ {
590+ // Read in single function import
591+ import_function_t func{};
592+
593+ std::tie(success, func.sig) = read_next_word(reader);
594+ if (!success) throw module_reader_exception{ "Failed to read import function signature" };
595+
596+ auto string_buffer = std::vector<uint8_t>{};
597+ if (!read_next_string(reader, string_buffer))
598+ throw module_reader_exception{ "Failed to read import function name" };
599+
600+ func.name = std::make_unique<vm_string_t>(string_buffer.size(), string_buffer.data());
601+
602+ module.functions.push_back(std::move(func));
603+ }
604+
605+ modobj.import_section.push_back(std::move(module));
606+ }
607+
608+ byte_t value;
609+ std::tie(success, value) = reader.get_next_byte();
610+ if (!success || value != 0)
611+ throw module_reader_exception{ "Failed to read final byte from import section" };
612+ }
613+
614+ // See handler section in Dis VM specification (http://www.vitanuova.com/inferno/man/6/dis.html)
615+ void read_handler_section(util::buffered_reader_t &reader, vm_module_t &modobj)
616+ {
617+ auto success = bool{};
618+ auto handler_count = operand_t{};
619+ std::tie(success, handler_count) = read_next_operand(reader);
620+
621+ // Allocate memory for handlers
622+ modobj.handler_section.reserve(handler_count);
623+
624+ for (auto c = operand_t{ 0 }; c < handler_count; ++c)
625+ {
626+ // Read in single handler
627+ handler_t handler{};
628+
629+ std::tie(success, handler.exception_offset) = read_next_operand(reader);
630+ if (!success) throw module_reader_exception{ "Failed to read exception offset" };
631+
632+ std::tie(success, handler.begin_pc) = read_next_operand(reader);
633+ if (!success) throw module_reader_exception{ "Failed to read handler begin program counter" };
634+
635+ std::tie(success, handler.end_pc) = read_next_operand(reader);
636+ if (!success) throw module_reader_exception{ "Failed to read handler end program counter" };
637+
638+ auto type_desc_number = operand_t{};
639+ std::tie(success, type_desc_number) = read_next_operand(reader);
640+ if (!success) throw module_reader_exception{ "Failed to read handler type description ID" };
641+
642+ if (type_desc_number != module_constants::no_error_handle_type_desc)
643+ {
644+ assert(0 <= type_desc_number && type_desc_number < static_cast<operand_t>(modobj.type_section.size()));
645+ handler.type_desc = modobj.type_section[type_desc_number];
646+ assert(handler.type_desc != nullptr);
647+ }
648+
649+ auto handler_cases = operand_t{};
650+ std::tie(success, handler_cases) = read_next_operand(reader);
651+
652+ // [SPEC] The operand read above contains two pieces of data.
653+ // The higher 16-bits represent the number of exception cases that catch exception types.
654+ // The lower 16-bits represent the total number of handled cases.
655+ // In the limbo language strings and exceptions can be caught in an exception handler.
656+ // Based on Inferno implementation libinterp/load.c.
657+ handler.exception_type_count = word_t{ handler_cases >> 16 };
658+ const auto total_count = (handler_cases & 0xffff);
659+
660+ // Allocate memory for exceptions plus the wild card.
661+ handler.exception_table.reserve(total_count + 1);
662+
663+ for (auto k = operand_t{ 0 }; k < total_count; ++k)
664+ {
665+ // Read in single exception
666+ exception_t except{};
667+
668+ auto string_buffer = std::vector<uint8_t>{};
669+ if (!read_next_string(reader, string_buffer))
670+ throw module_reader_exception{ "Failed to read exception name" };
671+
672+ except.name = std::make_unique<vm_string_t>(string_buffer.size(), string_buffer.data());
673+
674+ std::tie(success, except.pc) = read_next_operand(reader);
675+ if (!success) throw module_reader_exception{ "Failed to read exception pc" };
676+
677+ handler.exception_table.push_back(std::move(except));
678+ }
679+
680+ // Read in wildcard exception
681+ exception_t wildcard_exception{};
682+ std::tie(success, wildcard_exception.pc) = read_next_operand(reader);
683+ if (!success) throw module_reader_exception{ "Failed to read wildcard exception pc" };
684+
685+ handler.exception_table.push_back(std::move(wildcard_exception));
686+
687+ modobj.handler_section.push_back(std::move(handler));
688+ }
689+
690+ byte_t value;
691+ std::tie(success, value) = reader.get_next_byte();
692+ if (!success || value != 0)
693+ throw module_reader_exception{ "Failed to read final byte from handler section" };
694+ }
695+}
696+
697+export_function_t::export_function_t(export_function_t &&other)
698+ : pc{ other.pc }
699+ , frame_type{ other.frame_type }
700+ , sig{ other.sig }
701+ , name{ std::move(other.name) }
702+{
703+}
704+
705+export_function_t::~export_function_t()
706+{
707+ if (name != nullptr)
708+ name->release();
709+}
710+
711+import_function_t::import_function_t(import_function_t &&other)
712+ : sig{ other.sig }
713+ , name{ std::move(other.name) }
714+{
715+}
716+
717+import_function_t::~import_function_t()
718+{
719+ if (name != nullptr)
720+ name->release();
721+}
722+
723+import_vm_module_t::import_vm_module_t(import_vm_module_t &&other)
724+ : functions{ std::move(other.functions) }
725+{
726+}
727+
728+exception_t::exception_t(exception_t &&other)
729+ : pc{ other.pc }
730+ , name{ std::move(other.name) }
731+{
732+}
733+
734+exception_t::~exception_t()
735+{
736+ if (name != nullptr)
737+ name->release();
738+}
739+
740+handler_t::handler_t(handler_t &&other)
741+ : exception_offset{ other.exception_offset }
742+ , begin_pc{ other.begin_pc }
743+ , end_pc{ other.end_pc }
744+ , type_desc{ std::move(other.type_desc) }
745+ , exception_table{ std::move(other.exception_table) }
746+ , exception_type_count{ other.exception_type_count }
747+{
748+}
749+
750+vm_module_t::~vm_module_t()
751+{
752+ if (module_name != nullptr)
753+ module_name->release();
754+
755+ if (original_mp != nullptr)
756+ original_mp->release();
757+}
758+
759+std::unique_ptr<vm_module_t> disvm::read_module(const char *path)
760+{
761+ assert(path != nullptr);
762+
763+ auto module_file = std::ifstream{ path, std::ifstream::binary };
764+ if (!module_file.is_open())
765+ {
766+ assert(false && "Failed to open module");
767+ throw vm_user_exception{ "Failed to open module" };
768+ }
769+
770+ return read_module(module_file);
771+}
772+
773+std::unique_ptr<runtime::vm_module_t> disvm::read_module(std::istream &data)
774+{
775+ util::buffered_reader_t reader{ data };
776+
777+ auto modobj = std::make_unique<vm_module_t>();
778+
779+ read_header(reader, *modobj);
780+
781+ if (util::has_flag(modobj->header.runtime_flag, runtime_flags_t::has_import_deprecated))
782+ throw module_reader_exception{ "Obsolete module" };
783+
784+ read_code_section(reader, *modobj);
785+ read_type_section(reader, *modobj);
786+ read_data_section(reader, *modobj);
787+ read_module_name(reader, *modobj);
788+ read_link_section(reader, *modobj);
789+
790+ if (util::has_flag(modobj->header.runtime_flag, runtime_flags_t::has_import))
791+ read_import_section(reader, *modobj);
792+
793+ if (util::has_flag(modobj->header.runtime_flag, runtime_flags_t::has_handler))
794+ read_handler_section(reader, *modobj);
795+
796+ // [TODO] Native code generation - runtime_flags_t::must_compile
797+ assert(!util::has_flag(modobj->header.runtime_flag, runtime_flags_t::must_compile));
798+
799+ return modobj;
800+}
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+
13+using namespace disvm;
14+using namespace disvm::runtime;
15+
16+namespace
17+{
18+ std::unique_ptr<vm_array_t> resolve_imports(const export_section_t &exports, const import_vm_module_t &imports)
19+ {
20+ static auto function_ref_type_desc = type_descriptor_t::create(sizeof(vm_module_function_ref_t), std::vector<byte_t>{});
21+ auto refs = std::make_unique<vm_array_t>(function_ref_type_desc, imports.functions.size());
22+
23+ for (auto index = std::size_t{ 0 }; index < imports.functions.size(); ++index)
24+ {
25+ const auto &import = imports.functions[index];
26+
27+ const auto exports_with_sig = exports.equal_range(import.sig);
28+ if (exports_with_sig.first == exports.cend() && exports_with_sig.second == exports.cend())
29+ throw vm_user_exception{ "Module does not export a function with expected signature" };
30+
31+ auto export_match = exports_with_sig.first;
32+ while (export_match != exports_with_sig.second)
33+ {
34+ // Look for the matching function name in those with matching signature
35+ if (0 == vm_string_t::compare(export_match->second.name.get(), import.name.get()))
36+ break;
37+
38+ ++export_match;
39+ }
40+
41+ if (export_match == exports_with_sig.second)
42+ throw vm_user_exception{ "Type check failure for exported function" };
43+
44+ const auto &export_entry = export_match->second;
45+
46+ auto &ref = refs->at<vm_module_function_ref_t>(index);
47+ ref.entry_pc = export_entry.pc;
48+ ref.frame_type = export_entry.frame_type;
49+
50+ if (debug::is_component_tracing_enabled<debug::component_trace_t::module>())
51+ {
52+ debug::log_msg(
53+ debug::component_trace_t::module,
54+ debug::log_level_t::debug,
55+ "export: %d %#08x >>%s<<\n",
56+ index,
57+ export_entry.sig,
58+ export_entry.name->str());
59+ }
60+ }
61+
62+ return refs;
63+ }
64+}
65+
66+std::shared_ptr<const type_descriptor_t> vm_module_ref_t::type_desc()
67+{
68+ return intrinsic_type_desc::type<vm_module_ref_t>();
69+}
70+
71+vm_module_ref_t::vm_module_ref_t(std::shared_ptr<const vm_module_t> module)
72+ : vm_alloc_t(vm_module_ref_t::type_desc())
73+ , code_section{ module->code_section }
74+ , module{ module }
75+ , mp_base{ nullptr }
76+ , type_section{ module->type_section }
77+ , _builtin_module{ util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin) }
78+{
79+ if (module->original_mp != nullptr)
80+ mp_base = vm_alloc_t::copy(*module->original_mp);
81+
82+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
83+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm module ref\n");
84+}
85+
86+vm_module_ref_t::vm_module_ref_t(std::shared_ptr<const vm_module_t> module, const import_vm_module_t &imports)
87+ : vm_alloc_t(vm_module_ref_t::type_desc())
88+ , code_section{ module->code_section }
89+ , module{ module }
90+ , type_section{ module->type_section }
91+ , _function_refs{ resolve_imports(module->export_section, imports) }
92+ , _builtin_module{ util::has_flag(module->header.runtime_flag, runtime_flags_t::builtin) }
93+{
94+ assert(module->header.data_size == 0 || module->original_mp != nullptr);
95+
96+ if (module->original_mp != nullptr)
97+ mp_base = vm_alloc_t::copy(*module->original_mp);
98+
99+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
100+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm module ref: exported %d\n", _function_refs->get_length());
101+}
102+
103+vm_module_ref_t::~vm_module_ref_t()
104+{
105+ dec_ref_count_and_free(mp_base);
106+
107+ if (_function_refs != nullptr)
108+ _function_refs->release();
109+
110+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
111+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm module ref\n");
112+}
113+
114+bool vm_module_ref_t::is_builtin_module() const
115+{
116+ return _builtin_module;
117+}
118+
119+const vm_module_function_ref_t& vm_module_ref_t::get_function_ref(word_t index) const
120+{
121+ assert(_function_refs != nullptr);
122+
123+ if (index < 0 || _function_refs->get_length() <= index)
124+ throw vm_system_exception{ "Invalid function reference index into module reference functions" };
125+
126+ return _function_refs->at<vm_module_function_ref_t>(index);
127+}
128+
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+
11+using namespace disvm;
12+using namespace disvm::runtime;
13+
14+// Empty destructors for vm scheduler 'interfaces'
15+vm_scheduler_t::~vm_scheduler_t()
16+{
17+}
18+
19+vm_scheduler_control_t::~vm_scheduler_control_t()
20+{
21+}
22+
23+void default_scheduler_t::worker_main(default_scheduler_t &instance)
24+{
25+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: worker: start\n");
26+
27+ // [TODO] Handle cases of when a vm_system_exception is thrown
28+
29+ auto current_thread = std::shared_ptr<thread_instance_t>{};
30+ for (;;)
31+ {
32+ current_thread = instance.next_thread(std::move(current_thread));
33+ if (current_thread == nullptr)
34+ {
35+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: worker: stop\n");
36+ break;
37+ }
38+
39+ if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>())
40+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: worker: execute: %d\n", current_thread->vm_thread->get_thread_id());
41+
42+ current_thread->vm_thread->execute(instance._vm, instance._vm_thread_quanta);
43+ }
44+}
45+
46+default_scheduler_t::default_scheduler_t(vm_t &vm, uint32_t system_thread_count, uint32_t thread_quanta)
47+ : _gc_complete{ true }
48+ , _gc_counter{ 1 }
49+ , _worker_thread_count{ system_thread_count }
50+ , _vm{ vm }
51+ , _vm_thread_quanta{ thread_quanta }
52+{
53+}
54+
55+default_scheduler_t::~default_scheduler_t()
56+{
57+ // Drain the runnable queue and clear out all the threads
58+ {
59+ std::lock_guard<std::mutex> lock{ _vm_threads_lock };
60+ while (!_runnable_vm_threads.empty())
61+ _runnable_vm_threads.pop();
62+
63+ _all_vm_threads.clear();
64+ }
65+
66+ // Set terminating flag
67+ {
68+ std::unique_lock<std::mutex> worker_event_lock{ _worker_event_lock };
69+ _terminating = true;
70+ }
71+
72+ // Notify all workers
73+ _worker_event.notify_all();
74+
75+ // Wait for all worker threads to finish
76+ for (auto &w : _worker_pool)
77+ w.join();
78+
79+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: shutdown\n");
80+}
81+
82+bool default_scheduler_t::is_idle() const
83+{
84+ std::lock_guard<std::mutex> lock{ _vm_threads_lock };
85+ return _running_vm_thread_count == 0 && _runnable_vm_threads.empty() && _blocked_vm_thread_ids.empty();
86+}
87+
88+vm_scheduler_control_t &default_scheduler_t::get_controller() const
89+{
90+ return const_cast<default_scheduler_t&>(*this);
91+}
92+
93+const vm_thread_t& default_scheduler_t::schedule_thread(std::unique_ptr<runtime::vm_thread_t> thread)
94+{
95+ assert(thread != nullptr);
96+ if (thread->get_state() != vm_thread_state_t::ready)
97+ throw vm_system_exception{ "Scheduled thread in invalid state" };
98+
99+ if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>())
100+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: scheduled: %d\n", thread->get_thread_id());
101+
102+ std::lock_guard<std::mutex> lock{ _vm_threads_lock };
103+
104+ // Create workers on first scheduled thread
105+ if (_worker_pool.empty())
106+ {
107+ for (auto i = uint32_t{ 0 }; i < _worker_thread_count; ++i)
108+ _worker_pool.push_back(std::thread{ default_scheduler_t::worker_main, std::ref(*this) });
109+ }
110+
111+ const auto new_thread_id = thread->get_thread_id();
112+ assert(_all_vm_threads.find(new_thread_id) == _all_vm_threads.cend());
113+
114+ auto new_thread = std::make_shared<thread_instance_t>(std::move(thread));
115+
116+ // Allocate a new container and set the thread entry
117+ _all_vm_threads[new_thread_id] = new_thread;
118+
119+ _runnable_vm_threads.push(new_thread);
120+
121+ // Notify the worker a thread has been enqueued.
122+ _worker_event.notify_one();
123+
124+ return *(new_thread->vm_thread);
125+}
126+
127+void default_scheduler_t::enqueue_blocked_thread(uint32_t thread_id)
128+{
129+ all_thread_map_t::iterator thread_to_enqueue;
130+ {
131+ std::lock_guard<std::mutex> lock{ _vm_threads_lock };
132+ thread_to_enqueue = _all_vm_threads.find(thread_id);
133+ if (thread_to_enqueue == _all_vm_threads.cend())
134+ {
135+ assert(false && "Unknown thread to enqueue");
136+ return;
137+ }
138+ }
139+
140+ auto thread_container = thread_to_enqueue->second;
141+
142+ // Lock the vm thread collections and this vm thread
143+ std::lock(_vm_threads_lock, thread_container->system_thread_ownership);
144+ std::lock_guard<std::mutex> lock_all{ _vm_threads_lock, std::adopt_lock };
145+ std::lock_guard<std::mutex> lock_thread{ thread_container->system_thread_ownership, std::adopt_lock };
146+
147+ // If the thread is not blocked, just return.
148+ auto blocked_iter = _blocked_vm_thread_ids.find(thread_id);
149+ if (blocked_iter == _blocked_vm_thread_ids.cend())
150+ {
151+ assert(false && "Thread to enqueue not blocked");
152+ return;
153+ }
154+
155+ _blocked_vm_thread_ids.erase(blocked_iter);
156+
157+ if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>())
158+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: enqueue blocked: %d\n", thread_id);
159+
160+ enqueue_thread_unsafe(thread_container, vm_thread_state_t::ready);
161+}
162+
163+std::vector<std::shared_ptr<const vm_thread_t>> default_scheduler_t::get_all_threads() const
164+{
165+ auto result = std::vector<std::shared_ptr<const vm_thread_t>>{};
166+
167+ {
168+ std::lock_guard<std::mutex> lock{ _vm_threads_lock };
169+ for (auto &p : _all_vm_threads)
170+ result.push_back(p.second->vm_thread);
171+ }
172+
173+ return result;
174+}
175+
176+std::shared_ptr<default_scheduler_t::thread_instance_t> default_scheduler_t::next_thread(std::shared_ptr<thread_instance_t> prev_thread)
177+{
178+ auto next_thread = std::shared_ptr<thread_instance_t>{};
179+
180+ // Check if the passed in thread should be re-enqueued
181+ if (prev_thread != nullptr)
182+ {
183+ std::lock_guard<std::mutex> lock{ _vm_threads_lock };
184+ --_running_vm_thread_count;
185+ auto current_state = prev_thread->vm_thread->get_state();
186+
187+ // This system thread releases ownership of the vm thread
188+ prev_thread->system_thread_ownership.unlock();
189+ enqueue_thread_unsafe(std::move(prev_thread), current_state);
190+ }
191+
192+ for (;;)
193+ {
194+ // Check for a vm thread in the queue
195+ {
196+ std::unique_lock<std::mutex> lock{ _vm_threads_lock };
197+
198+ // Check if a GC should be performed.
199+ // Matching part of the logic in Inferno (emu/port/dis.c).
200+ if ((_gc_counter & 0xff) == 0)
201+ {
202+ const auto running_thread_count_local = _running_vm_thread_count;
203+ const auto is_gc_thread = running_thread_count_local == 0;
204+ perform_gc(is_gc_thread, lock);
205+ }
206+
207+ if (!_runnable_vm_threads.empty())
208+ {
209+ next_thread = _runnable_vm_threads.front();
210+ _runnable_vm_threads.pop();
211+ ++_running_vm_thread_count;
212+ ++_gc_counter;
213+
214+ // This system thread now takes ownership of the vm thread
215+ next_thread->system_thread_ownership.lock();
216+ return next_thread;
217+ }
218+
219+ if (!_all_vm_threads.empty() && _blocked_vm_thread_ids.size() == _all_vm_threads.size())
220+ {
221+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::warning, "scheduler: deadlock detected\n");
222+ assert(false && "VM thread deadlock detected");
223+ }
224+ }
225+
226+ // If the queue is empty, wait for a notification
227+ {
228+ std::unique_lock<std::mutex> worker_event_lock{ _worker_event_lock };
229+ if (_terminating)
230+ return{};
231+
232+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: worker: waiting\n");
233+ _worker_event.wait(worker_event_lock);
234+
235+ if (_terminating)
236+ return{};
237+ }
238+ }
239+}
240+
241+void default_scheduler_t::perform_gc(bool is_gc_thread, std::unique_lock<std::mutex> &all_vm_threads_lock)
242+{
243+ if (!is_gc_thread)
244+ {
245+ _gc_complete = false;
246+ all_vm_threads_lock.unlock();
247+ std::unique_lock<std::mutex> lock{ _gc_wait };
248+ _gc_done_event.wait(lock, [&]{ return _gc_complete.load(); });
249+
250+ // After the gc completes, take back the lock.
251+ all_vm_threads_lock.lock();
252+ }
253+ else
254+ {
255+ auto result = std::vector<std::shared_ptr<const vm_thread_t>>{};
256+ for (auto &p : _all_vm_threads)
257+ {
258+ auto &vm_thread = p.second->vm_thread;
259+ if (vm_thread->get_state() != vm_thread_state_t::broken)
260+ result.push_back(vm_thread);
261+ }
262+
263+ _vm.get_garbage_collector().collect(result);
264+ _gc_complete = true;
265+ _gc_done_event.notify_all();
266+ }
267+}
268+
269+void default_scheduler_t::enqueue_thread_unsafe(std::shared_ptr<thread_instance_t> thread_instance, runtime::vm_thread_state_t current_state)
270+{
271+ assert(thread_instance != nullptr);
272+
273+ const auto thread_id = thread_instance->vm_thread->get_thread_id();
274+ switch (current_state)
275+ {
276+ case vm_thread_state_t::ready:
277+ case vm_thread_state_t::debug:
278+ {
279+ _runnable_vm_threads.push(thread_instance);
280+
281+ if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>())
282+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: enqueue: %d\n", thread_id);
283+
284+ // Notify a worker a thread has been enqueued.
285+ _worker_event.notify_one();
286+ break;
287+ }
288+
289+ case vm_thread_state_t::blocked_in_alt:
290+ case vm_thread_state_t::blocked_sending:
291+ case vm_thread_state_t::blocked_receiving:
292+ {
293+ _blocked_vm_thread_ids.insert(thread_id);
294+
295+ if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>())
296+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: blocked: %d\n", thread_id);
297+
298+ break;
299+ }
300+
301+ case vm_thread_state_t::empty_stack:
302+ case vm_thread_state_t::exiting:
303+ {
304+ if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>())
305+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: exiting: %d\n", thread_id);
306+
307+ // Remove the thread
308+ auto thread_to_remove = _all_vm_threads.find(thread_id);
309+ assert(thread_to_remove != _all_vm_threads.cend());
310+ _all_vm_threads.erase(thread_to_remove);
311+
312+ break;
313+ }
314+
315+ case vm_thread_state_t::broken:
316+ {
317+ // [TODO] What happens when a thread enters this state but no debugger is attached?
318+ _non_runnable_vm_thread_ids.push_front(thread_id);
319+
320+ if (debug::is_component_tracing_enabled<debug::component_trace_t::scheduler>())
321+ debug::log_msg(debug::component_trace_t::scheduler, debug::log_level_t::debug, "scheduler: broken: %d\n", thread_id);
322+
323+ break;
324+ }
325+
326+ case vm_thread_state_t::release:
327+ case vm_thread_state_t::running:
328+ default:
329+ assert(false && "Unexpected thread state");
330+ throw vm_system_exception{ "Unexpected thread state" };
331+ }
332+}
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+
21+namespace disvm
22+{
23+ namespace runtime
24+ {
25+ // Default multi-threaded scheduler
26+ class default_scheduler_t final : public vm_scheduler_t, public vm_scheduler_control_t
27+ {
28+ private: // static
29+ static void worker_main(default_scheduler_t &instance);
30+
31+ public:
32+ default_scheduler_t(vm_t &vm, uint32_t system_thread_count, uint32_t thread_quanta);
33+
34+ ~default_scheduler_t();
35+
36+ public: // vm_scheduler_t
37+ bool is_idle() const override final;
38+
39+ vm_scheduler_control_t &get_controller() const override final;
40+
41+ const vm_thread_t& schedule_thread(std::unique_ptr<vm_thread_t> thread) override final;
42+
43+ public: // vm_scheduler_control_t
44+ void enqueue_blocked_thread(uint32_t thread_id) override final;
45+
46+ std::vector<std::shared_ptr<const vm_thread_t>> get_all_threads() const override final;
47+
48+ private:
49+ struct thread_instance_t
50+ {
51+ thread_instance_t(std::unique_ptr<vm_thread_t> t)
52+ : vm_thread{ std::move(t) }
53+ {
54+ }
55+
56+ ~thread_instance_t()
57+ {
58+ if (vm_thread != nullptr)
59+ vm_thread->release();
60+ }
61+
62+ std::shared_ptr<vm_thread_t> vm_thread;
63+ std::mutex system_thread_ownership;
64+ };
65+
66+ std::shared_ptr<thread_instance_t> next_thread(std::shared_ptr<thread_instance_t> prev_thread);
67+
68+ void perform_gc(bool is_gc_thread, std::unique_lock<std::mutex> &all_vm_threads_lock);
69+
70+ // Add the thread to the queue in a non-thread safe manner.
71+ void enqueue_thread_unsafe(std::shared_ptr<thread_instance_t> thread, runtime::vm_thread_state_t current_state);
72+
73+ private:
74+ vm_t &_vm;
75+
76+ const uint32_t _worker_thread_count;
77+ std::vector<std::thread> _worker_pool;
78+
79+ std::mutex _worker_event_lock;
80+ std::condition_variable _worker_event;
81+ std::atomic_bool _terminating;
82+
83+ const uint32_t _vm_thread_quanta;
84+ mutable std::mutex _vm_threads_lock;
85+ std::queue<std::shared_ptr<thread_instance_t>> _runnable_vm_threads;
86+ std::size_t _running_vm_thread_count;
87+
88+ std::mutex _gc_wait;
89+ std::condition_variable _gc_done_event;
90+ std::atomic_bool _gc_complete; // Used to avoid spurious wakeups
91+ std::size_t _gc_counter;
92+
93+ std::unordered_set<uint32_t> _blocked_vm_thread_ids;
94+ std::forward_list<uint32_t> _non_runnable_vm_thread_ids;
95+
96+ using all_thread_map_t = std::unordered_map<uint32_t, std::shared_ptr<thread_instance_t>>;
97+ all_thread_map_t _all_vm_threads;
98+ };
99+ }
100+}
101+
102+#endif // _DISVM_SRC_VM_SCHEDULER_H_
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+
14+using namespace disvm;
15+using namespace disvm::runtime;
16+
17+namespace
18+{
19+ struct vm_stack_page
20+ {
21+ vm_stack_page *prev_page;
22+ uintptr_t page_limit_addr;
23+ vm_frame_t *page_top_frame; // Do not free. This is used as a tracking pointer.
24+
25+ void *stack_data() const
26+ {
27+ // The beginning of the stack data is at the end of the struct.
28+ return reinterpret_cast<uint8_t *>(const_cast<vm_stack_page *>(this)) + sizeof(vm_stack_page);
29+ }
30+
31+ bool contains_frame(const vm_frame_t *frame) const
32+ {
33+ return reinterpret_cast<uintptr_t>(stack_data()) <= reinterpret_cast<uintptr_t>(frame) && reinterpret_cast<uintptr_t>(frame) < page_limit_addr;
34+ }
35+ };
36+
37+ struct vm_stack_layout final : public vm_alloc_t
38+ {
39+ static std::shared_ptr<const type_descriptor_t> type_desc()
40+ {
41+ static auto td = type_descriptor_t::create(0);
42+ return td;
43+ }
44+
45+ vm_stack_layout(std::size_t stack_page_size)
46+ : vm_alloc_t(type_desc())
47+ , stack_page_size{ stack_page_size }
48+ , top_frame{ nullptr }
49+ , top_page{ nullptr }
50+ {
51+ new_page();
52+ }
53+
54+ ~vm_stack_layout()
55+ {
56+ assert(top_page != nullptr);
57+ free_memory(top_page);
58+ }
59+
60+ const std::size_t stack_page_size;
61+
62+ vm_stack_page *top_page;
63+ vm_frame_t *top_frame; // Do not free. This is used as a tracking pointer.
64+
65+ // Create a new 'top page' and return stack data address
66+ void *new_page()
67+ {
68+ auto current_stack_page = top_page;
69+ top_page = alloc_memory<vm_stack_page>(sizeof(vm_stack_page) + stack_page_size);
70+
71+ // Initialize the stack allocation.
72+ top_page->prev_page = current_stack_page;
73+
74+ // The top frame member is only for tracking and so is initialized to null.
75+ top_page->page_top_frame = nullptr;
76+ top_page->page_limit_addr = reinterpret_cast<uintptr_t>(top_page)+stack_page_size;
77+
78+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "alloc: vm stack alloc: %#" PRIxPTR " %#" PRIxPTR "\n", top_page, top_page->page_limit_addr);
79+
80+ return top_page->stack_data();
81+ }
82+
83+ // Drop the current page and move to the previous one
84+ void drop_page()
85+ {
86+ auto current_stack_page = top_page->prev_page;
87+ assert(current_stack_page != nullptr);
88+
89+ free_memory(top_page);
90+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "free: vm stack alloc\n");
91+
92+ // Set the top page
93+ top_page = current_stack_page;
94+ }
95+ };
96+}
97+
98+vm_frame_t::vm_frame_t(std::shared_ptr<const type_descriptor_t> &td)
99+ : frame_type{ std::move(td) }
100+{
101+ assert(frame_type != nullptr);
102+
103+ // Initialize the new frame
104+ init_memory(*frame_type, base());
105+
106+ prev_pc() = runtime_constants::invalid_program_counter;
107+ prev_frame() = nullptr;
108+ prev_module_ref() = nullptr;
109+
110+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
111+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm frame: %#" PRIxPTR " %d\n", this, frame_type->size_in_bytes);
112+}
113+
114+vm_frame_t::~vm_frame_t()
115+{
116+ dec_ref_count_and_free(prev_module_ref());
117+
118+ destroy_memory(*frame_type, base());
119+
120+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
121+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm frame: %#" PRIxPTR "\n", this);
122+}
123+
124+void vm_frame_t::copy_frame_contents(const vm_frame_t &other)
125+{
126+ auto this_frame_base = reinterpret_cast<uint8_t *>(base());
127+ auto this_frame_contents = this_frame_base + sizeof(vm_frame_base_alloc_t);
128+
129+ auto other_frame_base = reinterpret_cast<uint8_t *>(other.base());
130+ auto other_frame_contents = other_frame_base + sizeof(vm_frame_base_alloc_t);
131+
132+ assert(frame_type->size_in_bytes > sizeof(vm_frame_base_alloc_t));
133+ std::memcpy(this_frame_contents, other_frame_contents, frame_type->size_in_bytes - sizeof(vm_frame_base_alloc_t));
134+
135+ inc_ref_count_in_memory(*frame_type, this_frame_base);
136+}
137+
138+pointer_t vm_frame_t::base() const
139+{
140+ // The beginning of the frame data is at the end of the frame struct.
141+ return reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(const_cast<vm_frame_t *>(this)) + sizeof(vm_frame_t));
142+}
143+
144+vm_pc_t &vm_frame_t::prev_pc() const
145+{
146+ return base<vm_frame_base_alloc_t>().p_pc;
147+}
148+
149+vm_frame_t *&vm_frame_t::prev_frame() const
150+{
151+ return base<vm_frame_base_alloc_t>().p_fp;
152+}
153+
154+vm_module_ref_t *&vm_frame_t::prev_module_ref() const
155+{
156+ return base<vm_frame_base_alloc_t>().p_mr;
157+}
158+
159+pointer_t vm_frame_t::fixed_point_register_1() const
160+{
161+ const auto offset = runtime_constants::fixed_point_register_1_frame_offset;
162+ assert(offset < frame_type->size_in_bytes);
163+
164+ auto p = reinterpret_cast<uint8_t *>(base()) + offset;
165+ return reinterpret_cast<pointer_t>(p);
166+}
167+
168+pointer_t vm_frame_t::fixed_point_register_2() const
169+{
170+ const auto offset = runtime_constants::fixed_point_register_2_frame_offset;
171+ assert(offset < frame_type->size_in_bytes);
172+
173+ auto p = reinterpret_cast<uint8_t *>(base()) + offset;
174+ return reinterpret_cast<pointer_t>(p);
175+}
176+
177+vm_stack_t::vm_stack_t(std::size_t stack_extent)
178+ : _mem{ std::make_unique<vm_stack_layout>(stack_extent) }
179+{
180+ assert(stack_extent != 0);
181+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm stack\n");
182+}
183+
184+vm_stack_t::~vm_stack_t()
185+{
186+ while (nullptr != pop_frame())
187+ {
188+ // Pop all frames off the stack
189+ }
190+
191+ // Free stack memory
192+ _mem->release();
193+ _mem.reset();
194+
195+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm stack\n");
196+}
197+
198+vm_frame_t *vm_stack_t::alloc_frame(std::shared_ptr<const type_descriptor_t> frame_type)
199+{
200+ assert(frame_type != nullptr);
201+ assert(sizeof(vm_frame_base_alloc_t) < frame_type->size_in_bytes && "Requested frame size less than VM frame base size");
202+
203+ auto layout = dynamic_cast<vm_stack_layout *>(_mem.get());
204+
205+ auto page_top_frame = layout->top_page->page_top_frame;
206+ const auto new_frame_size = sizeof(vm_frame_t) + frame_type->size_in_bytes;
207+ if (layout->stack_page_size < new_frame_size)
208+ throw vm_system_exception{ "Requested stack frame larger than stack page" };
209+
210+ vm_frame_t *new_frame;
211+ if (page_top_frame == nullptr)
212+ {
213+ new_frame = ::new(layout->top_page->stack_data())vm_frame_t{ frame_type };
214+ }
215+ else
216+ {
217+ auto current_page_top_frame_addr = reinterpret_cast<std::size_t>(page_top_frame);
218+ auto new_frame_addr_maybe = current_page_top_frame_addr + (sizeof(vm_frame_t) + page_top_frame->frame_type->size_in_bytes);
219+
220+ // If the new frame is not going to fit on the current page, create a new page.
221+ if ((new_frame_addr_maybe + new_frame_size) >= layout->top_page->page_limit_addr)
222+ new_frame_addr_maybe = reinterpret_cast<uintptr_t>(layout->new_page());
223+
224+ new_frame = ::new(reinterpret_cast<void *>(new_frame_addr_maybe))vm_frame_t{ frame_type };
225+ }
226+
227+ // Record the top frame on the page
228+ layout->top_page->page_top_frame = new_frame;
229+
230+ return new_frame;
231+}
232+
233+vm_frame_t *vm_stack_t::push_frame()
234+{
235+ auto layout = dynamic_cast<vm_stack_layout *>(_mem.get());
236+
237+ auto current_top_frame = layout->top_frame;
238+ auto new_top_frame = layout->top_page->page_top_frame;
239+ new_top_frame->prev_frame() = current_top_frame;
240+
241+ layout->top_frame = new_top_frame;
242+
243+ if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>())
244+ debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "update: push vm frame: %#" PRIxPTR "\n", new_top_frame);
245+
246+ return new_top_frame;
247+}
248+
249+vm_frame_t *vm_stack_t::pop_frame()
250+{
251+ auto layout = dynamic_cast<vm_stack_layout *>(_mem.get());
252+
253+ auto current_frame = layout->top_frame;
254+ if (current_frame == nullptr)
255+ return nullptr;
256+
257+ {
258+ // Dispose of the current top frame and update the current frame.
259+ auto previous_frame = current_frame;
260+ current_frame = previous_frame->prev_frame();
261+
262+ // Call destructor on previous frame since we allocated
263+ // the frame on the stack page.
264+ previous_frame->~vm_frame_t();
265+ }
266+
267+ layout->top_frame = current_frame;
268+ layout->top_page->page_top_frame = current_frame;
269+
270+ // Check if the stack page is no longer needed.
271+ if (current_frame != nullptr)
272+ {
273+ // Clean up the stack page allocation if the current frame is on a previous page
274+ if (!layout->top_page->contains_frame(current_frame))
275+ {
276+ layout->drop_page();
277+
278+ // Ensure the current frame is in the new page.
279+ assert(layout->top_page->contains_frame(current_frame));
280+ }
281+ }
282+
283+ if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>())
284+ debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "update: pop vm frame: %#" PRIxPTR "\n", current_frame);
285+
286+ return current_frame;
287+}
288+
289+vm_frame_t *vm_stack_t::peek_frame() const
290+{
291+ auto layout = dynamic_cast<vm_stack_layout *>(_mem.get());
292+ return layout->top_frame;
293+}
294+
295+void vm_stack_t::walk_stack(vm_stack_walk_callback_t callback) const
296+{
297+ if (callback == nullptr)
298+ return;
299+
300+ auto layout = dynamic_cast<vm_stack_layout *>(_mem.get());
301+ auto current_page = layout->top_page;
302+ auto current_frame = layout->top_frame;
303+
304+ while (current_page != nullptr && current_frame != nullptr)
305+ {
306+ if (!callback(current_frame, current_page))
307+ break;
308+
309+ current_frame = current_frame->prev_frame();
310+
311+ if (!current_page->contains_frame(current_frame))
312+ current_page = current_page->prev_page;
313+ }
314+}
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+
14+using namespace disvm;
15+using namespace disvm::runtime;
16+
17+std::shared_ptr<const type_descriptor_t> vm_string_t::type_desc()
18+{
19+ return intrinsic_type_desc::type<vm_string_t>();
20+}
21+
22+namespace
23+{
24+ // Enumeration for character type compare cases.
25+ enum class compare_case
26+ {
27+ ascii_ascii,
28+ ascii_rune,
29+ rune_ascii,
30+ rune_rune
31+ };
32+
33+ // Scenario matrix for compare cases
34+ const compare_case scenario_matrix[][2] =
35+ {
36+ { compare_case::ascii_ascii, compare_case::ascii_rune },
37+ { compare_case::rune_ascii, compare_case::rune_rune },
38+ };
39+
40+ // Case indices
41+ const auto ascii_idx = std::size_t{ 0 };
42+ const auto rune_idx = std::size_t{ 1 };
43+
44+ // Compare function for character types.
45+ template <typename CharacterType1, typename CharacterType2>
46+ int _compare(word_t len, CharacterType1 *s1, CharacterType2 *s2)
47+ {
48+ for (auto i = word_t{ 0 }; i < len; ++i)
49+ {
50+ const auto sc1 = static_cast<rune_t>(s1[i]);
51+ const auto sc2 = static_cast<rune_t>(s2[i]);
52+
53+ if (sc1 != sc2)
54+ return (sc1 < sc2) ? (-1) : 1;
55+ }
56+
57+ return 0;
58+ }
59+}
60+
61+int vm_string_t::compare(const vm_string_t *s1, const vm_string_t *s2)
62+{
63+ auto len_1 = word_t{ 0 };
64+ auto scen_1 = ascii_idx;
65+ void *str_mem_1 = "";
66+ if (s1 != nullptr)
67+ {
68+ len_1 = s1->_length;
69+ scen_1 = s1->_character_size == sizeof(char) ? ascii_idx : rune_idx;
70+ str_mem_1 = (s1->_is_alloc) ? s1->_mem.alloc : s1->_mem.local;
71+ }
72+
73+ auto len_2 = word_t{ 0 };
74+ auto scen_2 = ascii_idx;
75+ void *str_mem_2 = "";
76+ if (s2 != nullptr)
77+ {
78+ len_2 = s2->_length;
79+ scen_2 = s2->_character_size == sizeof(char) ? ascii_idx : rune_idx;
80+ str_mem_2 = (s2->_is_alloc) ? s2->_mem.alloc : s2->_mem.local;
81+ }
82+
83+ // Check the simple compare case between the same string.
84+ if (str_mem_1 == str_mem_2)
85+ return 0;
86+
87+ const auto scenario = scenario_matrix[scen_1][scen_2];
88+
89+ auto result = int{ 0 };
90+ const auto min_length = std::min(len_1, len_2);
91+ switch (scenario)
92+ {
93+ default:
94+ assert(false && "Unknown compare scenario");
95+ case compare_case::ascii_ascii:
96+ case compare_case::rune_rune:
97+ result = std::memcmp(str_mem_1, str_mem_2, min_length);
98+ break;
99+ case compare_case::ascii_rune:
100+ result = _compare(min_length, reinterpret_cast<char *>(str_mem_1), reinterpret_cast<rune_t *>(str_mem_2));
101+ break;
102+ case compare_case::rune_ascii:
103+ result = _compare(min_length, reinterpret_cast<rune_t *>(str_mem_1), reinterpret_cast<char *>(str_mem_2));
104+ break;
105+ }
106+
107+ if (result != 0)
108+ return result;
109+
110+ // Comparing lengths if one string is a proper sub-string of the other.
111+ return (len_1 - len_2);
112+}
113+
114+namespace
115+{
116+ template<typename DestCharacterType, typename SourceCharacterType>
117+ void copy_characters_to(DestCharacterType *dest, word_t dest_start, const SourceCharacterType *source, word_t source_length)
118+ {
119+ for (auto i = word_t{ 0 }; i < source_length; ++i)
120+ dest[dest_start + i] = static_cast<DestCharacterType>(source[i]);
121+ }
122+
123+ template<>
124+ void copy_characters_to(char *dest, word_t dest_start, const char *source, word_t source_length)
125+ {
126+ assert(dest != nullptr && source != nullptr);
127+ std::memcpy((dest + dest_start), source, source_length);
128+ }
129+
130+ template<>
131+ void copy_characters_to(rune_t *dest, word_t dest_start, const rune_t *source, word_t source_length)
132+ {
133+ assert(dest != nullptr && source != nullptr);
134+ std::memcpy((dest + dest_start), source, (source_length * sizeof(rune_t)));
135+ }
136+
137+ void combine(
138+ bool dest_is_rune,
139+ uint8_t *dest,
140+ bool source1_is_rune,
141+ word_t source1_length,
142+ const uint8_t *source1,
143+ bool source2_is_rune,
144+ word_t source2_length,
145+ const uint8_t *source2)
146+ {
147+ if (dest_is_rune)
148+ {
149+ auto dest_rune = reinterpret_cast<rune_t *>(dest);
150+ if (source1_is_rune)
151+ copy_characters_to(dest_rune, 0, reinterpret_cast<const rune_t *>(source1), source1_length);
152+ else
153+ copy_characters_to(dest_rune, 0, source1, source1_length);
154+
155+ if (source2_is_rune)
156+ copy_characters_to(dest_rune, source1_length, reinterpret_cast<const rune_t *>(source2), source2_length);
157+ else
158+ copy_characters_to(dest_rune, source1_length, source2, source2_length);
159+ }
160+ else
161+ {
162+ copy_characters_to(dest, 0, source1, source1_length);
163+ copy_characters_to(dest, source1_length, source2, source2_length);
164+ }
165+ }
166+
167+ word_t compute_max_length(word_t current_length)
168+ {
169+ // Length + 1 (null) + space for string expansion
170+ return (current_length + 1 + (current_length / 4));
171+ }
172+}
173+
174+vm_string_t::vm_string_t()
175+ : vm_alloc_t(vm_string_t::type_desc())
176+ , _character_size{ sizeof(char) }
177+ , _encoded_str{ nullptr }
178+ , _is_alloc{ false }
179+ , _length{ 0 }
180+ , _length_max{ sizeof(_mem.local) }
181+ , _mem{}
182+{ }
183+
184+vm_string_t::vm_string_t(std::size_t encoded_str_len, const uint8_t *encoded_str)
185+ : vm_alloc_t(vm_string_t::type_desc())
186+ , _character_size{ sizeof(char) }
187+ , _encoded_str{ nullptr }
188+ , _is_alloc{ false }
189+ , _length{ 0 }
190+ , _length_max{ sizeof(_mem.local) }
191+ , _mem{}
192+{
193+ assert((encoded_str_len > 0 && encoded_str != nullptr) || (encoded_str_len == 0 && encoded_str == nullptr));
194+ auto character_count = utf8::count_codepoints(encoded_str, encoded_str_len);
195+ _character_size = (encoded_str_len != character_count) ? sizeof(rune_t) : sizeof(char);
196+
197+ _length = static_cast<word_t>(character_count);
198+ _length_max = sizeof(_mem.local) / _character_size;
199+
200+ // Allocate memory based on character count and size
201+ auto dest = _mem.local;
202+ if ((encoded_str_len + 1) > sizeof(_mem.local))
203+ {
204+ // If we are going to allocate, make sure it is larger than needed.
205+ _length_max = compute_max_length(_length);
206+ _mem.alloc = calloc_memory<uint8_t>(_length_max * _character_size);
207+ _is_alloc = true;
208+
209+ dest = _mem.alloc;
210+ }
211+
212+ // Copy over the entire buffer if ASCII or convert and copy if multi-byte
213+ if (_character_size == sizeof(char))
214+ {
215+ std::memcpy(dest, encoded_str, encoded_str_len);
216+ }
217+ else
218+ {
219+ auto rune_dest = reinterpret_cast<rune_t *>(dest);
220+ for (auto i = word_t{ 0 }; i < _length; ++i)
221+ encoded_str += utf8::decode(encoded_str, rune_dest[i]);
222+ }
223+
224+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
225+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm string: %d %d >>%s<<\n", _length, (_length_max * _character_size), dest);
226+}
227+
228+vm_string_t::vm_string_t(const vm_string_t &s1, const vm_string_t &s2)
229+ : vm_alloc_t(vm_string_t::type_desc())
230+ , _character_size{ std::max(s1._character_size, s2._character_size) }
231+ , _encoded_str{ nullptr }
232+ , _is_alloc{ true }
233+ , _length{ s1._length + s2._length }
234+ , _length_max{ s1._length + s2._length }
235+ , _mem{}
236+{
237+ _mem.alloc = calloc_memory<uint8_t>(_length_max * _character_size);
238+
239+ const auto s1_data = (s1._is_alloc) ? s1._mem.alloc : s1._mem.local;
240+ const auto s2_data = (s2._is_alloc) ? s2._mem.alloc : s2._mem.local;
241+
242+ // Combine the two strings
243+ combine(
244+ _character_size == sizeof(rune_t),
245+ _mem.alloc,
246+ s1._character_size == sizeof(rune_t),
247+ s1._length,
248+ s1_data,
249+ s2._character_size == sizeof(rune_t),
250+ s2._length,
251+ s2_data);
252+}
253+
254+vm_string_t::vm_string_t(const vm_string_t &other, word_t begin_index, word_t end_index)
255+ : vm_alloc_t(other.alloc_type)
256+ , _character_size{ other._character_size }
257+ , _encoded_str{ nullptr }
258+ , _is_alloc{ other._is_alloc }
259+ , _length{ end_index - begin_index }
260+ , _length_max{ compute_max_length(end_index - begin_index) }
261+ , _mem{}
262+{
263+ if (end_index <= begin_index)
264+ throw out_of_range_memory{};
265+
266+ if (begin_index < 0)
267+ throw out_of_range_memory{ 0, other._length, begin_index };
268+
269+ if (other._length < end_index)
270+ throw out_of_range_memory{ 0, other._length, end_index };
271+
272+ // Allocate memory based on character count and size
273+ auto src = other._mem.local;
274+ auto dest = _mem.local;
275+ const auto length_in_bytes = _length * _character_size;
276+ if (_is_alloc)
277+ {
278+ src = other._mem.alloc;
279+ _mem.alloc = calloc_memory<uint8_t>(_length_max);
280+ dest = _mem.alloc;
281+ }
282+
283+ // Copy over string content starting at the correct index (char vs. rune_t)
284+ ::memcpy(dest, (src + (begin_index * _character_size)), length_in_bytes);
285+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "copy: vm string: %#" PRIxPTR " %d %d %#" PRIxPTR "\n", &other, begin_index, end_index, this);
286+}
287+
288+vm_string_t::~vm_string_t()
289+{
290+ // Free memory and string buffer
291+ if (_is_alloc)
292+ {
293+ if (static_cast<void *>(_mem.alloc) != static_cast<void *>(_encoded_str))
294+ free_memory(_encoded_str);
295+
296+ free_memory(_mem.alloc);
297+ }
298+ else
299+ {
300+ if (static_cast<void *>(_mem.local) != static_cast<void *>(_encoded_str))
301+ free_memory(_encoded_str);
302+ }
303+
304+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm string\n");
305+
306+ debug::assign_debug_pointer(&_mem.alloc);
307+ debug::assign_debug_pointer(&_encoded_str);
308+}
309+
310+vm_string_t &vm_string_t::append(const vm_string_t &other)
311+{
312+ // This function does not adhere to the immutable string contract.
313+ // Therefore, it is unsafe to call this function if there is more
314+ // than 1 reference to this string instance.
315+ assert(get_ref_count() <= 1);
316+
317+ // Check if the new string has any length at all.
318+ const auto other_length = other._length;
319+ if (other_length == 0)
320+ return *this;
321+
322+ const auto other_is_rune = other._character_size == sizeof(rune_t);
323+ const auto other_source = (other._is_alloc) ? other._mem.alloc : other._mem.local;
324+
325+ const auto local_is_rune = _character_size == sizeof(rune_t);
326+ const auto local_length = _length;
327+ const auto local_source = (_is_alloc) ? _mem.alloc : _mem.local;
328+
329+ // Release the encoded string for this instance.
330+ {
331+ std::lock_guard<std::mutex> lock{ _encoded_str_lock };
332+ if (_encoded_str != reinterpret_cast<char*>(local_source))
333+ {
334+ free_memory(_encoded_str);
335+ _encoded_str = nullptr;
336+ }
337+ }
338+
339+ // Define the values for the appended string
340+ const auto new_length = local_length + other_length;
341+ const auto new_length_max = (new_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(new_length);
342+ const auto new_is_rune = local_is_rune || other_is_rune;
343+ uint8_t *new_source = nullptr;
344+
345+ // Check if this string and the supplied string have the same character size.
346+ if (new_is_rune)
347+ new_source = calloc_memory<uint8_t>(new_length_max * sizeof(rune_t));
348+ else
349+ new_source = calloc_memory<uint8_t>(new_length_max);
350+
351+ // Combine the two string sources
352+ combine(
353+ new_is_rune,
354+ new_source,
355+ local_is_rune,
356+ local_length,
357+ local_source,
358+ other_is_rune,
359+ other_length,
360+ other_source);
361+
362+ _length = new_length;
363+ _length_max = new_length_max;
364+
365+ // Free the local allocation
366+ if (_is_alloc)
367+ {
368+ free_memory(local_source);
369+ _mem.alloc = nullptr;
370+ }
371+
372+ _mem.alloc = new_source;
373+ _is_alloc = true;
374+ _character_size = new_is_rune ? sizeof(rune_t) : sizeof(char);
375+
376+ return *this;
377+}
378+
379+word_t vm_string_t::get_length() const
380+{
381+ return _length;
382+}
383+
384+rune_t vm_string_t::get_rune(word_t index) const
385+{
386+ if (index < 0 && _length <= index)
387+ throw out_of_range_memory{ 0, _length - 1, index };
388+
389+ // Determine if local or allocated memory
390+ auto source = reinterpret_cast<const char *>(_mem.local);
391+ if (_is_alloc)
392+ source = reinterpret_cast<const char *>(_mem.alloc);
393+
394+ // Check if multi-byte or ASCII
395+ if (_character_size != sizeof(char))
396+ return reinterpret_cast<const rune_t *>(source)[index];
397+ else
398+ return static_cast<const rune_t>(source[index]);
399+}
400+
401+void vm_string_t::set_rune(word_t index, rune_t value)
402+{
403+ // This function does not adhere to the immutable string contract.
404+ // Therefore, it is unsafe to call this function if there is more
405+ // than 1 reference to this string instance.
406+ assert(get_ref_count() <= 1);
407+
408+ if (value > utf8::constants::max_supported_codepoint)
409+ throw invalid_utf8{};
410+
411+ // Index equal to length is allowed to grow the string
412+ if (index < 0 && _length <= index)
413+ throw out_of_range_memory{ 0, _length, index };
414+
415+ // Determine if local or allocated memory
416+ auto source = static_cast<uint8_t *>(_mem.local);
417+ if (_is_alloc)
418+ {
419+ source = static_cast<uint8_t *>(_mem.alloc);
420+
421+ // Since we are setting a rune, the string value should be freed.
422+ if (_encoded_str != nullptr)
423+ free_memory(_encoded_str);
424+ }
425+
426+ // Reset the string so it can be regenerated.
427+ _encoded_str = nullptr;
428+
429+ const auto new_character_size = (value > utf8::constants::max_codepoint_ascii) ? sizeof(rune_t) : sizeof(char);
430+ assert(new_character_size < std::numeric_limits<uint8_t>::max());
431+
432+ if (new_character_size > _character_size) // Expand the string if the new character is non-ASCII.
433+ {
434+ _character_size = static_cast<uint8_t>(new_character_size);
435+ assert(_character_size == sizeof(rune_t));
436+
437+ _length_max = (_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(_length);
438+ auto new_alloc = calloc_memory<rune_t>(_length_max * _character_size);
439+
440+ // Copy source into the new alloc.
441+ copy_characters_to(new_alloc, 0, source, _length);
442+
443+ // Free memory, if allocated
444+ if (_is_alloc)
445+ free_memory(source);
446+
447+ debug::assign_debug_memory(&_mem, sizeof(_mem));
448+
449+ _is_alloc = true;
450+ _mem.alloc = reinterpret_cast<uint8_t *>(new_alloc);
451+ source = _mem.alloc;
452+ }
453+ else if (static_cast<word_t>(_length_max) <= index) // Check if string can accomodate the new character.
454+ {
455+ _length_max = (_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(_length);
456+ auto new_alloc = calloc_memory<uint8_t>(_length_max * _character_size);
457+ ::memcpy(new_alloc, source, _length * _character_size);
458+
459+ // Free memory, if allocated
460+ if (_is_alloc)
461+ free_memory(source);
462+
463+ debug::assign_debug_memory(&_mem, sizeof(_mem));
464+
465+ _is_alloc = true;
466+ _mem.alloc = new_alloc;
467+ source = _mem.alloc;
468+ }
469+
470+ // Check if multi-byte or ASCII.
471+ if (_character_size != sizeof(char))
472+ reinterpret_cast<rune_t *>(source)[index] = value;
473+ else
474+ source[index] = static_cast<uint8_t>(value & utf8::constants::max_codepoint_ascii);
475+
476+ // Increment the length, if the index matches the length.
477+ if (_length == index)
478+ _length++;
479+}
480+
481+int vm_string_t::compare_to(const vm_string_t *s) const
482+{
483+ return vm_string_t::compare(this, s);
484+}
485+
486+const char *vm_string_t::str() const
487+{
488+ if (_encoded_str != nullptr)
489+ return _encoded_str;
490+
491+ {
492+ // [PERF] Use a spin-lock instead of a mutex here.
493+ std::lock_guard<std::mutex> lock{ _encoded_str_lock };
494+
495+ if (_encoded_str != nullptr)
496+ return _encoded_str;
497+
498+ // If the string is ASCII then just return the memory
499+ if (_character_size == sizeof(char))
500+ {
501+ _encoded_str = (_is_alloc) ? reinterpret_cast<char *>(_mem.alloc) : reinterpret_cast<char *>(_mem.local);
502+ return _encoded_str;
503+ }
504+
505+ const auto rune_str = (_is_alloc) ? reinterpret_cast<rune_t *>(_mem.alloc) : reinterpret_cast<rune_t *>(_mem.local);
506+ const auto str_len = (_length * _character_size) + 1; // Plus 1 for a null terminator
507+ auto encoded_str_local = alloc_memory<char>(str_len);
508+
509+ auto s = reinterpret_cast<uint8_t *>(encoded_str_local);
510+ for (auto i = word_t{ 0 }; i < _length; ++i)
511+ {
512+ auto curr_rune = rune_str[i];
513+ s = utf8::encode(curr_rune, s);
514+ }
515+
516+ // Ensure the final value is null.
517+ *s = 0;
518+
519+ _encoded_str = encoded_str_local;
520+ return _encoded_str;
521+ }
522+}
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+
15+using namespace disvm;
16+using namespace disvm::runtime;
17+
18+namespace
19+{
20+ disvm::runtime::builtin::vm_runtab_t Sysmodtab[] = {
21+ "announce",0xb7c4ac0,Sys_announce,40,2,{ 0x0,0x80, },
22+ "aprint",0x77442d46,Sys_aprint,0,0,{ 0 },
23+ "bind",0x66326d91,Sys_bind,48,2,{ 0x0,0xc0, },
24+ "byte2char",0x3d6094f9,Sys_byte2char,40,2,{ 0x0,0x80, },
25+ "char2byte",0x2ba5ab41,Sys_char2byte,48,2,{ 0x0,0x40, },
26+ "chdir",0xc6935858,Sys_chdir,40,2,{ 0x0,0x80, },
27+ "create",0x54db77d9,Sys_create,48,2,{ 0x0,0x80, },
28+ "dial",0x29e90174,Sys_dial,40,2,{ 0x0,0xc0, },
29+ "dirread",0x72210d71,Sys_dirread,40,2,{ 0x0,0x80, },
30+ "dup",0x6584767b,Sys_dup,40,0,{ 0 },
31+ "export",0x6fc6dc03,Sys_export,48,2,{ 0x0,0xc0, },
32+ "fauth",0x20ccc34b,Sys_fauth,40,2,{ 0x0,0xc0, },
33+ "fd2path",0x749c6042,Sys_fd2path,40,2,{ 0x0,0x80, },
34+ "fildes",0x1478f993,Sys_fildes,40,0,{ 0 },
35+ "file2chan",0x9f34d686,Sys_file2chan,40,2,{ 0x0,0xc0, },
36+ "fprint",0xf46486c8,Sys_fprint,0,0,{ 0 },
37+ "fstat",0xda4499c2,Sys_fstat,40,2,{ 0x0,0x80, },
38+ "fversion",0xfe9c0a06,Sys_fversion,48,2,{ 0x0,0xa0, },
39+ "fwstat",0x50a6c7e0,Sys_fwstat,104,2,{ 0x0,0xbc, },
40+ "iounit",0x5583b730,Sys_iounit,40,2,{ 0x0,0x80, },
41+ "listen",0xb97416e0,Sys_listen,48,2,{ 0x0,0xe0, },
42+ "millisec",0x616977e8,Sys_millisec,32,0,{ 0 },
43+ "mount",0x74c17b3a,Sys_mount,56,2,{ 0x0,0xe8, },
44+ "open",0x8f477f99,Sys_open,40,2,{ 0x0,0x80, },
45+ "pctl",0x5df27fb,Sys_pctl,40,2,{ 0x0,0x40, },
46+ "pipe",0x1f2c52ea,Sys_pipe,40,2,{ 0x0,0x80, },
47+ "pread",0x9d8aac6,Sys_pread,56,2,{ 0x0,0xc0, },
48+ "print",0xac849033,Sys_print,0,0,{ 0 },
49+ "pwrite",0x9d8aac6,Sys_pwrite,56,2,{ 0x0,0xc0, },
50+ "read",0x7cfef557,Sys_read,48,2,{ 0x0,0xc0, },
51+ "readn",0x7cfef557,Sys_readn,48,2,{ 0x0,0xc0, },
52+ "remove",0xc6935858,Sys_remove,40,2,{ 0x0,0x80, },
53+ "seek",0xaeccaddb,Sys_seek,56,2,{ 0x0,0x80, },
54+ "sleep",0xe67bf126,Sys_sleep,40,0,{ 0 },
55+ "sprint",0x4c0624b6,Sys_sprint,0,0,{ 0 },
56+ "stat",0x319328dd,Sys_stat,40,2,{ 0x0,0x80, },
57+ "stream",0xb9e8f9ea,Sys_stream,48,2,{ 0x0,0xc0, },
58+ "tokenize",0x57338f20,Sys_tokenize,40,2,{ 0x0,0xc0, },
59+ "unmount",0x21e337e3,Sys_unmount,40,2,{ 0x0,0xc0, },
60+ "utfbytes",0x1d4a1f4,Sys_utfbytes,40,2,{ 0x0,0x80, },
61+ "werrstr",0xc6935858,Sys_werrstr,40,2,{ 0x0,0x80, },
62+ "write",0x7cfef557,Sys_write,48,2,{ 0x0,0xc0, },
63+ "wstat",0x56b02096,Sys_wstat,104,2,{ 0x0,0xbc, },
64+ 0
65+ };
66+
67+ const word_t Sysmodlen = 43;
68+}
69+
70+std::shared_ptr<const type_descriptor_t> T_Qid;
71+std::shared_ptr<const type_descriptor_t> T_Dir;
72+std::shared_ptr<const type_descriptor_t> T_FD;
73+std::shared_ptr<const type_descriptor_t> T_Connection;
74+std::shared_ptr<const type_descriptor_t> T_FileIO;
75+
76+void
77+Sysmodinit(void)
78+{
79+ builtin::register_module_exports(Sys_PATH, Sysmodlen, Sysmodtab);
80+ T_Qid = type_descriptor_t::create(sizeof(Sys_Qid), sizeof(Sys_Qid_map), Sys_Qid_map);
81+ T_Dir = type_descriptor_t::create(sizeof(Sys_Dir), sizeof(Sys_Dir_map), Sys_Dir_map);
82+ T_FD = type_descriptor_t::create(sizeof(Sys_FD), sizeof(Sys_FD_map), Sys_FD_map);
83+ T_Connection = type_descriptor_t::create(sizeof(Sys_Connection), sizeof(Sys_Connection_map), Sys_Connection_map);
84+ T_FileIO = type_descriptor_t::create(sizeof(Sys_FileIO), sizeof(Sys_FileIO_map), Sys_FileIO_map);
85+}
86+
87+void
88+Sys_announce(vm_registers_t &r, vm_t &vm)
89+{
90+ auto &fp = r.stack.peek_frame()->base<F_Sys_announce>();
91+ throw vm_system_exception{ "Instruction not implemented" };
92+}
93+
94+void
95+Sys_aprint(vm_registers_t &r, vm_t &vm)
96+{
97+ auto &fp = r.stack.peek_frame()->base<F_Sys_aprint>();
98+ throw vm_system_exception{ "Instruction not implemented" };
99+}
100+
101+void
102+Sys_bind(vm_registers_t &r, vm_t &vm)
103+{
104+ auto &fp = r.stack.peek_frame()->base<F_Sys_bind>();
105+ throw vm_system_exception{ "Instruction not implemented" };
106+}
107+
108+void
109+Sys_byte2char(vm_registers_t &r, vm_t &vm)
110+{
111+ auto &fp = r.stack.peek_frame()->base<F_Sys_byte2char>();
112+
113+ auto buffer = vm_alloc_t::from_allocation<vm_array_t>(fp.buf);
114+ assert(buffer->get_element_type()->size_in_bytes == intrinsic_type_desc::type<byte_t>()->size_in_bytes);
115+
116+ const auto n = fp.n;
117+ if (buffer == nullptr || n >= buffer->get_length())
118+ throw out_of_range_memory{};
119+
120+ // Initialize for failure.
121+ fp.ret->t0 = Sys_UTFerror;
122+ fp.ret->t1 = 0;
123+ fp.ret->t2 = 0;
124+
125+ auto state = utf8::decode_state::accept;
126+ auto result = rune_t{};
127+ const auto buffer_len = buffer->get_length();
128+ for (auto i = n; i < buffer_len; ++i)
129+ {
130+ auto b = buffer->at<byte_t>(i);
131+ state = utf8::decode_step(state, result, b);
132+ if (state == utf8::decode_state::accept)
133+ {
134+ fp.ret->t0 = result;
135+ fp.ret->t1 = (i - n) + 1; // Number of bytes consumed
136+ fp.ret->t2 = 1;
137+ return;
138+ }
139+ else if (state == utf8::decode_state::reject)
140+ {
141+ fp.ret->t1 = 1;
142+ return;
143+ }
144+ }
145+}
146+
147+void
148+Sys_char2byte(vm_registers_t &r, vm_t &vm)
149+{
150+ auto &fp = r.stack.peek_frame()->base<F_Sys_char2byte>();
151+
152+ auto buffer = vm_alloc_t::from_allocation<vm_array_t>(fp.buf);
153+ assert(buffer->get_element_type()->size_in_bytes == intrinsic_type_desc::type<byte_t>()->size_in_bytes);
154+
155+ const auto n = fp.n;
156+ if (buffer == nullptr || n >= buffer->get_length())
157+ throw out_of_range_memory{};
158+
159+ const auto rune = static_cast<rune_t>(fp.c);
160+
161+ static_assert(Sys_UTFmax == sizeof(rune_t), "UTF max should be same size as rune");
162+ uint8_t buffer_local[sizeof(Sys_UTFmax)];
163+ auto begin = buffer_local;
164+
165+ auto end = utf8::encode(rune, begin);
166+ const auto count = static_cast<word_t>(end - begin);
167+
168+ // Initialize for failure.
169+ *fp.ret = 0;
170+
171+ const auto final_index = n + count;
172+ if (final_index > buffer->get_length())
173+ return;
174+
175+ // Insert the bytes into the buffer
176+ for (auto i = word_t{ n }; i < final_index; ++i)
177+ {
178+ buffer->at<uint8_t>(i) = *begin;
179+ begin++;
180+ }
181+
182+ *fp.ret = count;
183+}
184+
185+void
186+Sys_chdir(vm_registers_t &r, vm_t &vm)
187+{
188+ auto &fp = r.stack.peek_frame()->base<F_Sys_chdir>();
189+ throw vm_system_exception{ "Instruction not implemented" };
190+}
191+
192+void
193+Sys_create(vm_registers_t &r, vm_t &vm)
194+{
195+ auto &fp = r.stack.peek_frame()->base<F_Sys_create>();
196+ throw vm_system_exception{ "Instruction not implemented" };
197+}
198+
199+void
200+Sys_dial(vm_registers_t &r, vm_t &vm)
201+{
202+ auto &fp = r.stack.peek_frame()->base<F_Sys_dial>();
203+ throw vm_system_exception{ "Instruction not implemented" };
204+}
205+
206+void
207+Sys_dirread(vm_registers_t &r, vm_t &vm)
208+{
209+ auto &fp = r.stack.peek_frame()->base<F_Sys_dirread>();
210+ throw vm_system_exception{ "Instruction not implemented" };
211+}
212+
213+void
214+Sys_dup(vm_registers_t &r, vm_t &vm)
215+{
216+ auto &fp = r.stack.peek_frame()->base<F_Sys_dup>();
217+ throw vm_system_exception{ "Instruction not implemented" };
218+}
219+
220+void
221+Sys_export(vm_registers_t &r, vm_t &vm)
222+{
223+ auto &fp = r.stack.peek_frame()->base<F_Sys_export>();
224+ throw vm_system_exception{ "Instruction not implemented" };
225+}
226+
227+void
228+Sys_fauth(vm_registers_t &r, vm_t &vm)
229+{
230+ auto &fp = r.stack.peek_frame()->base<F_Sys_fauth>();
231+ throw vm_system_exception{ "Instruction not implemented" };
232+}
233+
234+void
235+Sys_fd2path(vm_registers_t &r, vm_t &vm)
236+{
237+ auto &fp = r.stack.peek_frame()->base<F_Sys_fd2path>();
238+ throw vm_system_exception{ "Instruction not implemented" };
239+}
240+
241+void
242+Sys_fildes(vm_registers_t &r, vm_t &vm)
243+{
244+ auto &fp = r.stack.peek_frame()->base<F_Sys_fildes>();
245+ throw vm_system_exception{ "Instruction not implemented" };
246+}
247+
248+void
249+Sys_file2chan(vm_registers_t &r, vm_t &vm)
250+{
251+ auto &fp = r.stack.peek_frame()->base<F_Sys_file2chan>();
252+ throw vm_system_exception{ "Instruction not implemented" };
253+}
254+
255+void
256+Sys_fprint(vm_registers_t &r, vm_t &vm)
257+{
258+ auto &fp = r.stack.peek_frame()->base<F_Sys_fprint>();
259+ throw vm_system_exception{ "Instruction not implemented" };
260+}
261+
262+void
263+Sys_fstat(vm_registers_t &r, vm_t &vm)
264+{
265+ auto &fp = r.stack.peek_frame()->base<F_Sys_fstat>();
266+ throw vm_system_exception{ "Instruction not implemented" };
267+}
268+
269+void
270+Sys_fversion(vm_registers_t &r, vm_t &vm)
271+{
272+ auto &fp = r.stack.peek_frame()->base<F_Sys_fversion>();
273+ throw vm_system_exception{ "Instruction not implemented" };
274+}
275+
276+void
277+Sys_fwstat(vm_registers_t &r, vm_t &vm)
278+{
279+ auto &fp = r.stack.peek_frame()->base<F_Sys_fwstat>();
280+ throw vm_system_exception{ "Instruction not implemented" };
281+}
282+
283+void
284+Sys_iounit(vm_registers_t &r, vm_t &vm)
285+{
286+ auto &fp = r.stack.peek_frame()->base<F_Sys_iounit>();
287+ throw vm_system_exception{ "Instruction not implemented" };
288+}
289+
290+void
291+Sys_listen(vm_registers_t &r, vm_t &vm)
292+{
293+ auto &fp = r.stack.peek_frame()->base<F_Sys_listen>();
294+ throw vm_system_exception{ "Instruction not implemented" };
295+}
296+
297+void
298+Sys_millisec(vm_registers_t &r, vm_t &vm)
299+{
300+ const auto current_time = std::chrono::steady_clock::now();
301+ const auto current_time_in_ms = std::chrono::duration_cast<std::chrono::milliseconds>(current_time.time_since_epoch());
302+
303+ auto &fp = r.stack.peek_frame()->base<F_Sys_millisec>();
304+ *fp.ret = static_cast<word_t>(current_time_in_ms.count());
305+}
306+
307+void
308+Sys_mount(vm_registers_t &r, vm_t &vm)
309+{
310+ auto &fp = r.stack.peek_frame()->base<F_Sys_mount>();
311+ throw vm_system_exception{ "Instruction not implemented" };
312+}
313+
314+void
315+Sys_open(vm_registers_t &r, vm_t &vm)
316+{
317+ auto &fp = r.stack.peek_frame()->base<F_Sys_open>();
318+ throw vm_system_exception{ "Instruction not implemented" };
319+}
320+
321+void
322+Sys_pctl(vm_registers_t &r, vm_t &vm)
323+{
324+ auto &fp = r.stack.peek_frame()->base<F_Sys_pctl>();
325+ throw vm_system_exception{ "Instruction not implemented" };
326+}
327+
328+void
329+Sys_pipe(vm_registers_t &r, vm_t &vm)
330+{
331+ auto &fp = r.stack.peek_frame()->base<F_Sys_pipe>();
332+ throw vm_system_exception{ "Instruction not implemented" };
333+}
334+
335+void
336+Sys_pread(vm_registers_t &r, vm_t &vm)
337+{
338+ auto &fp = r.stack.peek_frame()->base<F_Sys_pread>();
339+ throw vm_system_exception{ "Instruction not implemented" };
340+}
341+
342+void
343+Sys_print(vm_registers_t &r, vm_t &vm)
344+{
345+ auto &fp = r.stack.peek_frame()->base<F_Sys_print>();
346+ auto str = vm_alloc_t::from_allocation<vm_string_t>(fp.s);
347+
348+ if (str == nullptr)
349+ throw dereference_nil{ "Print string" };
350+
351+ auto static_buffer = std::array<char, 1024>{};
352+ auto written = printf_to_buffer(*str, &fp.vargs, static_buffer.size(), static_buffer.data());
353+ if (written >= 0)
354+ {
355+ std::printf(static_buffer.data());
356+ }
357+ else
358+ {
359+ auto dynamic_buffer = std::vector<char>(static_buffer.size() * 2);
360+ written = printf_to_dynamic_buffer(*str, &fp.vargs, dynamic_buffer);
361+ std::printf(dynamic_buffer.data());
362+ }
363+
364+ *fp.ret = written;
365+}
366+
367+void
368+Sys_pwrite(vm_registers_t &r, vm_t &vm)
369+{
370+ auto &fp = r.stack.peek_frame()->base<F_Sys_pwrite>();
371+ throw vm_system_exception{ "Instruction not implemented" };
372+}
373+
374+void
375+Sys_read(vm_registers_t &r, vm_t &vm)
376+{
377+ auto &fp = r.stack.peek_frame()->base<F_Sys_read>();
378+ throw vm_system_exception{ "Instruction not implemented" };
379+}
380+
381+void
382+Sys_readn(vm_registers_t &r, vm_t &vm)
383+{
384+ auto &fp = r.stack.peek_frame()->base<F_Sys_readn>();
385+ throw vm_system_exception{ "Instruction not implemented" };
386+}
387+
388+void
389+Sys_remove(vm_registers_t &r, vm_t &vm)
390+{
391+ auto &fp = r.stack.peek_frame()->base<F_Sys_remove>();
392+ throw vm_system_exception{ "Instruction not implemented" };
393+}
394+
395+void
396+Sys_seek(vm_registers_t &r, vm_t &vm)
397+{
398+ auto &fp = r.stack.peek_frame()->base<F_Sys_seek>();
399+ throw vm_system_exception{ "Instruction not implemented" };
400+}
401+
402+void
403+Sys_sleep(vm_registers_t &r, vm_t &vm)
404+{
405+ auto &fp = r.stack.peek_frame()->base<F_Sys_sleep>();
406+
407+ const auto period_ms = fp.period;
408+
409+ // [TODO] Thread should yield to another according to the sleep() specification.
410+ r.current_thread_state = vm_thread_state_t::release;
411+ std::this_thread::sleep_for(std::chrono::milliseconds(period_ms));
412+ r.current_thread_state = vm_thread_state_t::ready;
413+
414+ *fp.ret = 0;
415+}
416+
417+void
418+Sys_sprint(vm_registers_t &r, vm_t &vm)
419+{
420+ auto &fp = r.stack.peek_frame()->base<F_Sys_sprint>();
421+ throw vm_system_exception{ "Instruction not implemented" };
422+}
423+
424+void
425+Sys_stat(vm_registers_t &r, vm_t &vm)
426+{
427+ auto &fp = r.stack.peek_frame()->base<F_Sys_stat>();
428+ throw vm_system_exception{ "Instruction not implemented" };
429+}
430+
431+void
432+Sys_stream(vm_registers_t &r, vm_t &vm)
433+{
434+ auto &fp = r.stack.peek_frame()->base<F_Sys_stream>();
435+ throw vm_system_exception{ "Instruction not implemented" };
436+}
437+
438+void
439+Sys_tokenize(vm_registers_t &r, vm_t &vm)
440+{
441+ auto &fp = r.stack.peek_frame()->base<F_Sys_tokenize>();
442+ throw vm_system_exception{ "Instruction not implemented" };
443+}
444+
445+void
446+Sys_unmount(vm_registers_t &r, vm_t &vm)
447+{
448+ auto &fp = r.stack.peek_frame()->base<F_Sys_unmount>();
449+ throw vm_system_exception{ "Instruction not implemented" };
450+}
451+
452+void
453+Sys_utfbytes(vm_registers_t &r, vm_t &vm)
454+{
455+ auto &fp = r.stack.peek_frame()->base<F_Sys_utfbytes>();
456+ throw vm_system_exception{ "Instruction not implemented" };
457+}
458+
459+void
460+Sys_werrstr(vm_registers_t &r, vm_t &vm)
461+{
462+ auto &fp = r.stack.peek_frame()->base<F_Sys_werrstr>();
463+ throw vm_system_exception{ "Instruction not implemented" };
464+}
465+
466+void
467+Sys_write(vm_registers_t &r, vm_t &vm)
468+{
469+ auto &fp = r.stack.peek_frame()->base<F_Sys_write>();
470+ throw vm_system_exception{ "Instruction not implemented" };
471+}
472+
473+void
474+Sys_wstat(vm_registers_t &r, vm_t &vm)
475+{
476+ auto &fp = r.stack.peek_frame()->base<F_Sys_wstat>();
477+ throw vm_system_exception{ "Instruction not implemented" };
478+}
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+
14+using namespace disvm;
15+using namespace disvm::runtime;
16+
17+struct Sys_Qid
18+{
19+ big_t path;
20+ word_t vers;
21+ word_t qtype;
22+};
23+const byte_t Sys_Qid_map[] = { 0 };
24+struct Sys_Dir
25+{
26+ /* vm_string_t */ pointer_t name;
27+ /* vm_string_t */ pointer_t uid;
28+ /* vm_string_t */ pointer_t gid;
29+ /* vm_string_t */ pointer_t muid;
30+ Sys_Qid qid;
31+ word_t mode;
32+ word_t atime;
33+ word_t mtime;
34+ byte_t _pad44[4];
35+ big_t length;
36+ word_t dtype;
37+ word_t dev;
38+};
39+const byte_t Sys_Dir_map[] = { 0xf0, };
40+struct Sys_FD
41+{
42+ word_t fd;
43+};
44+const byte_t Sys_FD_map[] = { 0 };
45+struct Sys_Connection
46+{
47+ /* Sys_FD */ pointer_t dfd;
48+ /* Sys_FD */ pointer_t cfd;
49+ /* vm_string_t */ pointer_t dir;
50+};
51+const byte_t Sys_Connection_map[] = { 0xe0, };
52+using Sys_Rread = struct { /* vm_array_t */ pointer_t t0; /* vm_string_t */ pointer_t t1; };
53+#define Sys_Rread_map {0xc0,}
54+using Sys_Rwrite = struct { word_t t0; /* vm_string_t */ pointer_t t1; };
55+#define Sys_Rwrite_map {0x40,}
56+struct Sys_FileIO
57+{
58+ /* vm_channel_t */ pointer_t read;
59+ /* vm_channel_t */ pointer_t write;
60+};
61+using Sys_FileIO_read = struct { word_t t0; word_t t1; word_t t2; /* vm_channel_t */ pointer_t t3; };
62+#define Sys_FileIO_read_map {0x10,}
63+using Sys_FileIO_write = struct { word_t t0; /* vm_array_t */ pointer_t t1; word_t t2; /* vm_channel_t */ pointer_t t3; };
64+#define Sys_FileIO_write_map {0x50,}
65+const byte_t Sys_FileIO_map[] = { 0xc0, };
66+void Sys_announce(vm_registers_t &r, vm_t &vm);
67+struct F_Sys_announce : public vm_frame_base_alloc_t
68+{
69+ struct { word_t t0; Sys_Connection t1; }*ret;
70+ byte_t temps[12];
71+ /* vm_string_t */ pointer_t addr;
72+};
73+void Sys_aprint(vm_registers_t &r, vm_t &vm);
74+struct F_Sys_aprint : public vm_frame_base_alloc_t
75+{
76+ /* vm_array_t */ pointer_t* ret;
77+ byte_t temps[12];
78+ /* vm_string_t */ pointer_t s;
79+ byte_t vargs;
80+};
81+void Sys_bind(vm_registers_t &r, vm_t &vm);
82+struct F_Sys_bind : public vm_frame_base_alloc_t
83+{
84+ word_t* ret;
85+ byte_t temps[12];
86+ /* vm_string_t */ pointer_t s;
87+ /* vm_string_t */ pointer_t on;
88+ word_t flags;
89+};
90+void Sys_byte2char(vm_registers_t &r, vm_t &vm);
91+struct F_Sys_byte2char : public vm_frame_base_alloc_t
92+{
93+ struct { word_t t0; word_t t1; word_t t2; }*ret;
94+ byte_t temps[12];
95+ /* vm_array_t */ pointer_t buf;
96+ word_t n;
97+};
98+void Sys_char2byte(vm_registers_t &r, vm_t &vm);
99+struct F_Sys_char2byte : public vm_frame_base_alloc_t
100+{
101+ word_t* ret;
102+ byte_t temps[12];
103+ word_t c;
104+ /* vm_array_t */ pointer_t buf;
105+ word_t n;
106+};
107+void Sys_chdir(vm_registers_t &r, vm_t &vm);
108+struct F_Sys_chdir : public vm_frame_base_alloc_t
109+{
110+ word_t* ret;
111+ byte_t temps[12];
112+ /* vm_string_t */ pointer_t path;
113+};
114+void Sys_create(vm_registers_t &r, vm_t &vm);
115+struct F_Sys_create : public vm_frame_base_alloc_t
116+{
117+ /* Sys_FD */ pointer_t* ret;
118+ byte_t temps[12];
119+ /* vm_string_t */ pointer_t s;
120+ word_t mode;
121+ word_t perm;
122+};
123+void Sys_dial(vm_registers_t &r, vm_t &vm);
124+struct F_Sys_dial : public vm_frame_base_alloc_t
125+{
126+ struct { word_t t0; Sys_Connection t1; }*ret;
127+ byte_t temps[12];
128+ /* vm_string_t */ pointer_t addr;
129+ /* vm_string_t */ pointer_t local;
130+};
131+void Sys_dirread(vm_registers_t &r, vm_t &vm);
132+struct F_Sys_dirread : public vm_frame_base_alloc_t
133+{
134+ struct { word_t t0; /* vm_array_t */ pointer_t t1; }*ret;
135+ byte_t temps[12];
136+ /* Sys_FD */ pointer_t fd;
137+};
138+void Sys_dup(vm_registers_t &r, vm_t &vm);
139+struct F_Sys_dup : public vm_frame_base_alloc_t
140+{
141+ word_t* ret;
142+ byte_t temps[12];
143+ word_t old;
144+ word_t new_;
145+};
146+void Sys_export(vm_registers_t &r, vm_t &vm);
147+struct F_Sys_export : public vm_frame_base_alloc_t
148+{
149+ word_t* ret;
150+ byte_t temps[12];
151+ /* Sys_FD */ pointer_t c;
152+ /* vm_string_t */ pointer_t dir;
153+ word_t flag;
154+};
155+void Sys_fauth(vm_registers_t &r, vm_t &vm);
156+struct F_Sys_fauth : public vm_frame_base_alloc_t
157+{
158+ /* Sys_FD */ pointer_t* ret;
159+ byte_t temps[12];
160+ /* Sys_FD */ pointer_t fd;
161+ /* vm_string_t */ pointer_t aname;
162+};
163+void Sys_fd2path(vm_registers_t &r, vm_t &vm);
164+struct F_Sys_fd2path : public vm_frame_base_alloc_t
165+{
166+ /* vm_string_t */ pointer_t* ret;
167+ byte_t temps[12];
168+ /* Sys_FD */ pointer_t fd;
169+};
170+void Sys_fildes(vm_registers_t &r, vm_t &vm);
171+struct F_Sys_fildes : public vm_frame_base_alloc_t
172+{
173+ /* Sys_FD */ pointer_t* ret;
174+ byte_t temps[12];
175+ word_t fd;
176+};
177+void Sys_file2chan(vm_registers_t &r, vm_t &vm);
178+struct F_Sys_file2chan : public vm_frame_base_alloc_t
179+{
180+ /* Sys_FileIO */ pointer_t* ret;
181+ byte_t temps[12];
182+ /* vm_string_t */ pointer_t dir;
183+ /* vm_string_t */ pointer_t file;
184+};
185+void Sys_fprint(vm_registers_t &r, vm_t &vm);
186+struct F_Sys_fprint : public vm_frame_base_alloc_t
187+{
188+ word_t* ret;
189+ byte_t temps[12];
190+ /* Sys_FD */ pointer_t fd;
191+ /* vm_string_t */ pointer_t s;
192+ byte_t vargs;
193+};
194+void Sys_fstat(vm_registers_t &r, vm_t &vm);
195+struct F_Sys_fstat : public vm_frame_base_alloc_t
196+{
197+ struct { word_t t0; byte_t _pad4[4]; Sys_Dir t1; }*ret;
198+ byte_t temps[12];
199+ /* Sys_FD */ pointer_t fd;
200+};
201+void Sys_fversion(vm_registers_t &r, vm_t &vm);
202+struct F_Sys_fversion : public vm_frame_base_alloc_t
203+{
204+ struct { word_t t0; /* vm_string_t */ pointer_t t1; }*ret;
205+ byte_t temps[12];
206+ /* Sys_FD */ pointer_t fd;
207+ word_t msize;
208+ /* vm_string_t */ pointer_t version;
209+};
210+void Sys_fwstat(vm_registers_t &r, vm_t &vm);
211+struct F_Sys_fwstat : public vm_frame_base_alloc_t
212+{
213+ word_t* ret;
214+ byte_t temps[12];
215+ /* Sys_FD */ pointer_t fd;
216+ byte_t _pad36[4];
217+ Sys_Dir d;
218+};
219+void Sys_iounit(vm_registers_t &r, vm_t &vm);
220+struct F_Sys_iounit : public vm_frame_base_alloc_t
221+{
222+ word_t* ret;
223+ byte_t temps[12];
224+ /* Sys_FD */ pointer_t fd;
225+};
226+void Sys_listen(vm_registers_t &r, vm_t &vm);
227+struct F_Sys_listen : public vm_frame_base_alloc_t
228+{
229+ struct { word_t t0; Sys_Connection t1; }*ret;
230+ byte_t temps[12];
231+ Sys_Connection c;
232+};
233+void Sys_millisec(vm_registers_t &r, vm_t &vm);
234+struct F_Sys_millisec : public vm_frame_base_alloc_t
235+{
236+ word_t* ret;
237+ byte_t temps[12];
238+};
239+void Sys_mount(vm_registers_t &r, vm_t &vm);
240+struct F_Sys_mount : public vm_frame_base_alloc_t
241+{
242+ word_t* ret;
243+ byte_t temps[12];
244+ /* Sys_FD */ pointer_t fd;
245+ /* Sys_FD */ pointer_t afd;
246+ /* vm_string_t */ pointer_t on;
247+ word_t flags;
248+ /* vm_string_t */ pointer_t spec;
249+};
250+void Sys_open(vm_registers_t &r, vm_t &vm);
251+struct F_Sys_open : public vm_frame_base_alloc_t
252+{
253+ /* Sys_FD */ pointer_t* ret;
254+ byte_t temps[12];
255+ /* vm_string_t */ pointer_t s;
256+ word_t mode;
257+};
258+void Sys_pctl(vm_registers_t &r, vm_t &vm);
259+struct F_Sys_pctl : public vm_frame_base_alloc_t
260+{
261+ word_t* ret;
262+ byte_t temps[12];
263+ word_t flags;
264+ /* vm_list_t */ pointer_t movefd;
265+};
266+void Sys_pipe(vm_registers_t &r, vm_t &vm);
267+struct F_Sys_pipe : public vm_frame_base_alloc_t
268+{
269+ word_t* ret;
270+ byte_t temps[12];
271+ /* vm_array_t */ pointer_t fds;
272+};
273+void Sys_pread(vm_registers_t &r, vm_t &vm);
274+struct F_Sys_pread : public vm_frame_base_alloc_t
275+{
276+ word_t* ret;
277+ byte_t temps[12];
278+ /* Sys_FD */ pointer_t fd;
279+ /* vm_array_t */ pointer_t buf;
280+ word_t n;
281+ byte_t _pad44[4];
282+ big_t off;
283+};
284+void Sys_print(vm_registers_t &r, vm_t &vm);
285+struct F_Sys_print : public vm_frame_base_alloc_t
286+{
287+ word_t* ret;
288+ byte_t temps[12];
289+ /* vm_string_t */ pointer_t s;
290+ byte_t vargs;
291+};
292+void Sys_pwrite(vm_registers_t &r, vm_t &vm);
293+struct F_Sys_pwrite : public vm_frame_base_alloc_t
294+{
295+ word_t* ret;
296+ byte_t temps[12];
297+ /* Sys_FD */ pointer_t fd;
298+ /* vm_array_t */ pointer_t buf;
299+ word_t n;
300+ byte_t _pad44[4];
301+ big_t off;
302+};
303+void Sys_read(vm_registers_t &r, vm_t &vm);
304+struct F_Sys_read : public vm_frame_base_alloc_t
305+{
306+ word_t* ret;
307+ byte_t temps[12];
308+ /* Sys_FD */ pointer_t fd;
309+ /* vm_array_t */ pointer_t buf;
310+ word_t n;
311+};
312+void Sys_readn(vm_registers_t &r, vm_t &vm);
313+struct F_Sys_readn : public vm_frame_base_alloc_t
314+{
315+ word_t* ret;
316+ byte_t temps[12];
317+ /* Sys_FD */ pointer_t fd;
318+ /* vm_array_t */ pointer_t buf;
319+ word_t n;
320+};
321+void Sys_remove(vm_registers_t &r, vm_t &vm);
322+struct F_Sys_remove : public vm_frame_base_alloc_t
323+{
324+ word_t* ret;
325+ byte_t temps[12];
326+ /* vm_string_t */ pointer_t s;
327+};
328+void Sys_seek(vm_registers_t &r, vm_t &vm);
329+struct F_Sys_seek : public vm_frame_base_alloc_t
330+{
331+ big_t* ret;
332+ byte_t temps[12];
333+ /* Sys_FD */ pointer_t fd;
334+ byte_t _pad36[4];
335+ big_t off;
336+ word_t start;
337+};
338+void Sys_sleep(vm_registers_t &r, vm_t &vm);
339+struct F_Sys_sleep : public vm_frame_base_alloc_t
340+{
341+ word_t* ret;
342+ byte_t temps[12];
343+ word_t period;
344+};
345+void Sys_sprint(vm_registers_t &r, vm_t &vm);
346+struct F_Sys_sprint : public vm_frame_base_alloc_t
347+{
348+ /* vm_string_t */ pointer_t* ret;
349+ byte_t temps[12];
350+ /* vm_string_t */ pointer_t s;
351+ byte_t vargs;
352+};
353+void Sys_stat(vm_registers_t &r, vm_t &vm);
354+struct F_Sys_stat : public vm_frame_base_alloc_t
355+{
356+ struct { word_t t0; byte_t _pad4[4]; Sys_Dir t1; }*ret;
357+ byte_t temps[12];
358+ /* vm_string_t */ pointer_t s;
359+};
360+void Sys_stream(vm_registers_t &r, vm_t &vm);
361+struct F_Sys_stream : public vm_frame_base_alloc_t
362+{
363+ word_t* ret;
364+ byte_t temps[12];
365+ /* Sys_FD */ pointer_t src;
366+ /* Sys_FD */ pointer_t dst;
367+ word_t bufsiz;
368+};
369+void Sys_tokenize(vm_registers_t &r, vm_t &vm);
370+struct F_Sys_tokenize : public vm_frame_base_alloc_t
371+{
372+ struct { word_t t0; /* vm_list_t */ pointer_t t1; }*ret;
373+ byte_t temps[12];
374+ /* vm_string_t */ pointer_t s;
375+ /* vm_string_t */ pointer_t delim;
376+};
377+void Sys_unmount(vm_registers_t &r, vm_t &vm);
378+struct F_Sys_unmount : public vm_frame_base_alloc_t
379+{
380+ word_t* ret;
381+ byte_t temps[12];
382+ /* vm_string_t */ pointer_t s1;
383+ /* vm_string_t */ pointer_t s2;
384+};
385+void Sys_utfbytes(vm_registers_t &r, vm_t &vm);
386+struct F_Sys_utfbytes : public vm_frame_base_alloc_t
387+{
388+ word_t* ret;
389+ byte_t temps[12];
390+ /* vm_array_t */ pointer_t buf;
391+ word_t n;
392+};
393+void Sys_werrstr(vm_registers_t &r, vm_t &vm);
394+struct F_Sys_werrstr : public vm_frame_base_alloc_t
395+{
396+ word_t* ret;
397+ byte_t temps[12];
398+ /* vm_string_t */ pointer_t s;
399+};
400+void Sys_write(vm_registers_t &r, vm_t &vm);
401+struct F_Sys_write : public vm_frame_base_alloc_t
402+{
403+ word_t* ret;
404+ byte_t temps[12];
405+ /* Sys_FD */ pointer_t fd;
406+ /* vm_array_t */ pointer_t buf;
407+ word_t n;
408+};
409+void Sys_wstat(vm_registers_t &r, vm_t &vm);
410+struct F_Sys_wstat : public vm_frame_base_alloc_t
411+{
412+ word_t* ret;
413+ byte_t temps[12];
414+ /* vm_string_t */ pointer_t s;
415+ byte_t _pad36[4];
416+ Sys_Dir d;
417+};
418+const char *Sys_PATH = "$Sys";
419+const word_t Sys_Maxint = 0x7fffffff;
420+const word_t Sys_QTDIR = 0x80;
421+const word_t Sys_QTAPPEND = 0x40;
422+const word_t Sys_QTEXCL = 0x20;
423+const word_t Sys_QTAUTH = 0x8;
424+const word_t Sys_QTTMP = 0x4;
425+const word_t Sys_QTFILE = 0x0;
426+const word_t Sys_ATOMICIO = 0x2000;
427+const word_t Sys_SEEKSTART = 0x0;
428+const word_t Sys_SEEKRELA = 0x1;
429+const word_t Sys_SEEKEND = 0x2;
430+const word_t Sys_NAMEMAX = 0x100;
431+const word_t Sys_ERRMAX = 0x80;
432+const word_t Sys_WAITLEN = 0xc0;
433+const word_t Sys_OREAD = 0x0;
434+const word_t Sys_OWRITE = 0x1;
435+const word_t Sys_ORDWR = 0x2;
436+const word_t Sys_OTRUNC = 0x10;
437+const word_t Sys_ORCLOSE = 0x40;
438+const word_t Sys_OEXCL = 0x1000;
439+const word_t Sys_DMDIR = 0x80000000;
440+const word_t Sys_DMAPPEND = 0x40000000;
441+const word_t Sys_DMEXCL = 0x20000000;
442+const word_t Sys_DMAUTH = 0x8000000;
443+const word_t Sys_DMTMP = 0x4000000;
444+const word_t Sys_MREPL = 0x0;
445+const word_t Sys_MBEFORE = 0x1;
446+const word_t Sys_MAFTER = 0x2;
447+const word_t Sys_MCREATE = 0x4;
448+const word_t Sys_MCACHE = 0x10;
449+const word_t Sys_NEWFD = 0x1;
450+const word_t Sys_FORKFD = 0x2;
451+const word_t Sys_NEWNS = 0x4;
452+const word_t Sys_FORKNS = 0x8;
453+const word_t Sys_NEWPGRP = 0x10;
454+const word_t Sys_NODEVS = 0x20;
455+const word_t Sys_NEWENV = 0x40;
456+const word_t Sys_FORKENV = 0x80;
457+const word_t Sys_EXPWAIT = 0x0;
458+const word_t Sys_EXPASYNC = 0x1;
459+const word_t Sys_UTFmax = 0x4;
460+const word_t Sys_UTFerror = 0xfffd;
461+const word_t Sys_Runemax = 0x10ffff;
462+const word_t Sys_Runemask = 0x1fffff;
463+
464+#endif // _DISVM_SRC_VM_SYS_SYSMOD_H_
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+
12+using namespace disvm::runtime;
13+using namespace disvm::runtime::utf8;
14+
15+// Shamelessly adopted and altered from Inferno implementation (xprint - libinterp/runt.c)
16+// [TODO] Handle UTF-8
17+word_t printf_to_buffer(const vm_string_t &msg_fmt, byte_t *msg_args, std::size_t buffer_size, char *buffer)
18+{
19+ auto b_curr = buffer;
20+ const auto b_end = buffer + (buffer_size - 1);
21+ const auto fmt_len = msg_fmt.get_length();
22+ auto i = word_t{ 0 };
23+
24+ // Loop over the entire format string
25+ while (i < fmt_len)
26+ {
27+ if (b_curr >= b_end)
28+ return -1;
29+
30+ auto c = msg_fmt.get_rune(i);
31+ ++i;
32+ if (c != '%')
33+ {
34+ b_curr = reinterpret_cast<char *>(utf8::encode(c, reinterpret_cast<uint8_t *>(b_curr)));
35+ continue;
36+ }
37+
38+ auto format = std::vector<char>{};
39+ format.push_back('%');
40+ auto wb = int{ 0 };
41+ auto big_flag = false;
42+
43+ // Decode the format specifier
44+ while (i < fmt_len)
45+ {
46+ c = msg_fmt.get_rune(i);
47+ ++i;
48+ switch (c)
49+ {
50+ // Format flags
51+ default:
52+ continue;
53+ case 'b': // Print big type (64-bit)
54+ format.push_back('l');
55+ format.push_back('l');
56+ big_flag = true;
57+ continue;
58+
59+ // Format verbs
60+ case '%': // Escaped specifier
61+ wb = std::snprintf(b_curr, (b_end - b_curr), "%%%%");
62+ break;
63+ case 'o': // octal
64+ case 'd': // decimal
65+ case 'x': // hexidecimal
66+ case 'X': // Upper-case hexidecimal
67+ format.push_back(static_cast<char>(c));
68+ format.push_back('\0');
69+ if (big_flag)
70+ {
71+ const auto b = *reinterpret_cast<big_t *>(msg_args);
72+ wb = std::snprintf(b_curr, (b_end - b_curr), format.data(), b);
73+ msg_args += sizeof(b);
74+ }
75+ else
76+ {
77+ const auto w = *reinterpret_cast<word_t *>(msg_args);
78+ wb = std::snprintf(b_curr, (b_end - b_curr), format.data(), w);
79+ msg_args += sizeof(w);
80+ }
81+ break;
82+ case 's': // string
83+ {
84+ auto s = vm_alloc_t::from_allocation<vm_string_t>(*reinterpret_cast<pointer_t *>(msg_args));
85+ if (s != nullptr)
86+ wb = std::snprintf(b_curr, (b_end - b_curr), "%s", s->str());
87+
88+ msg_args += sizeof(pointer_t);
89+ break;
90+ }
91+ // [SPEC] Undocumented format verb for debugging VM objects.
92+ case 'H':
93+ {
94+ auto alloc = vm_alloc_t::from_allocation(*reinterpret_cast<pointer_t *>(msg_args));
95+ auto rc = static_cast<std::size_t>(-1);
96+ const void *type_alloc = nullptr;
97+ if (alloc != nullptr)
98+ {
99+ rc = alloc->get_ref_count();
100+ type_alloc = alloc->alloc_type.get();
101+ }
102+
103+ wb = std::snprintf(b_curr, (b_end - b_curr), "%" PRIdPTR ".%#08" PRIxPTR, rc, reinterpret_cast<std::size_t>(type_alloc));
104+ msg_args += sizeof(pointer_t);
105+ break;
106+ }
107+ }
108+
109+ break;
110+ }
111+
112+ if (wb < 0)
113+ return -1;
114+
115+ b_curr += wb;
116+ }
117+
118+ assert(*b_curr == '\0' && "The buffer should be null-terminated");
119+ return (b_curr - buffer);
120+}
121+
122+word_t printf_to_dynamic_buffer(const vm_string_t &msg_fmt, byte_t *msg_args, std::vector<char> &buffer)
123+{
124+ auto result = word_t{ 0 };
125+ while (-1 == (result = printf_to_buffer(msg_fmt, msg_args, buffer.size(), buffer.data())))
126+ {
127+ buffer.resize(buffer.size() * 2);
128+ }
129+
130+ return result;
131+}
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.
15+disvm::runtime::word_t printf_to_buffer(const disvm::runtime::vm_string_t &msg_fmt, disvm::runtime::byte_t *msg_args, std::size_t buffer_size, char *buffer);
16+
17+// Prints the format and arguments to the supplied dynamic buffer.
18+// Returns the number of bytes written.
19+disvm::runtime::word_t printf_to_dynamic_buffer(const disvm::runtime::vm_string_t &msg_fmt, disvm::runtime::byte_t *msg_args, std::vector<char> &buffer);
20+
21+#endif // _DISVM_SRC_VM_SYS_UTILS_H_
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+
17+using namespace disvm;
18+using namespace disvm::runtime;
19+
20+namespace
21+{
22+ // Update VM registers based on the instruction.
23+ void decode_address(const vm_exec_op_t &inst, vm_registers_t &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+
128+vm_registers_t::vm_registers_t(
129+ const vm_thread_t &thread,
130+ vm_module_ref_t &entry)
131+ : current_thread_state{ vm_thread_state_t::ready }
132+ , module_ref{ &entry }
133+ , mp_base{ entry.mp_base }
134+ , thread{ thread }
135+ , pc{ entry.module->header.entry_pc }
136+ , stack{ entry.module->header.stack_extent }
137+ , src{ nullptr }
138+ , mid{ nullptr }
139+ , dest{ nullptr }
140+{
141+ // Add a reference to the module ref and MP register
142+ if (mp_base != nullptr)
143+ mp_base->add_ref();
144+
145+ module_ref->add_ref();
146+}
147+
148+vm_registers_t::~vm_registers_t()
149+{
150+ dec_ref_count_and_free(mp_base);
151+ debug::assign_debug_pointer(&mp_base);
152+
153+ dec_ref_count_and_free(module_ref);
154+ debug::assign_debug_pointer(&module_ref);
155+}
156+
157+std::shared_ptr<const type_descriptor_t> vm_thread_t::type_desc()
158+{
159+ return intrinsic_type_desc::type<vm_thread_t>();
160+}
161+
162+vm_thread_t::vm_thread_t(
163+ vm_module_ref_t &entry,
164+ uint32_t parent_thread_id)
165+ : vm_alloc_t(vm_thread_t::type_desc())
166+ , _parent_thread_id{ parent_thread_id }
167+ , _registers{ *this, entry }
168+ , _thread_id{ get_unique_thread_id() }
169+{
170+ assert(_parent_thread_id != _thread_id);
171+ assert(static_cast<std::size_t>(_registers.pc) < entry.code_section.size());
172+
173+ // Set up the stack
174+ const auto entry_type = entry.module->header.entry_type;
175+ auto frame_type = entry.type_section[entry_type];
176+ _registers.stack.alloc_frame(frame_type);
177+
178+ // Pushing the initial frame sets the FP register
179+ _registers.stack.push_frame();
180+
181+ debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "init: vm thread: %d %d\n", _thread_id, _parent_thread_id);
182+}
183+
184+vm_thread_t::vm_thread_t(
185+ vm_module_ref_t &entry,
186+ const vm_frame_t &initial_frame,
187+ vm_pc_t start_pc,
188+ uint32_t parent_thread_id)
189+ : vm_alloc_t(vm_thread_t::type_desc())
190+ , _parent_thread_id{ parent_thread_id }
191+ , _registers{ *this, entry }
192+ , _thread_id{ get_unique_thread_id() }
193+{
194+ _registers.pc = start_pc;
195+
196+ assert(_parent_thread_id != _thread_id);
197+ assert(static_cast<std::size_t>(_registers.pc) < entry.code_section.size());
198+
199+ // Set the stack
200+ auto frame_type = initial_frame.frame_type;
201+ _registers.stack.alloc_frame(frame_type);
202+
203+ // Pushing the initial frame sets the FP register
204+ auto current_frame = _registers.stack.push_frame();
205+
206+ // Copy over the frame into this thread - pass arguments to the thread
207+ current_frame->copy_frame_contents(initial_frame);
208+
209+ debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "init: vm thread: %d %d\n", _thread_id, _parent_thread_id);
210+}
211+
212+vm_thread_t::~vm_thread_t()
213+{
214+ debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "destroy: vm thread: %d %d\n", _thread_id, _parent_thread_id);
215+}
216+
217+vm_thread_state_t vm_thread_t::execute(vm_t &virtual_machine, uint32_t quanta)
218+{
219+ _registers.current_thread_state = vm_thread_state_t::running;
220+ assert(_registers.pc != runtime_constants::invalid_program_counter);
221+
222+ auto execution_duration = uint32_t{ 0 };
223+
224+ try
225+ {
226+ for (; execution_duration < quanta; ++execution_duration)
227+ {
228+ if (_registers.stack.peek_frame() == nullptr)
229+ {
230+ debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "exit: vm thread: stack exhausted: %d\n", _thread_id);
231+ _registers.current_thread_state = vm_thread_state_t::empty_stack;
232+ break;
233+ }
234+
235+ const auto &code_section = _registers.module_ref->code_section;
236+ assert(static_cast<std::size_t>(_registers.pc) < code_section.size());
237+
238+ const auto &inst = code_section[_registers.pc].op;
239+ decode_address(inst, _registers);
240+ _registers.pc++;
241+ vm_exec_table[static_cast<std::size_t>(inst.opcode)](_registers, virtual_machine);
242+
243+ if (_registers.current_thread_state != vm_thread_state_t::running)
244+ break;
245+ }
246+ }
247+ catch (const unhandled_user_exception &uue)
248+ {
249+ // [TODO] Store the exception
250+ debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "broken: vm thread: unhandled: >>%s<< %d >>%s<<\n", uue.exception_id, uue.program_counter, uue.module_name);
251+ _registers.current_thread_state = vm_thread_state_t::broken;
252+ }
253+ catch (const vm_user_exception &ue)
254+ {
255+ // [TODO] Store the exception
256+ debug::log_msg(debug::component_trace_t::thread, debug::log_level_t::debug, "broken: vm thread: >>%s<<\n", ue.what());
257+ _registers.current_thread_state = vm_thread_state_t::broken;
258+ }
259+
260+ // Log the execution result
261+ if (debug::is_component_tracing_enabled<debug::component_trace_t::thread>())
262+ {
263+ debug::log_msg(
264+ debug::component_trace_t::thread,
265+ debug::log_level_t::debug,
266+ "status: vm thread: execute: %d %d %d %d\n",
267+ _thread_id,
268+ execution_duration,
269+ quanta,
270+ _registers.current_thread_state);
271+ }
272+
273+ // If the thread is still in the running state transition it back to ready.
274+ if (_registers.current_thread_state == vm_thread_state_t::running)
275+ _registers.current_thread_state = vm_thread_state_t::ready;
276+
277+ return _registers.current_thread_state;
278+}
279+
280+vm_thread_state_t vm_thread_t::get_state() const
281+{
282+ return _registers.current_thread_state;
283+}
284+
285+uint32_t vm_thread_t::get_thread_id() const
286+{
287+ return _thread_id;
288+}
289+
290+uint32_t vm_thread_t::get_parent_thread_id() const
291+{
292+ return _parent_thread_id;
293+}
294+
295+const vm_registers_t& vm_thread_t::get_registers() const
296+{
297+ return _registers;
298+}
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+
15+using namespace disvm::runtime;
16+
17+namespace
18+{
19+ const uint8_t utf8d[] =
20+ {
21+ // The first part of the table maps bytes to character classes that
22+ // to reduce the size of the transition table and create bitmasks.
23+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
26+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
27+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
28+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
29+ 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
30+ 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
31+
32+ // The second part is a transition table that maps a combination
33+ // of a state of the automaton and a character class to a state.
34+ 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
35+ 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
36+ 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
37+ 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
38+ 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
39+ };
40+}
41+
42+// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
43+utf8::decode_state disvm::runtime::utf8::decode_step(utf8::decode_state &state, rune_t &codepoint, uint8_t byte)
44+{
45+ auto type = uint32_t{ utf8d[byte] };
46+
47+ codepoint = (state != utf8::decode_state::accept) ?
48+ (byte & 0x3fu) | (codepoint << 6) :
49+ (0xff >> type) & (byte);
50+
51+ state = static_cast<utf8::decode_state>(utf8d[256 + static_cast<uint32_t>(state) + type]);
52+ return state;
53+}
54+
55+// Adopted from 'countCodePoints(uint8_t* s, size_t* count)'
56+// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
57+std::size_t disvm::runtime::utf8::count_codepoints(const uint8_t *str, const std::size_t max_len)
58+{
59+ assert(str != nullptr);
60+ auto single_character = rune_t{};
61+ auto points = std::size_t{ 0 };
62+ auto state = utf8::decode_state::accept;
63+
64+ for (auto i = std::size_t{ 0 }; i < max_len; ++i)
65+ {
66+ if (utf8::decode_state::accept == utf8::decode_step(state, single_character, str[i]))
67+ points++;
68+ }
69+
70+ if (state != utf8::decode_state::accept)
71+ throw invalid_utf8{};
72+
73+ return points;
74+}
75+
76+std::size_t disvm::runtime::utf8::decode(const uint8_t *str, rune_t &codepoint)
77+{
78+ assert(str != nullptr);
79+ auto state = utf8::decode_state::accept;
80+
81+ codepoint = 0;
82+ auto begin = str;
83+ auto curr = str;
84+ auto c = uint8_t{};
85+ while (0 != (c = *curr))
86+ {
87+ curr++;
88+ state = utf8::decode_step(state, codepoint, c);
89+ if (state == utf8::decode_state::accept)
90+ return (curr - begin);
91+ }
92+
93+ // Empty string
94+ if (begin == curr)
95+ return 0;
96+
97+ throw invalid_utf8{};
98+}
99+
100+uint8_t *disvm::runtime::utf8::encode(const rune_t rune, uint8_t *str)
101+{
102+ assert(str != nullptr);
103+ if (rune <= utf8::constants::max_codepoint_ascii)
104+ {
105+ // Layout: 0xxxxxxx
106+ str[0] = static_cast<uint8_t>(rune);
107+ return (str + 1);
108+ }
109+ else if (rune <= utf8::constants::max_codepoint_2_byte)
110+ {
111+ // Layout: 110xxxxx 10xxxxxx
112+ str[0] = static_cast<uint8_t>(0xc0 + (rune >> 6));
113+ str[1] = static_cast<uint8_t>(0x80 + (rune & utf8::constants::multi_byte_value_mask));
114+ return (str + 2);
115+ }
116+ else if (rune <= utf8::constants::max_codepoint_3_byte)
117+ {
118+ // Layout: 1110xxxx 10xxxxxx 10xxxxxx
119+ str[0] = static_cast<uint8_t>(0xe0 + (rune >> 12));
120+ str[1] = static_cast<uint8_t>(0x80 + ((rune >> 6) & utf8::constants::multi_byte_value_mask));
121+ str[2] = static_cast<uint8_t>(0x80 + (rune & utf8::constants::multi_byte_value_mask));
122+ return (str + 3);
123+ }
124+ else if (rune <= utf8::constants::max_codepoint_4_byte)
125+ {
126+ // Layout: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
127+ str[0] = static_cast<uint8_t>(0xf0 + (rune >> 18));
128+ str[1] = static_cast<uint8_t>(0x80 + ((rune >> 12) & utf8::constants::multi_byte_value_mask));
129+ str[2] = static_cast<uint8_t>(0x80 + ((rune >> 6) & utf8::constants::multi_byte_value_mask));
130+ str[3] = static_cast<uint8_t>(0x80 + (rune & utf8::constants::multi_byte_value_mask));
131+ return (str + 4);
132+ }
133+
134+ assert(false && "Unsupported UTF-8 character");
135+ return nullptr;
136+}
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+
18+using namespace disvm;
19+using namespace disvm::runtime;
20+
21+vm_t::loaded_module_t::loaded_module_t(std::unique_ptr<runtime::vm_string_t> origin, std::shared_ptr<const runtime::vm_module_t> module)
22+ : origin{ std::move(origin) }
23+ , module{ module }
24+{
25+ assert(this->origin != nullptr);
26+}
27+
28+vm_t::loaded_module_t::loaded_module_t(loaded_module_t &&other)
29+ : origin{ std::move(other.origin) }
30+ , module{ std::move(other.module) }
31+{
32+}
33+
34+vm_t::loaded_module_t::~loaded_module_t()
35+{
36+ if (origin != nullptr)
37+ origin->release();
38+}
39+
40+vm_t::vm_t(
41+ create_vm_interface_callback_t<runtime::vm_scheduler_t> create_scheduler,
42+ create_vm_interface_callback_t<runtime::vm_garbage_collector_t> create_gc)
43+{
44+ // Initialize intrinsic type descriptors
45+ intrinsic_type_desc::initialize();
46+
47+ // Initialize built-in modules.
48+ builtin::initialize_builtin_modules();
49+
50+ if (create_gc == nullptr)
51+ _gc = std::make_unique<default_garbage_collector_t>(*this);
52+ else
53+ _gc = create_gc(*this);
54+
55+ if (create_scheduler == nullptr)
56+ {
57+ const auto system_thread_count = uint32_t{ 1 };
58+
59+ // The Inferno implementation defined the thread quanta as 2048 (include/interp.h)
60+ const auto thread_quanta = uint32_t{ 2048 };
61+
62+ _scheduler = std::make_unique<default_scheduler_t>(*this, system_thread_count, thread_quanta);
63+ }
64+ else
65+ {
66+ _scheduler = create_scheduler(*this);
67+ }
68+}
69+
70+vm_t::vm_t(uint32_t system_thread_count, uint32_t thread_quanta)
71+{
72+ // Initialize intrinsic type descriptors
73+ runtime::intrinsic_type_desc::initialize();
74+
75+ // Initialize built-in modules.
76+ builtin::initialize_builtin_modules();
77+
78+ _gc = std::make_unique<default_garbage_collector_t>(*this);
79+ _scheduler = std::make_unique<default_scheduler_t>(*this, system_thread_count, thread_quanta);
80+}
81+
82+vm_t::~vm_t()
83+{
84+ // Release the scheduler first since it could be using the gc instance.
85+ _scheduler.reset();
86+ _gc.reset();
87+}
88+
89+namespace
90+{
91+ const runtime::vm_thread_t& _exec(runtime::vm_scheduler_t &s, std::unique_ptr<runtime::vm_module_ref_t> &entry_module_ref)
92+ {
93+ auto thread = std::make_unique<vm_thread_t>(*entry_module_ref, vm_t::root_vm_thread_id);
94+
95+ // Now that the thread has been created and has inherited ownership of the pointer, relinquish the smart pointer's ownership.
96+ entry_module_ref->release(); // decrement the vm alloc ref count
97+ entry_module_ref.release(); // release the smart pointer
98+
99+ return s.schedule_thread(std::move(thread));
100+ }
101+}
102+
103+const runtime::vm_thread_t& vm_t::exec(const char *path)
104+{
105+ assert(path != nullptr);
106+ auto entry_module = load_module(path);
107+
108+ auto entry_module_ref = std::make_unique<vm_module_ref_t>(entry_module);
109+ return _exec(*_scheduler, entry_module_ref);
110+}
111+
112+const runtime::vm_thread_t& vm_t::exec(std::unique_ptr<runtime::vm_module_t> entry_module)
113+{
114+ assert(entry_module != nullptr);
115+
116+ auto entry_module_ref = std::make_unique<vm_module_ref_t>(std::move(entry_module));
117+ return _exec(*_scheduler, entry_module_ref);
118+}
119+
120+const runtime::vm_thread_t& vm_t::fork(
121+ const runtime::vm_thread_t &parent,
122+ runtime::vm_module_ref_t &module_ref,
123+ const runtime::vm_frame_t &initial_frame,
124+ runtime::vm_pc_t initial_pc)
125+{
126+ if (initial_pc < 0 || static_cast<vm_pc_t>(module_ref.code_section.size()) <= initial_pc)
127+ throw vm_system_exception{ "Invalid entry program counter" };
128+
129+ auto thread = std::make_unique<vm_thread_t>(module_ref, initial_frame, initial_pc, parent.get_thread_id());
130+ return _scheduler->schedule_thread(std::move(thread));
131+}
132+
133+std::shared_ptr<const runtime::vm_module_t> vm_t::load_module(const char *path)
134+{
135+ if (path == nullptr)
136+ throw vm_user_exception{ "Invalid module path" };
137+
138+ std::lock_guard<std::mutex> lock{ _modules_lock };
139+
140+ // Check if the module is already loaded.
141+ auto iter = std::begin(_modules);
142+ while (iter != std::end(_modules))
143+ {
144+ if (0 == ::strcmp(iter->origin->str(), path))
145+ {
146+ auto module = iter->module.lock();
147+
148+ // If an entry exists but is null, the module was loaded before. Read the module again and return.
149+ if (module == nullptr)
150+ {
151+ module = std::shared_ptr<vm_module_t>{ std::move(read_module(path)) };
152+ iter->module = module;
153+
154+ debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "reload: vm module: >>%s<<\n", path);
155+ }
156+
157+ return module;
158+ }
159+
160+ ++iter;
161+ }
162+
163+ auto new_module = std::shared_ptr<vm_module_t>{};
164+ if (path[0] == BUILTIN_MODULE_PREFIX_CHAR)
165+ {
166+ new_module = std::move(builtin::get_builtin_module(path));
167+ }
168+ else
169+ {
170+ // Read in the module
171+ new_module = std::move(read_module(path));
172+ }
173+
174+ auto path_local = std::make_unique<vm_string_t>(std::strlen(path), reinterpret_cast<const uint8_t *>(path));
175+ _modules.push_front(std::move(loaded_module_t{ std::move(path_local), new_module }));
176+
177+ debug::log_msg(debug::component_trace_t::module, debug::log_level_t::debug, "load: vm module: >>%s<<\n", path);
178+
179+ return new_module;
180+}
181+
182+runtime::vm_scheduler_control_t &vm_t::get_scheduler_control() const
183+{
184+ return _scheduler->get_controller();
185+}
186+
187+runtime::vm_garbage_collector_t &vm_t::get_garbage_collector() const
188+{
189+ return *_gc;
190+}
191+
192+void vm_t::spin_sleep_till_idle(std::chrono::milliseconds sleep_interval) const
193+{
194+ do
195+ std::this_thread::sleep_for(sleep_interval);
196+ while (!_scheduler->is_idle());
197+}
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+
13+using namespace disvm;
14+using namespace disvm::runtime;
15+
16+std::tuple<const handler_t *, vm_frame_t *, vm_pc_t> disvm::runtime::find_exception_handler(
17+ const vm_stack_t &stack,
18+ const vm_module_ref_t &faulting_module,
19+ const vm_pc_t faulting_pc,
20+ const vm_string_t &exception_id)
21+{
22+ auto unwind_depth = uint32_t{ 0 };
23+ auto current_module = &faulting_module;
24+ auto current_pc = faulting_pc;
25+ auto current_frame = stack.peek_frame();
26+
27+ while (current_frame != nullptr)
28+ {
29+ debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "begin: handler: %d\n", unwind_depth);
30+
31+ auto &handler_section = current_module->module->handler_section;
32+
33+ for (auto &handler : handler_section)
34+ {
35+ // Outside pc range of this handler
36+ if (current_pc < handler.begin_pc || handler.end_pc <= current_pc)
37+ continue;
38+
39+ // Find the appropriate exception case
40+ for (const auto &exception : handler.exception_table)
41+ {
42+ auto exception_name = exception.name.get();
43+ if (exception_id.compare_to(exception_name) == 0)
44+ {
45+ debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "end: handler: match: %d\n", unwind_depth);
46+ return std::tuple<const handler_t *, vm_frame_t *, std::size_t>{ &handler, current_frame, exception.pc };
47+ }
48+ else if (exception_name == nullptr && exception.pc != runtime_constants::invalid_program_counter)
49+ {
50+ debug::log_msg(debug::component_trace_t::exception, debug::log_level_t::debug, "end: handler: fallback: %d\n", unwind_depth);
51+ return std::tuple<const handler_t *, vm_frame_t *, std::size_t>{ &handler, current_frame, exception.pc };
52+ }
53+ }
54+ }
55+
56+ // Decrement the frame program counter. See raise instruction for details.
57+ current_pc = static_cast<word_t>(current_frame->prev_pc() - 1);
58+
59+ // Update the current module if it changes walking up the stack.
60+ auto prev_mod_ref = current_frame->prev_module_ref();
61+ if (prev_mod_ref != nullptr)
62+ current_module = prev_mod_ref;
63+
64+ // Move up the stack.
65+ current_frame = current_frame->prev_frame();
66+ ++unwind_depth;
67+ }
68+
69+ auto faulting_module_name = faulting_module.module->module_name->str();
70+ throw unhandled_user_exception{ exception_id.str(), faulting_module_name, faulting_pc };
71+}
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+
17+using namespace disvm;
18+using namespace disvm::runtime;
19+
20+namespace
21+{
22+ std::mutex global_lock;
23+ std::condition_variable release_lock;
24+
25+ enum class lock_state_t
26+ {
27+ none,
28+ pending,
29+ active
30+ };
31+
32+ std::atomic<uint32_t> allocating_thread_count;
33+ std::atomic<lock_state_t> lock_state;
34+
35+ struct safe_alloc final
36+ {
37+ safe_alloc()
38+ {
39+ allocating_thread_count++;
40+ if (lock_state != lock_state_t::none)
41+ {
42+ allocating_thread_count--;
43+
44+ std::unique_lock<std::mutex> lock{ global_lock };
45+ release_lock.wait(lock, [&]{ return lock_state == lock_state_t::none; });
46+
47+ allocating_thread_count++;
48+ }
49+ }
50+
51+ ~safe_alloc()
52+ {
53+ assert(allocating_thread_count > 0);
54+ allocating_thread_count--;
55+ }
56+ };
57+}
58+
59+global_alloc_lock_t::global_alloc_lock_t(bool owner)
60+ : _owner{ owner }
61+{ }
62+
63+global_alloc_lock_t::global_alloc_lock_t(global_alloc_lock_t &&other)
64+ : _owner{ other._owner }
65+{
66+ other._owner = false;
67+}
68+
69+global_alloc_lock_t::~global_alloc_lock_t()
70+{
71+ if (_owner)
72+ {
73+ lock_state = lock_state_t::none;
74+ release_lock.notify_all();
75+ }
76+}
77+
78+// Lock all allocations on the VM heap.
79+global_alloc_lock_t disvm::runtime::get_global_alloc_lock()
80+{
81+ auto expected = lock_state_t::none;
82+
83+ // Block while trying to get lock
84+ while (!lock_state.compare_exchange_strong(expected, lock_state_t::pending))
85+ {
86+ std::unique_lock<std::mutex> lock{ global_lock };
87+ release_lock.wait(lock);
88+ }
89+
90+ while (allocating_thread_count != 0)
91+ {
92+ // Spin-wait for all allocating threads to stop
93+ }
94+
95+ // Transition to active lock state
96+ lock_state = lock_state_t::active;
97+
98+ return{ true };
99+}
100+
101+void *disvm::runtime::alloc_memory(std::size_t amount_in_bytes)
102+{
103+ safe_alloc s;
104+
105+ auto memory = std::malloc(amount_in_bytes);
106+ if (memory == nullptr)
107+ throw vm_system_exception{ "Out of memory" };
108+
109+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
110+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "alloc: %#" PRIxPTR " %d\n", memory, amount_in_bytes);
111+
112+ return memory;
113+}
114+
115+void *disvm::runtime::calloc_memory(std::size_t amount_in_bytes)
116+{
117+ safe_alloc s;
118+
119+ auto memory = std::calloc(amount_in_bytes, sizeof(uint8_t));
120+ if (memory == nullptr)
121+ throw vm_system_exception{ "Out of memory" };
122+
123+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
124+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "alloc: zeroed: %#" PRIxPTR " %d\n", memory, amount_in_bytes);
125+
126+ return memory;
127+}
128+
129+void disvm::runtime::free_memory(void *memory)
130+{
131+ if (memory == nullptr)
132+ return;
133+
134+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
135+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "free: %#" PRIxPTR "\n", memory);
136+
137+ std::free(memory);
138+}
139+
140+void disvm::runtime::init_memory(const type_descriptor_t &type_desc, void *data)
141+{
142+ assert(data != nullptr);
143+ auto memory_word_len = std::size_t{ type_desc.size_in_bytes / sizeof(word_t) };
144+ if (memory_word_len == 0)
145+ return;
146+
147+ debug::assign_debug_memory(data, type_desc.size_in_bytes);
148+
149+ auto memory = reinterpret_cast<word_t *>(data);
150+ assert((memory_word_len / sizeof(word_t)) <= static_cast<std::size_t>(type_desc.size_in_bytes)); // type desc is in bytes convert len to bytes from words.
151+ for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8)
152+ {
153+ const auto words8 = type_desc.pointer_map[i];
154+ if (words8 != 0)
155+ {
156+ auto flags = std::bitset<sizeof(words8) * 8>{ words8 };
157+
158+ // Enumerating the flags in reverse order so memory access is linear.
159+ if (flags[7]) memory[0] = runtime_constants::nil;
160+ if (flags[6]) memory[1] = runtime_constants::nil;
161+ if (flags[5]) memory[2] = runtime_constants::nil;
162+ if (flags[4]) memory[3] = runtime_constants::nil;
163+ if (flags[3]) memory[4] = runtime_constants::nil;
164+ if (flags[2]) memory[5] = runtime_constants::nil;
165+ if (flags[1]) memory[6] = runtime_constants::nil;
166+ if (flags[0]) memory[7] = runtime_constants::nil;
167+ }
168+ }
169+}
170+
171+namespace
172+{
173+ void dec_ref_and_free_pointer_field(pointer_t pointer_field)
174+ {
175+ assert(pointer_field != nullptr);
176+ dec_ref_count_and_free(vm_alloc_t::from_allocation(pointer_field));
177+ }
178+
179+ void add_ref_pointer_field(pointer_t pointer_field)
180+ {
181+ assert(pointer_field != nullptr);
182+ vm_alloc_t::from_allocation(pointer_field)->add_ref();
183+ }
184+}
185+
186+void disvm::runtime::destroy_memory(const type_descriptor_t &type_desc, void *data)
187+{
188+ enum_pointer_fields(type_desc, data, dec_ref_and_free_pointer_field);
189+
190+ debug::assign_debug_memory(data, type_desc.size_in_bytes);
191+}
192+
193+void disvm::runtime::inc_ref_count_in_memory(const type_descriptor_t &type_desc, void *data)
194+{
195+ if (data == nullptr)
196+ return;
197+
198+ enum_pointer_fields(type_desc, data, add_ref_pointer_field);
199+}
200+
201+void disvm::runtime::dec_ref_count_and_free(vm_alloc_t *alloc)
202+{
203+ if (alloc == nullptr)
204+ return;
205+
206+ auto current_ref = alloc->release();
207+ if (current_ref == 0)
208+ delete alloc;
209+}
210+
211+const zero_memory_t vm_alloc_t::zero_memory = {};
212+
213+void *vm_alloc_t::operator new(std::size_t sz)
214+{
215+ return alloc_memory(sz);
216+}
217+
218+void vm_alloc_t::operator delete(void *ptr)
219+{
220+ free_memory(ptr);
221+}
222+
223+vm_alloc_t *vm_alloc_t::allocate(std::shared_ptr<const type_descriptor_t> td)
224+{
225+ assert(td != nullptr);
226+ if (td->size_in_bytes <= 0)
227+ throw vm_system_exception{ "Invalid dynamic memory allocation size" };
228+
229+ auto mem = alloc_memory(sizeof(vm_alloc_t) + td->size_in_bytes);
230+ auto alloc = ::new(mem)vm_alloc_t{ td };
231+
232+ // Initialize pointer types
233+ init_memory(*td, alloc->allocation());
234+
235+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm alloc: %d\n", td->size_in_bytes);
236+ return alloc;
237+}
238+
239+vm_alloc_t *vm_alloc_t::allocate(std::shared_ptr<const type_descriptor_t> td, const zero_memory_t &)
240+{
241+ assert(td != nullptr);
242+ if (td->size_in_bytes <= 0)
243+ throw vm_system_exception{ "Invalid dynamic memory allocation size" };
244+
245+ // No need to initialize memory since it is already zeroed out.
246+ auto mem = calloc_memory(sizeof(vm_alloc_t) + td->size_in_bytes);
247+ auto alloc = ::new(mem)vm_alloc_t{ td };
248+
249+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "initz: vm alloc: %d\n", td->size_in_bytes);
250+ return alloc;
251+}
252+
253+vm_alloc_t *vm_alloc_t::copy(const vm_alloc_t &other)
254+{
255+ auto alloc_copy = vm_alloc_t::allocate(other.alloc_type);
256+
257+ // Copy the current memory contents of the allocation
258+ std::memcpy(alloc_copy->allocation(), other.allocation(), alloc_copy->alloc_type->size_in_bytes);
259+
260+ // Ref count all dynamic allocations
261+ inc_ref_count_in_memory(*alloc_copy->alloc_type, alloc_copy->allocation());
262+
263+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "copy: vm alloc: %#" PRIxPTR " %#" PRIxPTR "\n", &other, alloc_copy);
264+ return alloc_copy;
265+}
266+
267+vm_alloc_t *vm_alloc_t::from_allocation(pointer_t allocation)
268+{
269+ if (allocation == nullptr)
270+ return nullptr;
271+
272+ return reinterpret_cast<vm_alloc_t *>(reinterpret_cast<uint8_t *>(allocation) - sizeof(vm_alloc_t));
273+}
274+
275+vm_alloc_t::vm_alloc_t(std::shared_ptr<const type_descriptor_t> td)
276+ : alloc_type{ td }
277+ , gc_reserved{ nullptr }
278+ , _ref_count{ 1 }
279+{
280+ assert(alloc_type != nullptr);
281+}
282+
283+vm_alloc_t::~vm_alloc_t()
284+{
285+ assert(_ref_count == 0);
286+
287+ auto finalizer = alloc_type->finalizer;
288+ if (finalizer != type_descriptor_t::no_finalizer)
289+ finalizer(this);
290+
291+ // Free pointer types
292+ destroy_memory(*alloc_type, allocation());
293+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm alloc\n");
294+}
295+
296+std::size_t vm_alloc_t::add_ref()
297+{
298+ assert(_ref_count > 0);
299+
300+ auto prev_value = _ref_count.fetch_add(1, std::memory_order::memory_order_relaxed);
301+ return (prev_value + 1);
302+}
303+
304+std::size_t vm_alloc_t::release()
305+{
306+ assert(_ref_count > 0);
307+
308+ auto prev_value = _ref_count.fetch_sub(1, std::memory_order::memory_order_relaxed);
309+ return (prev_value - 1);
310+}
311+
312+std::size_t vm_alloc_t::get_ref_count() const
313+{
314+ return _ref_count;
315+}
316+
317+pointer_t vm_alloc_t::allocation() const
318+{
319+ // Remove const on the this pointer so the offset can be computed.
320+ auto non_const_this = const_cast<vm_alloc_t *>(this);
321+ return reinterpret_cast<pointer_t>(reinterpret_cast<uint8_t *>(non_const_this)+sizeof(vm_alloc_t));
322+}
323+
324+namespace
325+{
326+ namespace hidden_type_desc
327+ {
328+ std::shared_ptr<const type_descriptor_t> byte;
329+ std::shared_ptr<const type_descriptor_t> short_word;
330+ std::shared_ptr<const type_descriptor_t> word;
331+ std::shared_ptr<const type_descriptor_t> short_real;
332+ std::shared_ptr<const type_descriptor_t> real;
333+ std::shared_ptr<const type_descriptor_t> big;
334+ std::shared_ptr<const type_descriptor_t> pointer;
335+
336+ std::shared_ptr<const type_descriptor_t> vm_array;
337+ std::shared_ptr<const type_descriptor_t> vm_list;
338+ std::shared_ptr<const type_descriptor_t> vm_channel;
339+ std::shared_ptr<const type_descriptor_t> vm_string;
340+
341+ std::shared_ptr<const type_descriptor_t> vm_module_ref;
342+ std::shared_ptr<const type_descriptor_t> vm_stack;
343+ std::shared_ptr<const type_descriptor_t> vm_thread;
344+ }
345+}
346+
347+std::atomic_bool intrinsic_type_desc::is_initialized;
348+
349+void intrinsic_type_desc::initialize()
350+{
351+ auto is_false = bool{ false };
352+ if (!intrinsic_type_desc::is_initialized.compare_exchange_strong(is_false, true))
353+ return;
354+
355+ hidden_type_desc::byte = type_descriptor_t::create(sizeof(byte_t));
356+ hidden_type_desc::short_word = type_descriptor_t::create(sizeof(short_word_t));
357+ hidden_type_desc::word = type_descriptor_t::create(sizeof(word_t));
358+ hidden_type_desc::short_real = type_descriptor_t::create(sizeof(short_real_t));
359+ hidden_type_desc::real = type_descriptor_t::create(sizeof(real_t));
360+ hidden_type_desc::big = type_descriptor_t::create(sizeof(big_t));
361+ hidden_type_desc::pointer = type_descriptor_t::create(sizeof(pointer_t), std::vector<byte_t>{0x80});
362+
363+ hidden_type_desc::vm_array = type_descriptor_t::create(0);
364+ hidden_type_desc::vm_list = type_descriptor_t::create(0);
365+ hidden_type_desc::vm_channel = type_descriptor_t::create(0);
366+ hidden_type_desc::vm_string = type_descriptor_t::create(0);
367+
368+ hidden_type_desc::vm_module_ref = type_descriptor_t::create(0);
369+ hidden_type_desc::vm_stack = type_descriptor_t::create(0);
370+ hidden_type_desc::vm_thread = type_descriptor_t::create(0);
371+}
372+
373+template<>
374+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<byte_t>() { return hidden_type_desc::byte; }
375+
376+template<>
377+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<short_word_t>() { return hidden_type_desc::short_word; }
378+
379+template<>
380+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<word_t>() { return hidden_type_desc::word; }
381+
382+template<>
383+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<short_real_t>() { return hidden_type_desc::short_real; }
384+
385+template<>
386+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<real_t>() { return hidden_type_desc::real; }
387+
388+template<>
389+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<big_t>() { return hidden_type_desc::big; }
390+
391+template<>
392+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<pointer_t>() { return hidden_type_desc::pointer; }
393+
394+template<>
395+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_array_t>() { return hidden_type_desc::vm_array; }
396+
397+template<>
398+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_list_t>() { return hidden_type_desc::vm_list; }
399+
400+template<>
401+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_channel_t>() { return hidden_type_desc::vm_channel; }
402+
403+template<>
404+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_string_t>() { return hidden_type_desc::vm_string; }
405+
406+template<>
407+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_module_ref_t>() { return hidden_type_desc::vm_module_ref; }
408+
409+template<>
410+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_stack_t>() { return hidden_type_desc::vm_stack; }
411+
412+template<>
413+static std::shared_ptr<const type_descriptor_t> intrinsic_type_desc::type<vm_thread_t>() { return hidden_type_desc::vm_thread; }
414+
415+vm_alloc_instance_finalizer_t type_descriptor_t::no_finalizer = nullptr;
416+
417+std::shared_ptr<const type_descriptor_t> type_descriptor_t::create(const word_t size)
418+{
419+ return type_descriptor_t::create(size, 0, nullptr);
420+}
421+
422+std::shared_ptr<const type_descriptor_t> type_descriptor_t::create(const word_t size, const std::vector<byte_t> &pointer_map)
423+{
424+ assert(pointer_map.size() < static_cast<std::size_t>(std::numeric_limits<word_t>::max()));
425+ return type_descriptor_t::create(size, static_cast<word_t>(pointer_map.size()), pointer_map.data());
426+}
427+
428+std::shared_ptr<const type_descriptor_t> type_descriptor_t::create(
429+ const word_t size,
430+ const word_t pointer_map_length,
431+ const byte_t *pointer_map,
432+ const vm_alloc_instance_finalizer_t finalizer)
433+{
434+ struct
435+ {
436+ void operator()(type_descriptor_t *td)
437+ {
438+ free_memory(const_cast<byte_t *>(td->pointer_map));
439+ debug::assign_debug_pointer(const_cast<byte_t **>(&td->pointer_map));
440+
441+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: type descriptor\n");
442+ free_memory(td);
443+ }
444+ } deleter;
445+
446+ auto new_type_memory = alloc_memory(sizeof(type_descriptor_t));
447+
448+ byte_t *pointer_map_local = nullptr;
449+ if (pointer_map_length > 0)
450+ {
451+ pointer_map_local = alloc_memory<byte_t>(pointer_map_length);
452+ for (auto i = word_t{ 0 }; i < pointer_map_length; ++i)
453+ pointer_map_local[i] = pointer_map[i];
454+ }
455+
456+ auto new_type = ::new(new_type_memory) type_descriptor_t{ size, pointer_map_length, pointer_map_local, finalizer };
457+ return std::shared_ptr<type_descriptor_t>{ new_type, deleter };
458+}
459+
460+type_descriptor_t::type_descriptor_t(
461+ word_t size_in_bytes,
462+ word_t map_in_bytes,
463+ const byte_t * pointer_map,
464+ vm_alloc_instance_finalizer_t finalizer)
465+ : size_in_bytes{ size_in_bytes }
466+ , map_in_bytes{ map_in_bytes }
467+ , pointer_map{ pointer_map }
468+ , finalizer{ finalizer }
469+{
470+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: type descriptor\n");
471+}