Remove global memory lock mechanism - Wasn't being used but showed up on profile report :( Move enum_pointer_fields and is_offset_pointer impls into cpp file Create enum_pointer_fields_with_offset function since the VM never needs the offset values and the generated code was about 5% bigger. Now the VM can use the callback without the offset. Removed pointless init_memory() usage since alloc_memory() now is required to return zeroed out memory. Fixed bugs in GC Added support - Ranged absolute disassembly in debugger - New duration log component
Unified · Split
| @@ -311,7 +311,7 @@ namespace · 310 unmodified lines | ||
| 311 | 311 | { |
| 312 | 312 | ss << alloc->alloc_type << " - alloc" << dbg_alloc; |
| 313 | 313 | |
| 314 | − enum_pointer_fields(*alloc->alloc_type, alloc->get_allocation(), [&ss, recurse_depth](pointer_t p, std::size_t o) | |
| 314 | + enum_pointer_fields_with_offset(*alloc->alloc_type, alloc->get_allocation(), [&ss, recurse_depth](pointer_t p, std::size_t o) | |
| 315 | 315 | { |
| 316 | 316 | ss << "\n"; |
| 317 | 317 | std::fill_n(std::ostream_iterator<std::ostream::char_type>{ ss }, 4 * recurse_depth, ' '); |
| @@ -671,11 +671,15 @@ namespace · 353 unmodified lines | ||
| 671 | 671 | |
| 672 | 672 | void cmd_disassemble(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) |
| 673 | 673 | { |
| 674 | − auto absolute_pc = false; | |
| 675 | − auto disassemble_length = int{ 4 }; | |
| 676 | − if (a.size() > 1) | |
| 674 | + const auto &r = *cxt.current_register; | |
| 675 | + auto begin_pc = r.pc; | |
| 676 | + | |
| 677 | + const auto default_disassemble_length = int{ 4 }; | |
| 678 | + auto disassemble_length = default_disassemble_length; | |
| 679 | + for (auto i = std::size_t{ 1 }; i < a.size(); ++i) | |
| 677 | 680 | { |
| 678 | − auto disassemble_length_maybe = a[1]; | |
| 681 | + auto absolute_pc = false; | |
| 682 | + auto disassemble_length_maybe = a[i]; | |
| 679 | 683 | if (disassemble_length_maybe[0] == '@') |
| 680 | 684 | { |
| 681 | 685 | absolute_pc = true; |
| @@ -690,24 +694,22 @@ namespace · 8 unmodified lines | ||
| 690 | 694 | { |
| 691 | 695 | throw debug_cmd_error_t{ "Invalid disassemble count format" }; |
| 692 | 696 | } |
| 697 | + | |
| 698 | + if (absolute_pc) | |
| 699 | + { | |
| 700 | + begin_pc = disassemble_length; | |
| 701 | + disassemble_length = default_disassemble_length; | |
| 702 | + } | |
| 693 | 703 | } |
| 694 | 704 | |
| 705 | + auto end_pc = begin_pc; | |
| 695 | 706 | auto &dbg = cxt.dbg; |
| 696 | − const auto &r = *cxt.current_register; | |
| 697 | 707 | const auto &code_section = r.module_ref->code_section; |
| 698 | 708 | |
| 699 | − auto begin_pc = r.pc; | |
| 700 | − auto end_pc = begin_pc; | |
| 701 | − if (absolute_pc) | |
| 702 | − { | |
| 703 | − begin_pc = disassemble_length; | |
| 704 | − end_pc = disassemble_length; | |
| 705 | − disassemble_length = 0; | |
| 709 | + if (begin_pc < 0 || static_cast<int32_t>(code_section.size()) <= end_pc) | |
| 710 | + throw debug_cmd_error_t{ "Invalid absolute pc" }; | |
| 706 | 711 | |
| 707 | − if (begin_pc < 0 || static_cast<int32_t>(code_section.size()) <= end_pc) | |
| 708 | − throw debug_cmd_error_t{ "Invalid absolute pc" }; | |
| 709 | − } | |
| 710 | − else if (disassemble_length < 0) | |
| 712 | + if (disassemble_length < 0) | |
| 711 | 713 | { |
| 712 | 714 | const auto begin_maybe = begin_pc - (-disassemble_length); |
| 713 | 715 | begin_pc = std::max(begin_maybe, 0); |
| @@ -970,7 +972,7 @@ namespace · 256 unmodified lines | ||
| 970 | 972 | |
| 971 | 973 | { "sw", "Stack walk for the current or supplied thread", "sw ([0-9]+|\\*)?", "sw, sw 34, sw *", cmd_stackwalk }, |
| 972 | 974 | { "~", "Switch to thread", "~ [0-9]+", "~ 42", cmd_switchthread }, |
| 973 | − { "d", "Disassemble the next/previous N instructions (default N = 4)", "d (@?-?[0-9]+)?", "d, d -4, d 5, d @36", cmd_disassemble }, | |
| 975 | + { "d", "Disassemble the next/previous N instructions (default N = 4)", "d (@?-?[0-9]+)? (-?[0-9]+)?", "d, d -4, d 5, d @36 3", cmd_disassemble }, | |
| 974 | 976 | { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine }, |
| 975 | 977 | |
| 976 | 978 | { "set" , "Set a debug option", "set [ _a-zA-Z0-9]*", "'set' for available options", set_value_cmd }, |
| @@ -240,6 +240,7 @@ void print_help() · 239 unmodified lines | ||
| 240 | 240 | " t - Threads\n" |
| 241 | 241 | " T - Tool extensions\n" |
| 242 | 242 | " e - Exceptions\n" |
| 243 | + " d - Duration of actions\n" | |
| 243 | 244 | " g - Garbage collector (noisy)\n" |
| 244 | 245 | " m - Memory allocations (noisy)\n" |
| 245 | 246 | " q - Suppress banner and configuration\n" |
| @@ -341,6 +342,8 @@ void process_arg(char* arg, std::function<char *()> next, exec_options &options) · 95 unmodified lines | ||
| 341 | 342 | break; |
| 342 | 343 | case 'm': debug::set_component_tracing(debug::component_trace_t::memory, true); |
| 343 | 344 | break; |
| 345 | + case 'd': debug::set_component_tracing(debug::component_trace_t::duration, true); | |
| 346 | + break; | |
| 344 | 347 | } |
| 345 | 348 | } |
| 346 | 349 | break; |
| @@ -31,9 +31,10 @@ namespace disvm · 30 unmodified lines | ||
| 31 | 31 | // Potentially logged from a VM extension. |
| 32 | 32 | scheduler, |
| 33 | 33 | garbage_collector, |
| 34 | + duration, | |
| 34 | 35 | |
| 35 | 36 | // Tracking purposes |
| 36 | − last = garbage_collector | |
| 37 | + last = duration | |
| 37 | 38 | }; |
| 38 | 39 | |
| 39 | 40 | // Trace component enable state. |
| @@ -104,7 +104,7 @@ namespace disvm · 103 unmodified lines | ||
| 104 | 104 | static T *from_allocation(pointer_t allocation) |
| 105 | 105 | { |
| 106 | 106 | auto alloc_inst = vm_alloc_t::from_allocation(allocation); |
| 107 | − assert(alloc_inst == nullptr || alloc_inst->alloc_type == intrinsic_type_desc::type<T>()); | |
| 107 | + assert(alloc_inst == nullptr || alloc_inst->alloc_type->is_equal(intrinsic_type_desc::type<T>().get())); | |
| 108 | 108 | return static_cast<T *>(alloc_inst); |
| 109 | 109 | } |
| 110 | 110 | |
| @@ -875,7 +875,7 @@ namespace disvm · 764 unmodified lines | ||
| 875 | 875 | // Called by the scheduler to inform the collector it should attempt to free resources. |
| 876 | 876 | // All VM threads are guaranteed to be idle when called. |
| 877 | 877 | // Returns true if the collection algorithm was run, otherwise false. |
| 878 | − virtual bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) = 0; | |
| 878 | + virtual bool collect(std::vector<std::shared_ptr<const vm_thread_t>> threads) = 0; | |
| 879 | 879 | }; |
| 880 | 880 | |
| 881 | 881 | // Create a garbage collector that does nothing. |
| @@ -20,26 +20,6 @@ namespace disvm · 19 unmodified lines | ||
| 20 | 20 | { |
| 21 | 21 | namespace runtime |
| 22 | 22 | { |
| 23 | − class global_alloc_lock_t final | |
| 24 | − { | |
| 25 | − public: | |
| 26 | − global_alloc_lock_t(const global_alloc_lock_t &) = delete; | |
| 27 | − global_alloc_lock_t& operator=(const global_alloc_lock_t &) = delete; | |
| 28 | − | |
| 29 | − global_alloc_lock_t(global_alloc_lock_t &&); | |
| 30 | − | |
| 31 | − ~global_alloc_lock_t(); | |
| 32 | − | |
| 33 | − private: | |
| 34 | − bool _owner; | |
| 35 | − global_alloc_lock_t(bool owner); | |
| 36 | − friend global_alloc_lock_t get_global_alloc_lock(); | |
| 37 | − }; | |
| 38 | − | |
| 39 | − // Take the global allocation lock for the VM heap. | |
| 40 | − // While this lock is taken all further allocations will block. | |
| 41 | − global_alloc_lock_t get_global_alloc_lock(); | |
| 42 | − | |
| 43 | 23 | // Allocate memory from the VM heap that is initialize to 0. |
| 44 | 24 | // [SPEC] All memory in this implementation of the Dis VM spec |
| 45 | 25 | // will be 0 initialized. |
| @@ -69,63 +49,20 @@ namespace disvm · 23 unmodified lines | ||
| 69 | 49 | // Decrement the ref count and if 0 free the allocation |
| 70 | 50 | void dec_ref_count_and_free(vm_alloc_t *alloc); |
| 71 | 51 | |
| 52 | + // Callback for a pointer field supplying the pointer | |
| 53 | + using pointer_field_callback_t = std::function<void(pointer_t)>; | |
| 54 | + | |
| 55 | + // The callback will be called on all non-null pointer fields. | |
| 56 | + void enum_pointer_fields(const type_descriptor_t &type_desc, void *data, pointer_field_callback_t callback); | |
| 57 | + | |
| 72 | 58 | // Callback for a pointer field supplying the pointer and byte offset |
| 73 | − using pointer_field_callback_t = std::function<void(pointer_t, std::size_t)>; | |
| 59 | + using pointer_field_offset_callback_t = std::function<void(pointer_t, std::size_t)>; | |
| 74 | 60 | |
| 75 | 61 | // The callback will be called on all non-null pointer fields. |
| 76 | − inline void enum_pointer_fields(const type_descriptor_t &type_desc, void *data, pointer_field_callback_t callback) | |
| 77 | − { | |
| 78 | − assert(data != nullptr && callback != nullptr); | |
| 79 | − if (type_desc.size_in_bytes == 0) | |
| 80 | − return; | |
| 81 | − | |
| 82 | − auto offset_accum = std::size_t{ 0 }; | |
| 83 | − auto memory = reinterpret_cast<word_t *>(data); | |
| 84 | − for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8, offset_accum += (8 * sizeof(pointer_t))) | |
| 85 | − { | |
| 86 | − const auto words8 = type_desc.pointer_map[i]; | |
| 87 | − assert(sizeof(words8) == 1); | |
| 88 | − if (words8 == 0) | |
| 89 | − continue; | |
| 90 | − | |
| 91 | − const auto flags = std::bitset<sizeof(words8) * 8>{ words8 }; | |
| 92 | − | |
| 93 | − // Enumerating the flags in reverse order so memory access is sequential. | |
| 94 | − if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0]), offset_accum); | |
| 95 | − if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1]), offset_accum + (1 * sizeof(pointer_t))); | |
| 96 | − if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2]), offset_accum + (2 * sizeof(pointer_t))); | |
| 97 | − if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3]), offset_accum + (3 * sizeof(pointer_t))); | |
| 98 | − if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4]), offset_accum + (4 * sizeof(pointer_t))); | |
| 99 | − if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5]), offset_accum + (5 * sizeof(pointer_t))); | |
| 100 | − if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6]), offset_accum + (6 * sizeof(pointer_t))); | |
| 101 | − if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7]), offset_accum + (7 * sizeof(pointer_t))); | |
| 102 | − } | |
| 103 | − } | |
| 62 | + void enum_pointer_fields_with_offset(const type_descriptor_t &type_desc, void *data, pointer_field_offset_callback_t callback); | |
| 104 | 63 | |
| 105 | 64 | // Check if a specific pointer-sized offset in a type represents a pointer |
| 106 | − inline bool is_offset_pointer(const type_descriptor_t &type_desc, std::size_t offset) | |
| 107 | − { | |
| 108 | − if (type_desc.size_in_bytes == 0) | |
| 109 | − return false; | |
| 110 | − | |
| 111 | − assert(offset < static_cast<std::size_t>(std::numeric_limits<word_t>::max())); | |
| 112 | − const auto byte_offset = static_cast<word_t>(offset / 8); | |
| 113 | − | |
| 114 | − // Highest order bit is the first field | |
| 115 | − const auto bit_offset = 7 - (offset % 8); | |
| 116 | − if (byte_offset < type_desc.map_in_bytes) | |
| 117 | − { | |
| 118 | − const auto words8 = type_desc.pointer_map[byte_offset]; | |
| 119 | − assert(sizeof(words8) == 1); | |
| 120 | − if (words8 != 0) | |
| 121 | − { | |
| 122 | − const auto flags = std::bitset<sizeof(words8) * 8>{ words8 }; | |
| 123 | − return flags[bit_offset]; | |
| 124 | − } | |
| 125 | − } | |
| 126 | − | |
| 127 | − return false; | |
| 128 | − } | |
| 65 | + bool is_offset_pointer(const type_descriptor_t &type_desc, std::size_t offset); | |
| 129 | 66 | } |
| 130 | 67 | } |
| 131 | 68 | |
| @@ -29,18 +29,7 @@ vm_array_t::vm_array_t(std::shared_ptr<const type_descriptor_t> td, word_t lengt · 28 unmodified lines | ||
| 29 | 29 | _arr = alloc_memory<byte_t>(_length * _element_type->size_in_bytes); |
| 30 | 30 | debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: %d", _length); |
| 31 | 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", 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", array_len); | |
| 32 | + // [SPEC] Element memory is already initialized to zero based on the alloc_memory contract | |
| 44 | 33 | } |
| 45 | 34 | |
| 46 | 35 | vm_array_t::vm_array_t(vm_array_t &original, word_t begin_index, word_t length) |
| @@ -206,8 +206,7 @@ namespace · 205 unmodified lines | ||
| 206 | 206 | assert(len <= buffer.size()); |
| 207 | 207 | |
| 208 | 208 | auto new_string = new vm_string_t{ len, reinterpret_cast<uint8_t *>(buffer.data()) }; |
| 209 | − auto prev = at_val<vm_alloc_t>(r.dest); | |
| 210 | − dec_ref_count_and_free(prev); | |
| 209 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 211 | 210 | |
| 212 | 211 | pt_ref(r.dest) = new_string->get_allocation(); |
| 213 | 212 | } |
| @@ -232,9 +231,7 @@ namespace · 18 unmodified lines | ||
| 232 | 231 | assert(len <= buffer.size()); |
| 233 | 232 | |
| 234 | 233 | auto new_string = new vm_string_t{ len, reinterpret_cast<uint8_t *>(buffer.data()) }; |
| 235 | − | |
| 236 | − auto prev = at_val<vm_alloc_t>(r.dest); | |
| 237 | − dec_ref_count_and_free(prev); | |
| 234 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 238 | 235 | |
| 239 | 236 | pt_ref(r.dest) = new_string->get_allocation(); |
| 240 | 237 | } |
| @@ -259,9 +256,7 @@ namespace · 18 unmodified lines | ||
| 259 | 256 | assert(len <= buffer.size()); |
| 260 | 257 | |
| 261 | 258 | auto new_string = new vm_string_t{ len, reinterpret_cast<uint8_t *>(buffer.data()) }; |
| 262 | − | |
| 263 | − auto prev = at_val<vm_alloc_t>(r.dest); | |
| 264 | − dec_ref_count_and_free(prev); | |
| 259 | + dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 265 | 260 | |
| 266 | 261 | pt_ref(r.dest) = new_string->get_allocation(); |
| 267 | 262 | } |
| @@ -513,8 +508,6 @@ namespace · 245 unmodified lines | ||
| 513 | 508 | void _mov_<vm_alloc_t>(pointer_t dest, pointer_t src, const type_descriptor_t *) |
| 514 | 509 | { |
| 515 | 510 | auto alloc_prev = at_val<vm_alloc_t>(dest); |
| 516 | − dec_ref_count_and_free(alloc_prev); | |
| 517 | − | |
| 518 | 511 | auto alloc = at_val<vm_alloc_t>(src); |
| 519 | 512 | if (alloc != nullptr) |
| 520 | 513 | { |
| @@ -525,6 +518,8 @@ namespace · 4 unmodified lines | ||
| 525 | 518 | { |
| 526 | 519 | pt_ref(dest) = nullptr; |
| 527 | 520 | } |
| 521 | + | |
| 522 | + dec_ref_count_and_free(alloc_prev); | |
| 528 | 523 | } |
| 529 | 524 | |
| 530 | 525 | EXEC_DECL(movb) { _mov_<byte_t>(r.dest, r.src, nullptr); } |
| @@ -1729,8 +1724,7 @@ namespace · 1198 unmodified lines | ||
| 1729 | 1724 | auto exception_destination = reinterpret_cast<pointer_t>(frame_pointer + handler->exception_offset); |
| 1730 | 1725 | |
| 1731 | 1726 | // Destroy any data at this location. |
| 1732 | − auto current_value = at_val<vm_alloc_t>(exception_destination); | |
| 1733 | − dec_ref_count_and_free(current_value); | |
| 1727 | + dec_ref_count_and_free(at_val<vm_alloc_t>(exception_destination)); | |
| 1734 | 1728 | |
| 1735 | 1729 | // Set the exception destination. |
| 1736 | 1730 | pt_ref(exception_destination) = e->get_allocation(); |
| @@ -33,7 +33,7 @@ std::unique_ptr<vm_garbage_collector_t> disvm::runtime::create_no_op_gc(vm_t &) · 32 unmodified lines | ||
| 33 | 33 | { |
| 34 | 34 | } |
| 35 | 35 | |
| 36 | − bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &) override | |
| 36 | + bool collect(std::vector<std::shared_ptr<const vm_thread_t>>) override | |
| 37 | 37 | { |
| 38 | 38 | return true; |
| 39 | 39 | } |
| @@ -67,17 +67,17 @@ namespace · 27 unmodified lines | ||
| 67 | 67 | const auto current_colours = std::array<gc_colour_t, 3>{ gc_colour_t::white, gc_colour_t::grey, gc_colour_t::black }; |
| 68 | 68 | const auto sweeper_colours = std::array<gc_colour_t, 3>{ gc_colour_t::grey, gc_colour_t::black, gc_colour_t::white }; |
| 69 | 69 | |
| 70 | − gc_colour_t get_current_colour(const std::size_t epoch) | |
| 70 | + constexpr gc_colour_t get_current_colour(const std::size_t epoch) | |
| 71 | 71 | { |
| 72 | 72 | return current_colours[epoch % current_colours.size()]; |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | − gc_colour_t get_sweeper_colour(const std::size_t epoch) | |
| 75 | + constexpr gc_colour_t get_sweeper_colour(const std::size_t epoch) | |
| 76 | 76 | { |
| 77 | 77 | return sweeper_colours[epoch % sweeper_colours.size()]; |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | − gc_colour_t get_gc_colour(const vm_alloc_t *a) | |
| 80 | + constexpr gc_colour_t get_gc_colour(const vm_alloc_t *a) | |
| 81 | 81 | { |
| 82 | 82 | return *reinterpret_cast<const gc_colour_t *>(&a->gc_reserved); |
| 83 | 83 | } |
| @@ -118,16 +118,21 @@ namespace · 34 unmodified lines | ||
| 118 | 118 | { |
| 119 | 119 | std::queue<vm_alloc_t *> allocs; |
| 120 | 120 | |
| 121 | − auto add_pointer_to_queue = std::function<void(pointer_t p, std::size_t)>{ [&allocs](pointer_t p, std::size_t) -> void | |
| 121 | + // Making this raw since it is a built-in type and has unlimited lifetime. | |
| 122 | + const auto string_type_raw = intrinsic_type_desc::type<vm_string_t>().get(); | |
| 123 | + auto add_pointer_to_queue = std::function<void(pointer_t p)>{ [&allocs, string_type_raw, curr_colour](pointer_t p) -> void | |
| 122 | 124 | { |
| 123 | 125 | auto a = vm_alloc_t::from_allocation(p); |
| 124 | − allocs.push(a); | |
| 126 | + | |
| 127 | + // Don't examine vm strings or allocations with the current colour | |
| 128 | + if (curr_colour != get_gc_colour(a) && a->alloc_type.get() != string_type_raw) | |
| 129 | + allocs.push(a); | |
| 125 | 130 | } }; |
| 126 | 131 | |
| 127 | 132 | // Get roots from threads |
| 128 | − for (const auto &t : threads) | |
| 133 | + for (auto &t : threads) | |
| 129 | 134 | { |
| 130 | − const auto &r = t->get_registers(); | |
| 135 | + auto &r = t->get_registers(); | |
| 131 | 136 | |
| 132 | 137 | // Current MP |
| 133 | 138 | allocs.push(r.mp_base); |
| @@ -142,27 +147,15 @@ namespace · 8 unmodified lines | ||
| 142 | 147 | allocs.push(prev_mp); |
| 143 | 148 | } |
| 144 | 149 | |
| 145 | − if (frame->frame_type->map_in_bytes == 0) | |
| 146 | − continue; | |
| 147 | − | |
| 148 | − enum_pointer_fields(*frame->frame_type, frame->base(), add_pointer_to_queue); | |
| 150 | + if (frame->frame_type->map_in_bytes > 0) | |
| 151 | + enum_pointer_fields(*frame->frame_type, frame->base(), add_pointer_to_queue); | |
| 149 | 152 | } |
| 150 | 153 | } |
| 151 | 154 | |
| 152 | 155 | const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>(); |
| 153 | 156 | if (log_enabled) |
| 154 | − { | |
| 155 | 157 | debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: roots found: %" PRIuPTR, allocs.size()); |
| 156 | 158 | |
| 157 | − // If logging is enabled update the callback with logging | |
| 158 | − add_pointer_to_queue = std::function<void(pointer_t p, std::size_t)>{ [&allocs](pointer_t p, std::size_t) -> void | |
| 159 | − { | |
| 160 | − auto a = vm_alloc_t::from_allocation(p); | |
| 161 | − debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, " ref: %#" PRIxPTR, a); | |
| 162 | − allocs.push(a); | |
| 163 | − } }; | |
| 164 | − } | |
| 165 | − | |
| 166 | 159 | // Traverse the graph |
| 167 | 160 | while (!allocs.empty()) |
| 168 | 161 | { |
| @@ -179,41 +172,41 @@ namespace · 10 unmodified lines | ||
| 179 | 172 | } |
| 180 | 173 | } |
| 181 | 174 | |
| 182 | − void sweep(std::deque<vm_alloc_t *> &allocs, const gc_colour_t sweeper_colour) | |
| 175 | + void sweep(std::deque<vm_alloc_t *> &tracking_allocs, const gc_colour_t sweeper_colour) | |
| 183 | 176 | { |
| 184 | − auto allocs_local = std::move(allocs); | |
| 177 | + auto allocs_local = std::move(tracking_allocs); | |
| 185 | 178 | |
| 186 | − auto freed = uint32_t{ 0 }; | |
| 179 | + const auto starting_size = allocs_local.size(); | |
| 187 | 180 | for (auto a : allocs_local) |
| 188 | 181 | { |
| 189 | 182 | const auto colour = get_gc_colour(a); |
| 190 | 183 | if (colour == sweeper_colour) |
| 191 | − { | |
| 192 | 184 | dec_ref_count_and_free(a); |
| 193 | − freed++; | |
| 194 | − continue; | |
| 195 | − } | |
| 196 | − | |
| 197 | − allocs.push_back(a); | |
| 185 | + else | |
| 186 | + tracking_allocs.push_back(a); | |
| 198 | 187 | } |
| 199 | 188 | |
| 200 | 189 | if (debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>()) |
| 201 | − debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: sweep: %u", freed); | |
| 190 | + debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: sweep: %u", starting_size - tracking_allocs.size()); | |
| 202 | 191 | } |
| 203 | 192 | } |
| 204 | 193 | |
| 205 | −bool default_garbage_collector_t::collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) | |
| 194 | +bool default_garbage_collector_t::collect(std::vector<std::shared_ptr<const vm_thread_t>> threads) | |
| 206 | 195 | { |
| 207 | 196 | // No need to lock the actual memory allocator since the contract |
| 208 | 197 | // for this function call indicates all VM threads should be blocked. |
| 209 | 198 | std::lock_guard<std::mutex> lock{ _tracking_allocs_lock }; |
| 210 | 199 | |
| 211 | − if (threads.empty() || _tracking_allocs.empty()) | |
| 200 | + if (_tracking_allocs.empty()) | |
| 212 | 201 | return false; |
| 213 | 202 | |
| 214 | − const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>(); | |
| 203 | + std::chrono::high_resolution_clock::time_point start; | |
| 204 | + const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::duration>(); | |
| 215 | 205 | if (log_enabled) |
| 216 | − debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: begin: collect"); | |
| 206 | + { | |
| 207 | + start = std::chrono::high_resolution_clock::now(); | |
| 208 | + debug::log_msg(debug::component_trace_t::duration, debug::log_level_t::debug, "gc: begin: collect"); | |
| 209 | + } | |
| 217 | 210 | |
| 218 | 211 | const auto curr_colour = get_current_colour(_collection_epoch); |
| 219 | 212 | mark(threads, curr_colour); |
| @@ -222,7 +215,10 @@ bool default_garbage_collector_t::collect(std::vector<std::shared_ptr<const vm_t · 2 unmodified lines | ||
| 222 | 215 | sweep(_tracking_allocs, sweeper_colour); |
| 223 | 216 | |
| 224 | 217 | if (log_enabled) |
| 225 | − debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: end: collect"); | |
| 218 | + { | |
| 219 | + const auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start); | |
| 220 | + debug::log_msg(debug::component_trace_t::duration, debug::log_level_t::debug, "gc: end: collect: %lld us", elapsed.count()); | |
| 221 | + } | |
| 226 | 222 | |
| 227 | 223 | ++_collection_epoch; |
| 228 | 224 | return true; |
| @@ -30,7 +30,7 @@ namespace disvm · 29 unmodified lines | ||
| 30 | 30 | |
| 31 | 31 | void enum_tracked_allocations(vm_alloc_callback_t callback) const override; |
| 32 | 32 | |
| 33 | − bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads) override; | |
| 33 | + bool collect(std::vector<std::shared_ptr<const vm_thread_t>> threads) override; | |
| 34 | 34 | |
| 35 | 35 | private: |
| 36 | 36 | vm_t &_vm; |
| @@ -28,15 +28,9 @@ vm_list_t::vm_list_t(std::shared_ptr<const type_descriptor_t> td, vm_list_t *tai · 27 unmodified lines | ||
| 28 | 28 | set_tail(tail); |
| 29 | 29 | |
| 30 | 30 | // If the default list element storage is not enough, allocate more memory. |
| 31 | + // [SPEC] Element memory is already initialized to zero based on the alloc_memory contract | |
| 31 | 32 | if (_is_alloc) |
| 32 | − { | |
| 33 | 33 | _mem.alloc = static_cast<pointer_t>(alloc_memory(_element_type->size_in_bytes)); |
| 34 | − init_memory(*_element_type, _mem.alloc); | |
| 35 | − } | |
| 36 | − else | |
| 37 | − { | |
| 38 | − init_memory(*_element_type, &_mem.local); | |
| 39 | − } | |
| 40 | 34 | |
| 41 | 35 | if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) |
| 42 | 36 | debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm list"); |
| @@ -371,7 +371,7 @@ void default_scheduler_t::perform_gc(bool is_gc_thread, std::unique_lock<std::mu · 370 unmodified lines | ||
| 371 | 371 | result.push_back(vm_thread); |
| 372 | 372 | } |
| 373 | 373 | |
| 374 | − _vm.get_garbage_collector().collect(result); | |
| 374 | + _vm.get_garbage_collector().collect(std::move(result)); | |
| 375 | 375 | _gc_complete = true; |
| 376 | 376 | _gc_done_event.notify_all(); |
| 377 | 377 | } |
| @@ -20,92 +20,8 @@ · 19 unmodified lines | ||
| 20 | 20 | using namespace disvm; |
| 21 | 21 | using namespace disvm::runtime; |
| 22 | 22 | |
| 23 | −namespace | |
| 24 | −{ | |
| 25 | − std::mutex global_lock; | |
| 26 | − std::condition_variable release_lock; | |
| 27 | − | |
| 28 | − enum class lock_state_t | |
| 29 | − { | |
| 30 | − none, | |
| 31 | − pending, | |
| 32 | − active | |
| 33 | − }; | |
| 34 | − | |
| 35 | − std::atomic<uint32_t> allocating_thread_count; | |
| 36 | − std::atomic<lock_state_t> lock_state; | |
| 37 | − | |
| 38 | − struct safe_alloc final | |
| 39 | − { | |
| 40 | − safe_alloc() | |
| 41 | − { | |
| 42 | − allocating_thread_count++; | |
| 43 | − if (lock_state != lock_state_t::none) | |
| 44 | − { | |
| 45 | − allocating_thread_count--; | |
| 46 | − | |
| 47 | − std::unique_lock<std::mutex> lock{ global_lock }; | |
| 48 | − release_lock.wait(lock, [&]{ return lock_state == lock_state_t::none; }); | |
| 49 | − | |
| 50 | − allocating_thread_count++; | |
| 51 | − } | |
| 52 | − } | |
| 53 | − | |
| 54 | − ~safe_alloc() | |
| 55 | − { | |
| 56 | − assert(allocating_thread_count > 0); | |
| 57 | − allocating_thread_count--; | |
| 58 | − } | |
| 59 | − }; | |
| 60 | −} | |
| 61 | − | |
| 62 | −global_alloc_lock_t::global_alloc_lock_t(bool owner) | |
| 63 | − : _owner{ owner } | |
| 64 | −{ } | |
| 65 | − | |
| 66 | −global_alloc_lock_t::global_alloc_lock_t(global_alloc_lock_t &&other) | |
| 67 | − : _owner{ other._owner } | |
| 68 | −{ | |
| 69 | − other._owner = false; | |
| 70 | −} | |
| 71 | − | |
| 72 | −global_alloc_lock_t::~global_alloc_lock_t() | |
| 73 | −{ | |
| 74 | − if (_owner) | |
| 75 | − { | |
| 76 | − lock_state = lock_state_t::none; | |
| 77 | − release_lock.notify_all(); | |
| 78 | − } | |
| 79 | −} | |
| 80 | − | |
| 81 | −// Lock all allocations on the VM heap. | |
| 82 | −global_alloc_lock_t disvm::runtime::get_global_alloc_lock() | |
| 83 | −{ | |
| 84 | − auto expected = lock_state_t::none; | |
| 85 | − | |
| 86 | − // Block while trying to get lock | |
| 87 | − while (!lock_state.compare_exchange_strong(expected, lock_state_t::pending)) | |
| 88 | − { | |
| 89 | − expected = lock_state_t::none; | |
| 90 | − std::unique_lock<std::mutex> lock{ global_lock }; | |
| 91 | − release_lock.wait(lock); | |
| 92 | − } | |
| 93 | − | |
| 94 | − while (allocating_thread_count != 0) | |
| 95 | − { | |
| 96 | − // Spin-wait for all allocating threads to stop | |
| 97 | − } | |
| 98 | − | |
| 99 | − // Transition to active lock state | |
| 100 | − lock_state = lock_state_t::active; | |
| 101 | − | |
| 102 | − return{ true }; | |
| 103 | −} | |
| 104 | − | |
| 105 | 23 | void *disvm::runtime::alloc_memory(std::size_t amount_in_bytes) |
| 106 | 24 | { |
| 107 | − safe_alloc s; | |
| 108 | − | |
| 109 | 25 | auto memory = std::calloc(amount_in_bytes, sizeof(byte_t)); |
| 110 | 26 | if (memory == nullptr) |
| 111 | 27 | throw vm_system_exception{ "Out of memory" }; |
| @@ -143,13 +59,13 @@ void disvm::runtime::init_memory(const type_descriptor_t &type_desc, void *data) · 31 unmodified lines | ||
| 143 | 59 | |
| 144 | 60 | namespace |
| 145 | 61 | { |
| 146 | − void dec_ref_and_free_pointer_field(pointer_t pointer_field, std::size_t) | |
| 62 | + void dec_ref_and_free_pointer_field(pointer_t pointer_field) | |
| 147 | 63 | { |
| 148 | 64 | assert(pointer_field != nullptr); |
| 149 | 65 | dec_ref_count_and_free(vm_alloc_t::from_allocation(pointer_field)); |
| 150 | 66 | } |
| 151 | 67 | |
| 152 | − void add_ref_pointer_field(pointer_t pointer_field, std::size_t) | |
| 68 | + void add_ref_pointer_field(pointer_t pointer_field) | |
| 153 | 69 | { |
| 154 | 70 | assert(pointer_field != nullptr); |
| 155 | 71 | vm_alloc_t::from_allocation(pointer_field)->add_ref(); |
| @@ -184,6 +100,89 @@ void disvm::runtime::dec_ref_count_and_free(vm_alloc_t *alloc) · 28 unmodified lines | ||
| 184 | 100 | delete alloc; |
| 185 | 101 | } |
| 186 | 102 | |
| 103 | +void disvm::runtime::enum_pointer_fields(const type_descriptor_t &type_desc, void *data, pointer_field_callback_t callback) | |
| 104 | +{ | |
| 105 | + assert(data != nullptr && callback != nullptr); | |
| 106 | + if (type_desc.size_in_bytes == 0) | |
| 107 | + return; | |
| 108 | + | |
| 109 | + auto offset_accum = std::size_t{ 0 }; | |
| 110 | + auto memory = reinterpret_cast<word_t *>(data); | |
| 111 | + for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8, offset_accum += (8 * sizeof(pointer_t))) | |
| 112 | + { | |
| 113 | + const auto words8 = type_desc.pointer_map[i]; | |
| 114 | + assert(sizeof(words8) == 1); | |
| 115 | + if (words8 == 0) | |
| 116 | + continue; | |
| 117 | + | |
| 118 | + const auto flags = std::bitset<sizeof(words8) * 8>{ words8 }; | |
| 119 | + | |
| 120 | + // Enumerating the flags in reverse order so memory access is sequential. | |
| 121 | + if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0])); | |
| 122 | + if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1])); | |
| 123 | + if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2])); | |
| 124 | + if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3])); | |
| 125 | + if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4])); | |
| 126 | + if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5])); | |
| 127 | + if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6])); | |
| 128 | + if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7])); | |
| 129 | + } | |
| 130 | +} | |
| 131 | + | |
| 132 | +void disvm::runtime::enum_pointer_fields_with_offset(const type_descriptor_t &type_desc, void *data, pointer_field_offset_callback_t callback) | |
| 133 | +{ | |
| 134 | + assert(data != nullptr && callback != nullptr); | |
| 135 | + if (type_desc.size_in_bytes == 0) | |
| 136 | + return; | |
| 137 | + | |
| 138 | + auto offset_accum = std::size_t{ 0 }; | |
| 139 | + auto memory = reinterpret_cast<word_t *>(data); | |
| 140 | + for (auto i = word_t{ 0 }; i < type_desc.map_in_bytes; ++i, memory += 8, offset_accum += (8 * sizeof(pointer_t))) | |
| 141 | + { | |
| 142 | + const auto words8 = type_desc.pointer_map[i]; | |
| 143 | + assert(sizeof(words8) == 1); | |
| 144 | + if (words8 == 0) | |
| 145 | + continue; | |
| 146 | + | |
| 147 | + const auto flags = std::bitset<sizeof(words8) * 8>{ words8 }; | |
| 148 | + | |
| 149 | + // Enumerating the flags in reverse order so memory access is sequential. | |
| 150 | + if (flags[7] && (memory[0] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[0]), offset_accum); | |
| 151 | + if (flags[6] && (memory[1] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[1]), offset_accum + (1 * sizeof(pointer_t))); | |
| 152 | + if (flags[5] && (memory[2] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[2]), offset_accum + (2 * sizeof(pointer_t))); | |
| 153 | + if (flags[4] && (memory[3] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[3]), offset_accum + (3 * sizeof(pointer_t))); | |
| 154 | + if (flags[3] && (memory[4] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[4]), offset_accum + (4 * sizeof(pointer_t))); | |
| 155 | + if (flags[2] && (memory[5] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[5]), offset_accum + (5 * sizeof(pointer_t))); | |
| 156 | + if (flags[1] && (memory[6] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[6]), offset_accum + (6 * sizeof(pointer_t))); | |
| 157 | + if (flags[0] && (memory[7] != runtime_constants::nil)) callback(reinterpret_cast<pointer_t>(memory[7]), offset_accum + (7 * sizeof(pointer_t))); | |
| 158 | + } | |
| 159 | +} | |
| 160 | + | |
| 161 | +bool disvm::runtime::is_offset_pointer(const type_descriptor_t &type_desc, std::size_t offset) | |
| 162 | +{ | |
| 163 | + if (type_desc.size_in_bytes == 0) | |
| 164 | + return false; | |
| 165 | + | |
| 166 | + assert(offset < static_cast<std::size_t>(std::numeric_limits<word_t>::max())); | |
| 167 | + const auto byte_offset = static_cast<word_t>(offset / 8); | |
| 168 | + | |
| 169 | + if (byte_offset < type_desc.map_in_bytes) | |
| 170 | + { | |
| 171 | + const auto words8 = type_desc.pointer_map[byte_offset]; | |
| 172 | + assert(sizeof(words8) == 1); | |
| 173 | + if (words8 != 0) | |
| 174 | + { | |
| 175 | + const auto flags = std::bitset<sizeof(words8) * 8>{ words8 }; | |
| 176 | + | |
| 177 | + // Highest order bit is the first field | |
| 178 | + const auto bit_offset = 7 - (offset % 8); | |
| 179 | + return flags[bit_offset]; | |
| 180 | + } | |
| 181 | + } | |
| 182 | + | |
| 183 | + return false; | |
| 184 | +} | |
| 185 | + | |
| 187 | 186 | void *vm_alloc_t::operator new(std::size_t sz) |
| 188 | 187 | { |
| 189 | 188 | return alloc_memory(sz); |
| @@ -203,7 +202,9 @@ vm_alloc_t *vm_alloc_t::allocate(std::shared_ptr<const type_descriptor_t> td) · 13 unmodified lines | ||
| 203 | 202 | auto mem = alloc_memory(sizeof(vm_alloc_t) + td->size_in_bytes); |
| 204 | 203 | auto alloc = ::new(mem)vm_alloc_t{ td }; |
| 205 | 204 | |
| 206 | − debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm alloc: %d", td->size_in_bytes); | |
| 205 | + if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | |
| 206 | + debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm alloc: %d", td->size_in_bytes); | |
| 207 | + | |
| 207 | 208 | return alloc; |
| 208 | 209 | } |
| 209 | 210 | |