| @@ -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_ |