Showing the changes to src/include/runtime.h (1 of 40 files). Show all
Unified · Split
| @@ -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_ | ||