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 | ss << alloc->alloc_type << " - alloc" << dbg_alloc; | 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 | ss << "\n"; | 316 | ss << "\n"; |
| 317 | std::fill_n(std::ostream_iterator<std::ostream::char_type>{ ss }, 4 * recurse_depth, ' '); | 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 | void cmd_disassemble(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) | 672 | void cmd_disassemble(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt) |
| 673 | { | 673 | { |
| 674 | auto absolute_pc = false; | 674 | const auto &r = *cxt.current_register; |
| 675 | auto disassemble_length = int{ 4 }; | 675 | auto begin_pc = r.pc; |
| 676 | if (a.size() > 1) | 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 | if (disassemble_length_maybe[0] == '@') | 683 | if (disassemble_length_maybe[0] == '@') |
| 680 | { | 684 | { |
| 681 | absolute_pc = true; | 685 | absolute_pc = true; |
| @@ -690,24 +694,22 @@ namespace · 8 unmodified lines | |||
| 690 | { | 694 | { |
| 691 | throw debug_cmd_error_t{ "Invalid disassemble count format" }; | 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 | auto &dbg = cxt.dbg; | 706 | auto &dbg = cxt.dbg; |
| 696 | const auto &r = *cxt.current_register; | ||
| 697 | const auto &code_section = r.module_ref->code_section; | 707 | const auto &code_section = r.module_ref->code_section; |
| 698 | 708 | ||
| 699 | auto begin_pc = r.pc; | 709 | if (begin_pc < 0 || static_cast<int32_t>(code_section.size()) <= end_pc) |
| 700 | auto end_pc = begin_pc; | 710 | throw debug_cmd_error_t{ "Invalid absolute pc" }; |
| 701 | if (absolute_pc) | ||
| 702 | { | ||
| 703 | begin_pc = disassemble_length; | ||
| 704 | end_pc = disassemble_length; | ||
| 705 | disassemble_length = 0; | ||
| 706 | 711 | ||
| 707 | if (begin_pc < 0 || static_cast<int32_t>(code_section.size()) <= end_pc) | 712 | if (disassemble_length < 0) |
| 708 | throw debug_cmd_error_t{ "Invalid absolute pc" }; | ||
| 709 | } | ||
| 710 | else if (disassemble_length < 0) | ||
| 711 | { | 713 | { |
| 712 | const auto begin_maybe = begin_pc - (-disassemble_length); | 714 | const auto begin_maybe = begin_pc - (-disassemble_length); |
| 713 | begin_pc = std::max(begin_maybe, 0); | 715 | begin_pc = std::max(begin_maybe, 0); |
| @@ -970,7 +972,7 @@ namespace · 256 unmodified lines | |||
| 970 | 972 | ||
| 971 | { "sw", "Stack walk for the current or supplied thread", "sw ([0-9]+|\\*)?", "sw, sw 34, sw *", cmd_stackwalk }, | 973 | { "sw", "Stack walk for the current or supplied thread", "sw ([0-9]+|\\*)?", "sw, sw 34, sw *", cmd_stackwalk }, |
| 972 | { "~", "Switch to thread", "~ [0-9]+", "~ 42", cmd_switchthread }, | 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 | { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine }, | 976 | { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine }, |
| 975 | 977 | ||
| 976 | { "set" , "Set a debug option", "set [ _a-zA-Z0-9]*", "'set' for available options", set_value_cmd }, | 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 | " t - Threads\n" | 240 | " t - Threads\n" |
| 241 | " T - Tool extensions\n" | 241 | " T - Tool extensions\n" |
| 242 | " e - Exceptions\n" | 242 | " e - Exceptions\n" |
| 243 | " d - Duration of actions\n" | ||
| 243 | " g - Garbage collector (noisy)\n" | 244 | " g - Garbage collector (noisy)\n" |
| 244 | " m - Memory allocations (noisy)\n" | 245 | " m - Memory allocations (noisy)\n" |
| 245 | " q - Suppress banner and configuration\n" | 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 | break; | 342 | break; |
| 342 | case 'm': debug::set_component_tracing(debug::component_trace_t::memory, true); | 343 | case 'm': debug::set_component_tracing(debug::component_trace_t::memory, true); |
| 343 | break; | 344 | break; |
| 345 | case 'd': debug::set_component_tracing(debug::component_trace_t::duration, true); | ||
| 346 | break; | ||
| 344 | } | 347 | } |
| 345 | } | 348 | } |
| 346 | break; | 349 | break; |
| @@ -31,9 +31,10 @@ namespace disvm · 30 unmodified lines | |||
| 31 | // Potentially logged from a VM extension. | 31 | // Potentially logged from a VM extension. |
| 32 | scheduler, | 32 | scheduler, |
| 33 | garbage_collector, | 33 | garbage_collector, |
| 34 | duration, | ||
| 34 | 35 | ||
| 35 | // Tracking purposes | 36 | // Tracking purposes |
| 36 | last = garbage_collector | 37 | last = duration |
| 37 | }; | 38 | }; |
| 38 | 39 | ||
| 39 | // Trace component enable state. | 40 | // Trace component enable state. |
| @@ -104,7 +104,7 @@ namespace disvm · 103 unmodified lines | |||
| 104 | static T *from_allocation(pointer_t allocation) | 104 | static T *from_allocation(pointer_t allocation) |
| 105 | { | 105 | { |
| 106 | auto alloc_inst = vm_alloc_t::from_allocation(allocation); | 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 | return static_cast<T *>(alloc_inst); | 108 | return static_cast<T *>(alloc_inst); |
| 109 | } | 109 | } |
| 110 | 110 | ||
| @@ -875,7 +875,7 @@ namespace disvm · 764 unmodified lines | |||
| 875 | // Called by the scheduler to inform the collector it should attempt to free resources. | 875 | // Called by the scheduler to inform the collector it should attempt to free resources. |
| 876 | // All VM threads are guaranteed to be idle when called. | 876 | // All VM threads are guaranteed to be idle when called. |
| 877 | // Returns true if the collection algorithm was run, otherwise false. | 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 | // Create a garbage collector that does nothing. | 881 | // Create a garbage collector that does nothing. |
| @@ -20,26 +20,6 @@ namespace disvm · 19 unmodified lines | |||
| 20 | { | 20 | { |
| 21 | namespace runtime | 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 | // Allocate memory from the VM heap that is initialize to 0. | 23 | // Allocate memory from the VM heap that is initialize to 0. |
| 44 | // [SPEC] All memory in this implementation of the Dis VM spec | 24 | // [SPEC] All memory in this implementation of the Dis VM spec |
| 45 | // will be 0 initialized. | 25 | // will be 0 initialized. |
| @@ -69,63 +49,20 @@ namespace disvm · 23 unmodified lines | |||
| 69 | // Decrement the ref count and if 0 free the allocation | 49 | // Decrement the ref count and if 0 free the allocation |
| 70 | void dec_ref_count_and_free(vm_alloc_t *alloc); | 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 | // Callback for a pointer field supplying the pointer and byte offset | 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 | // The callback will be called on all non-null pointer fields. | 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) | 62 | void enum_pointer_fields_with_offset(const type_descriptor_t &type_desc, void *data, pointer_field_offset_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 | } | ||
| 104 | 63 | ||
| 105 | // Check if a specific pointer-sized offset in a type represents a pointer | 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) | 65 | 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 | } | ||
| 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 | _arr = alloc_memory<byte_t>(_length * _element_type->size_in_bytes); | 29 | _arr = alloc_memory<byte_t>(_length * _element_type->size_in_bytes); |
| 30 | debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: %d", _length); | 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 | 32 | // [SPEC] Element memory is already initialized to zero based on the alloc_memory contract |
| 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); | ||
| 44 | } | 33 | } |
| 45 | 34 | ||
| 46 | vm_array_t::vm_array_t(vm_array_t &original, word_t begin_index, word_t length) | 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 | assert(len <= buffer.size()); | 206 | assert(len <= buffer.size()); |
| 207 | 207 | ||
| 208 | auto new_string = new vm_string_t{ len, reinterpret_cast<uint8_t *>(buffer.data()) }; | 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); | 209 | dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); |
| 210 | dec_ref_count_and_free(prev); | ||
| 211 | 210 | ||
| 212 | pt_ref(r.dest) = new_string->get_allocation(); | 211 | pt_ref(r.dest) = new_string->get_allocation(); |
| 213 | } | 212 | } |
| @@ -232,9 +231,7 @@ namespace · 18 unmodified lines | |||
| 232 | assert(len <= buffer.size()); | 231 | assert(len <= buffer.size()); |
| 233 | 232 | ||
| 234 | auto new_string = new vm_string_t{ len, reinterpret_cast<uint8_t *>(buffer.data()) }; | 233 | auto new_string = new vm_string_t{ len, reinterpret_cast<uint8_t *>(buffer.data()) }; |
| 235 | 234 | dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 236 | auto prev = at_val<vm_alloc_t>(r.dest); | ||
| 237 | dec_ref_count_and_free(prev); | ||
| 238 | 235 | ||
| 239 | pt_ref(r.dest) = new_string->get_allocation(); | 236 | pt_ref(r.dest) = new_string->get_allocation(); |
| 240 | } | 237 | } |
| @@ -259,9 +256,7 @@ namespace · 18 unmodified lines | |||
| 259 | assert(len <= buffer.size()); | 256 | assert(len <= buffer.size()); |
| 260 | 257 | ||
| 261 | auto new_string = new vm_string_t{ len, reinterpret_cast<uint8_t *>(buffer.data()) }; | 258 | auto new_string = new vm_string_t{ len, reinterpret_cast<uint8_t *>(buffer.data()) }; |
| 262 | 259 | dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); | |
| 263 | auto prev = at_val<vm_alloc_t>(r.dest); | ||
| 264 | dec_ref_count_and_free(prev); | ||
| 265 | 260 | ||
| 266 | pt_ref(r.dest) = new_string->get_allocation(); | 261 | pt_ref(r.dest) = new_string->get_allocation(); |
| 267 | } | 262 | } |
| @@ -513,8 +508,6 @@ namespace · 245 unmodified lines | |||
| 513 | void _mov_<vm_alloc_t>(pointer_t dest, pointer_t src, const type_descriptor_t *) | 508 | void _mov_<vm_alloc_t>(pointer_t dest, pointer_t src, const type_descriptor_t *) |
| 514 | { | 509 | { |
| 515 | auto alloc_prev = at_val<vm_alloc_t>(dest); | 510 | auto alloc_prev = at_val<vm_alloc_t>(dest); |
| 516 | dec_ref_count_and_free(alloc_prev); | ||
| 517 | |||
| 518 | auto alloc = at_val<vm_alloc_t>(src); | 511 | auto alloc = at_val<vm_alloc_t>(src); |
| 519 | if (alloc != nullptr) | 512 | if (alloc != nullptr) |
| 520 | { | 513 | { |
| @@ -525,6 +518,8 @@ namespace · 4 unmodified lines | |||
| 525 | { | 518 | { |
| 526 | pt_ref(dest) = nullptr; | 519 | pt_ref(dest) = nullptr; |
| 527 | } | 520 | } |
| 521 | |||
| 522 | dec_ref_count_and_free(alloc_prev); | ||
| 528 | } | 523 | } |
| 529 | 524 | ||
| 530 | EXEC_DECL(movb) { _mov_<byte_t>(r.dest, r.src, nullptr); } | 525 | EXEC_DECL(movb) { _mov_<byte_t>(r.dest, r.src, nullptr); } |
| @@ -1729,8 +1724,7 @@ namespace · 1198 unmodified lines | |||
| 1729 | auto exception_destination = reinterpret_cast<pointer_t>(frame_pointer + handler->exception_offset); | 1724 | auto exception_destination = reinterpret_cast<pointer_t>(frame_pointer + handler->exception_offset); |
| 1730 | 1725 | ||
| 1731 | // Destroy any data at this location. | 1726 | // Destroy any data at this location. |
| 1732 | auto current_value = at_val<vm_alloc_t>(exception_destination); | 1727 | dec_ref_count_and_free(at_val<vm_alloc_t>(exception_destination)); |
| 1733 | dec_ref_count_and_free(current_value); | ||
| 1734 | 1728 | ||
| 1735 | // Set the exception destination. | 1729 | // Set the exception destination. |
| 1736 | pt_ref(exception_destination) = e->get_allocation(); | 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 | return true; | 38 | return true; |
| 39 | } | 39 | } |
| @@ -67,17 +67,17 @@ namespace · 27 unmodified lines | |||
| 67 | const auto current_colours = std::array<gc_colour_t, 3>{ gc_colour_t::white, gc_colour_t::grey, gc_colour_t::black }; | 67 | const auto current_colours = std::array<gc_colour_t, 3>{ gc_colour_t::white, gc_colour_t::grey, gc_colour_t::black }; |
| 68 | const auto sweeper_colours = std::array<gc_colour_t, 3>{ gc_colour_t::grey, gc_colour_t::black, gc_colour_t::white }; | 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 | return current_colours[epoch % current_colours.size()]; | 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 | return sweeper_colours[epoch % sweeper_colours.size()]; | 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 | return *reinterpret_cast<const gc_colour_t *>(&a->gc_reserved); | 82 | return *reinterpret_cast<const gc_colour_t *>(&a->gc_reserved); |
| 83 | } | 83 | } |
| @@ -118,16 +118,21 @@ namespace · 34 unmodified lines | |||
| 118 | { | 118 | { |
| 119 | std::queue<vm_alloc_t *> allocs; | 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 | auto a = vm_alloc_t::from_allocation(p); | 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 | // Get roots from threads | 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 | // Current MP | 137 | // Current MP |
| 133 | allocs.push(r.mp_base); | 138 | allocs.push(r.mp_base); |
| @@ -142,27 +147,15 @@ namespace · 8 unmodified lines | |||
| 142 | allocs.push(prev_mp); | 147 | allocs.push(prev_mp); |
| 143 | } | 148 | } |
| 144 | 149 | ||
| 145 | if (frame->frame_type->map_in_bytes == 0) | 150 | if (frame->frame_type->map_in_bytes > 0) |
| 146 | continue; | 151 | enum_pointer_fields(*frame->frame_type, frame->base(), add_pointer_to_queue); |
| 147 | |||
| 148 | enum_pointer_fields(*frame->frame_type, frame->base(), add_pointer_to_queue); | ||
| 149 | } | 152 | } |
| 150 | } | 153 | } |
| 151 | 154 | ||
| 152 | const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>(); | 155 | const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>(); |
| 153 | if (log_enabled) | 156 | if (log_enabled) |
| 154 | { | ||
| 155 | debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: roots found: %" PRIuPTR, allocs.size()); | 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 | // Traverse the graph | 159 | // Traverse the graph |
| 167 | while (!allocs.empty()) | 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 | for (auto a : allocs_local) | 180 | for (auto a : allocs_local) |
| 188 | { | 181 | { |
| 189 | const auto colour = get_gc_colour(a); | 182 | const auto colour = get_gc_colour(a); |
| 190 | if (colour == sweeper_colour) | 183 | if (colour == sweeper_colour) |
| 191 | { | ||
| 192 | dec_ref_count_and_free(a); | 184 | dec_ref_count_and_free(a); |
| 193 | freed++; | 185 | else |
| 194 | continue; | 186 | tracking_allocs.push_back(a); |
| 195 | } | ||
| 196 | |||
| 197 | allocs.push_back(a); | ||
| 198 | } | 187 | } |
| 199 | 188 | ||
| 200 | if (debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>()) | 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 | // No need to lock the actual memory allocator since the contract | 196 | // No need to lock the actual memory allocator since the contract |
| 208 | // for this function call indicates all VM threads should be blocked. | 197 | // for this function call indicates all VM threads should be blocked. |
| 209 | std::lock_guard<std::mutex> lock{ _tracking_allocs_lock }; | 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 | return false; | 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 | if (log_enabled) | 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 | const auto curr_colour = get_current_colour(_collection_epoch); | 211 | const auto curr_colour = get_current_colour(_collection_epoch); |
| 219 | mark(threads, curr_colour); | 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 | sweep(_tracking_allocs, sweeper_colour); | 215 | sweep(_tracking_allocs, sweeper_colour); |
| 223 | 216 | ||
| 224 | if (log_enabled) | 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 | ++_collection_epoch; | 223 | ++_collection_epoch; |
| 228 | return true; | 224 | return true; |
| @@ -30,7 +30,7 @@ namespace disvm · 29 unmodified lines | |||
| 30 | 30 | ||
| 31 | void enum_tracked_allocations(vm_alloc_callback_t callback) const override; | 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 | private: | 35 | private: |
| 36 | vm_t &_vm; | 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 | set_tail(tail); | 28 | set_tail(tail); |
| 29 | 29 | ||
| 30 | // If the default list element storage is not enough, allocate more memory. | 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 | if (_is_alloc) | 32 | if (_is_alloc) |
| 32 | { | ||
| 33 | _mem.alloc = static_cast<pointer_t>(alloc_memory(_element_type->size_in_bytes)); | 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 | if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) | 35 | if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>()) |
| 42 | debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm list"); | 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 | result.push_back(vm_thread); | 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 | _gc_complete = true; | 375 | _gc_complete = true; |
| 376 | _gc_done_event.notify_all(); | 376 | _gc_done_event.notify_all(); |
| 377 | } | 377 | } |
| @@ -20,92 +20,8 @@ · 19 unmodified lines | |||
| 20 | using namespace disvm; | 20 | using namespace disvm; |
| 21 | using namespace disvm::runtime; | 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 | void *disvm::runtime::alloc_memory(std::size_t amount_in_bytes) | 23 | void *disvm::runtime::alloc_memory(std::size_t amount_in_bytes) |
| 106 | { | 24 | { |
| 107 | safe_alloc s; | ||
| 108 | |||
| 109 | auto memory = std::calloc(amount_in_bytes, sizeof(byte_t)); | 25 | auto memory = std::calloc(amount_in_bytes, sizeof(byte_t)); |
| 110 | if (memory == nullptr) | 26 | if (memory == nullptr) |
| 111 | throw vm_system_exception{ "Out of memory" }; | 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 | namespace | 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 | assert(pointer_field != nullptr); | 64 | assert(pointer_field != nullptr); |
| 149 | dec_ref_count_and_free(vm_alloc_t::from_allocation(pointer_field)); | 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 | assert(pointer_field != nullptr); | 70 | assert(pointer_field != nullptr); |
| 155 | vm_alloc_t::from_allocation(pointer_field)->add_ref(); | 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 | delete alloc; | 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 | void *vm_alloc_t::operator new(std::size_t sz) | 186 | void *vm_alloc_t::operator new(std::size_t sz) |
| 188 | { | 187 | { |
| 189 | return alloc_memory(sz); | 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 | auto mem = alloc_memory(sizeof(vm_alloc_t) + td->size_in_bytes); | 202 | auto mem = alloc_memory(sizeof(vm_alloc_t) + td->size_in_bytes); |
| 204 | auto alloc = ::new(mem)vm_alloc_t{ td }; | 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 | return alloc; | 208 | return alloc; |
| 208 | } | 209 | } |
| 209 | 210 | ||