History

Alter debugger API to accept vector value not reference.

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

arr committed 9 years ago

Commit cc8f2b61 parent 41616d412 files · +167 −250Browse files

Unified · Split

12 files
  1. src/exec/debugger.cpp +20 −18
  2. src/exec/main.cpp +3 −0
  3. src/include/debug.h +2 −1
  4. src/include/runtime.h +2 −2
  5. src/include/vm_memory.h +9 −72
  6. src/vm/array.cpp +1 −12
  7. src/vm/execution_table.cpp +6 −12
  8. src/vm/garbage_collector.cpp +33 −37
  9. src/vm/garbage_collector.h +1 −1
  10. src/vm/list.cpp +1 −7
  11. src/vm/scheduler.cpp +1 −1
  12. src/vm/vm_memory.cpp +88 −87
src/exec/debugger.cpp+20−18
@@ -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 },
src/exec/main.cpp+3−0
@@ -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;
src/include/debug.h+2−1
@@ -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 purposes36 // Tracking purposes
36 last = garbage_collector37 last = duration
37 };38 };
38 39
39 // Trace component enable state.40 // Trace component enable state.
src/include/runtime.h+2−2
@@ -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.
src/include/vm_memory.h+9−72
@@ -20,26 +20,6 @@ namespace disvm · 19 unmodified lines
20{20{
21 namespace runtime21 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 spec24 // [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 allocation49 // 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 offset58 // 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 pointer64 // 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
src/vm/array.cpp+1−12
@@ -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 array32 // [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
46vm_array_t::vm_array_t(vm_array_t &original, word_t begin_index, word_t length)35vm_array_t::vm_array_t(vm_array_t &original, word_t begin_index, word_t length)
src/vm/execution_table.cpp+6−12
@@ -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();
src/vm/garbage_collector.cpp+33−37
@@ -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>> &) override36 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) -> void121 // 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 threads132 // 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 MP137 // 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 graph159 // 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
205bool default_garbage_collector_t::collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads)194bool 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 contract196 // 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;
src/vm/garbage_collector.h+1−1
@@ -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;
src/vm/list.cpp+1−7
@@ -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");
src/vm/scheduler.cpp+1−1
@@ -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 }
src/vm/vm_memory.cpp+88−87
@@ -20,92 +20,8 @@ · 19 unmodified lines
20using namespace disvm;20using namespace disvm;
21using namespace disvm::runtime;21using namespace disvm::runtime;
22 22
23namespace
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
62global_alloc_lock_t::global_alloc_lock_t(bool owner)
63 : _owner{ owner }
64{ }
65
66global_alloc_lock_t::global_alloc_lock_t(global_alloc_lock_t &&other)
67 : _owner{ other._owner }
68{
69 other._owner = false;
70}
71
72global_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.
82global_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
105void *disvm::runtime::alloc_memory(std::size_t amount_in_bytes)23void *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
144namespace60namespace
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
103void 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
132void 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
161bool 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
187void *vm_alloc_t::operator new(std::size_t sz)186void *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