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
311311 {
312312 ss << alloc->alloc_type << " - alloc" << dbg_alloc;
313313
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)
315315 {
316316 ss << "\n";
317317 std::fill_n(std::ostream_iterator<std::ostream::char_type>{ ss }, 4 * recurse_depth, ' ');
@@ -671,11 +671,15 @@ namespace · 353 unmodified lines
671671
672672 void cmd_disassemble(const std::vector<std::string> &a, dbg_cmd_cxt_t &cxt)
673673 {
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)
677680 {
678− auto disassemble_length_maybe = a[1];
681+ auto absolute_pc = false;
682+ auto disassemble_length_maybe = a[i];
679683 if (disassemble_length_maybe[0] == '@')
680684 {
681685 absolute_pc = true;
@@ -690,24 +694,22 @@ namespace · 8 unmodified lines
690694 {
691695 throw debug_cmd_error_t{ "Invalid disassemble count format" };
692696 }
697+
698+ if (absolute_pc)
699+ {
700+ begin_pc = disassemble_length;
701+ disassemble_length = default_disassemble_length;
702+ }
693703 }
694704
705+ auto end_pc = begin_pc;
695706 auto &dbg = cxt.dbg;
696− const auto &r = *cxt.current_register;
697707 const auto &code_section = r.module_ref->code_section;
698708
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" };
706711
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)
711713 {
712714 const auto begin_maybe = begin_pc - (-disassemble_length);
713715 begin_pc = std::max(begin_maybe, 0);
@@ -970,7 +972,7 @@ namespace · 256 unmodified lines
970972
971973 { "sw", "Stack walk for the current or supplied thread", "sw ([0-9]+|\\*)?", "sw, sw 34, sw *", cmd_stackwalk },
972974 { "~", "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 },
974976 { "x", "Examine memory", "x [mp|fp] [0-9]+ ([0-9]+)?", "x mp 3, x fp 20 6", cmd_examine },
975977
976978 { "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
240240 " t - Threads\n"
241241 " T - Tool extensions\n"
242242 " e - Exceptions\n"
243+ " d - Duration of actions\n"
243244 " g - Garbage collector (noisy)\n"
244245 " m - Memory allocations (noisy)\n"
245246 " 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
341342 break;
342343 case 'm': debug::set_component_tracing(debug::component_trace_t::memory, true);
343344 break;
345+ case 'd': debug::set_component_tracing(debug::component_trace_t::duration, true);
346+ break;
344347 }
345348 }
346349 break;
src/include/debug.h+2−1
@@ -31,9 +31,10 @@ namespace disvm · 30 unmodified lines
3131 // Potentially logged from a VM extension.
3232 scheduler,
3333 garbage_collector,
34+ duration,
3435
3536 // Tracking purposes
36− last = garbage_collector
37+ last = duration
3738 };
3839
3940 // Trace component enable state.
src/include/runtime.h+2−2
@@ -104,7 +104,7 @@ namespace disvm · 103 unmodified lines
104104 static T *from_allocation(pointer_t allocation)
105105 {
106106 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()));
108108 return static_cast<T *>(alloc_inst);
109109 }
110110
@@ -875,7 +875,7 @@ namespace disvm · 764 unmodified lines
875875 // Called by the scheduler to inform the collector it should attempt to free resources.
876876 // All VM threads are guaranteed to be idle when called.
877877 // 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;
879879 };
880880
881881 // Create a garbage collector that does nothing.
src/include/vm_memory.h+9−72
@@ -20,26 +20,6 @@ namespace disvm · 19 unmodified lines
2020 {
2121 namespace runtime
2222 {
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−
4323 // Allocate memory from the VM heap that is initialize to 0.
4424 // [SPEC] All memory in this implementation of the Dis VM spec
4525 // will be 0 initialized.
@@ -69,63 +49,20 @@ namespace disvm · 23 unmodified lines
6949 // Decrement the ref count and if 0 free the allocation
7050 void dec_ref_count_and_free(vm_alloc_t *alloc);
7151
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+
7258 // 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)>;
7460
7561 // 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);
10463
10564 // 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);
12966 }
13067 }
13168
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
2929 _arr = alloc_memory<byte_t>(_length * _element_type->size_in_bytes);
3030 debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm array: %d", _length);
3131
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
4433 }
4534
4635 vm_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
206206 assert(len <= buffer.size());
207207
208208 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));
211210
212211 pt_ref(r.dest) = new_string->get_allocation();
213212 }
@@ -232,9 +231,7 @@ namespace · 18 unmodified lines
232231 assert(len <= buffer.size());
233232
234233 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));
238235
239236 pt_ref(r.dest) = new_string->get_allocation();
240237 }
@@ -259,9 +256,7 @@ namespace · 18 unmodified lines
259256 assert(len <= buffer.size());
260257
261258 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));
265260
266261 pt_ref(r.dest) = new_string->get_allocation();
267262 }
@@ -513,8 +508,6 @@ namespace · 245 unmodified lines
513508 void _mov_<vm_alloc_t>(pointer_t dest, pointer_t src, const type_descriptor_t *)
514509 {
515510 auto alloc_prev = at_val<vm_alloc_t>(dest);
516− dec_ref_count_and_free(alloc_prev);
517−
518511 auto alloc = at_val<vm_alloc_t>(src);
519512 if (alloc != nullptr)
520513 {
@@ -525,6 +518,8 @@ namespace · 4 unmodified lines
525518 {
526519 pt_ref(dest) = nullptr;
527520 }
521+
522+ dec_ref_count_and_free(alloc_prev);
528523 }
529524
530525 EXEC_DECL(movb) { _mov_<byte_t>(r.dest, r.src, nullptr); }
@@ -1729,8 +1724,7 @@ namespace · 1198 unmodified lines
17291724 auto exception_destination = reinterpret_cast<pointer_t>(frame_pointer + handler->exception_offset);
17301725
17311726 // 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));
17341728
17351729 // Set the exception destination.
17361730 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
3333 {
3434 }
3535
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
3737 {
3838 return true;
3939 }
@@ -67,17 +67,17 @@ namespace · 27 unmodified lines
6767 const auto current_colours = std::array<gc_colour_t, 3>{ gc_colour_t::white, gc_colour_t::grey, gc_colour_t::black };
6868 const auto sweeper_colours = std::array<gc_colour_t, 3>{ gc_colour_t::grey, gc_colour_t::black, gc_colour_t::white };
6969
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)
7171 {
7272 return current_colours[epoch % current_colours.size()];
7373 }
7474
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)
7676 {
7777 return sweeper_colours[epoch % sweeper_colours.size()];
7878 }
7979
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)
8181 {
8282 return *reinterpret_cast<const gc_colour_t *>(&a->gc_reserved);
8383 }
@@ -118,16 +118,21 @@ namespace · 34 unmodified lines
118118 {
119119 std::queue<vm_alloc_t *> allocs;
120120
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
122124 {
123125 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);
125130 } };
126131
127132 // Get roots from threads
128− for (const auto &t : threads)
133+ for (auto &t : threads)
129134 {
130− const auto &r = t->get_registers();
135+ auto &r = t->get_registers();
131136
132137 // Current MP
133138 allocs.push(r.mp_base);
@@ -142,27 +147,15 @@ namespace · 8 unmodified lines
142147 allocs.push(prev_mp);
143148 }
144149
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);
149152 }
150153 }
151154
152155 const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>();
153156 if (log_enabled)
154− {
155157 debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: roots found: %" PRIuPTR, allocs.size());
156158
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−
166159 // Traverse the graph
167160 while (!allocs.empty())
168161 {
@@ -179,41 +172,41 @@ namespace · 10 unmodified lines
179172 }
180173 }
181174
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)
183176 {
184− auto allocs_local = std::move(allocs);
177+ auto allocs_local = std::move(tracking_allocs);
185178
186− auto freed = uint32_t{ 0 };
179+ const auto starting_size = allocs_local.size();
187180 for (auto a : allocs_local)
188181 {
189182 const auto colour = get_gc_colour(a);
190183 if (colour == sweeper_colour)
191− {
192184 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);
198187 }
199188
200189 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());
202191 }
203192 }
204193
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)
206195 {
207196 // No need to lock the actual memory allocator since the contract
208197 // for this function call indicates all VM threads should be blocked.
209198 std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };
210199
211− if (threads.empty() || _tracking_allocs.empty())
200+ if (_tracking_allocs.empty())
212201 return false;
213202
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>();
215205 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+ }
217210
218211 const auto curr_colour = get_current_colour(_collection_epoch);
219212 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
222215 sweep(_tracking_allocs, sweeper_colour);
223216
224217 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+ }
226222
227223 ++_collection_epoch;
228224 return true;
src/vm/garbage_collector.h+1−1
@@ -30,7 +30,7 @@ namespace disvm · 29 unmodified lines
3030
3131 void enum_tracked_allocations(vm_alloc_callback_t callback) const override;
3232
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;
3434
3535 private:
3636 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
2828 set_tail(tail);
2929
3030 // 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
3132 if (_is_alloc)
32− {
3333 _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− }
4034
4135 if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
4236 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
371371 result.push_back(vm_thread);
372372 }
373373
374− _vm.get_garbage_collector().collect(result);
374+ _vm.get_garbage_collector().collect(std::move(result));
375375 _gc_complete = true;
376376 _gc_done_event.notify_all();
377377 }
src/vm/vm_memory.cpp+88−87
@@ -20,92 +20,8 @@ · 19 unmodified lines
2020 using namespace disvm;
2121 using namespace disvm::runtime;
2222
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−
10523 void *disvm::runtime::alloc_memory(std::size_t amount_in_bytes)
10624 {
107− safe_alloc s;
108−
10925 auto memory = std::calloc(amount_in_bytes, sizeof(byte_t));
11026 if (memory == nullptr)
11127 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
14359
14460 namespace
14561 {
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)
14763 {
14864 assert(pointer_field != nullptr);
14965 dec_ref_count_and_free(vm_alloc_t::from_allocation(pointer_field));
15066 }
15167
152− void add_ref_pointer_field(pointer_t pointer_field, std::size_t)
68+ void add_ref_pointer_field(pointer_t pointer_field)
15369 {
15470 assert(pointer_field != nullptr);
15571 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
184100 delete alloc;
185101 }
186102
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+
187186 void *vm_alloc_t::operator new(std::size_t sz)
188187 {
189188 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
203202 auto mem = alloc_memory(sizeof(vm_alloc_t) + td->size_in_bytes);
204203 auto alloc = ::new(mem)vm_alloc_t{ td };
205204
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+
207208 return alloc;
208209 }
209210