{ auto list = at_val<vm_list_t>(r.src); if (list == nullptr) throw dereference_nil{ "Head of list" }; r.src = list->value(); movp(r, vm); } EXEC_DECL(headmp) { auto list = at_val<vm_list_t>(r.src); if (list == nullptr) throw dereference_nil{ "Head of list" }; r.src = list->value(); movmp(r, vm); } EXEC_DECL(tail) { auto list = at_val<vm_list_t>(r.src); if (list == nullptr) throw dereference_nil{ "Tail of list" }; assert(list->alloc_type == vm_list_t::type_desc()); auto tail = pointer_t{}; auto tail_maybe = list->get_tail(); if (tail_maybe != nullptr) tail = tail_maybe->get_allocation(); // Use the address of the local variable. r.src = reinterpret_cast<pointer_t>(&tail); movp(r, vm); } // // Channel operations // namespace { void _channel_movm(pointer_t dest, pointer_t src, const type_descriptor_t *type) { assert(type != nullptr); std::memmove(dest, src, type->size_in_bytes); } void _channel_movmp(pointer_t dest, pointer_t src, const type_descriptor_t *type) { assert(type != nullptr); destroy_memory(*type, dest); inc_ref_count_in_memory(*type, src); std::memmove(dest, src, type->size_in_bytes); } // [SPEC] 'Create new channel' op codes // Documented: (http://www.vitanuova.com/inferno/papers/dis.html) // As documented the src2 register is not used, however the Inferno // implementation does use the src2 register. It is checked against // the dst register and if they are not equal the src2 register // contains the internal buffer size of the channel. // // See Inferno implementation: (libinterp/xec.c) void _newc_( vm_registers_t &r, const vm_t &vm, std::shared_ptr<const type_descriptor_t> type_desc, vm_channel_t::data_transfer_func_t transfer) { auto buffer_len = word_t{ 0 }; if (r.mid != r.dest) { buffer_len = vt_ref<word_t>(r.mid); if (buffer_len < 0) throw out_of_range_memory{}; } dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); auto new_channel = new vm_channel_t{ type_desc, transfer, buffer_len }; if (type_desc->map_in_bytes > 0) vm.get_garbage_collector().track_allocation(new_channel); pt_ref(r.dest) = new_channel->get_allocation(); } } EXEC_DECL(newcb) { _newc_(r, vm, intrinsic_type_desc::type<byte_t>(), _mov_<byte_t>); } EXEC_DECL(newcw) { _newc_(r, vm, intrinsic_type_desc::type<word_t>(), _mov_<word_t>); } EXEC_DECL(newcf) { _newc_(r, vm, intrinsic_type_desc::type<real_t>(), _mov_<real_t>); } EXEC_DECL(newcl) { _newc_(r, vm, intrinsic_type_desc::type<big_t>(), _mov_<big_t>); } EXEC_DECL(newcp) { _newc_(r, vm, intrinsic_type_desc::type<pointer_t>(), _mov_<vm_alloc_t>); } EXEC_DECL(newcm) { auto memory_size = vt_ref<word_t>(r.src); auto channel_data_type = type_descriptor_t::create(memory_size); _newc_(r, vm, channel_data_type, _channel_movm); } EXEC_DECL(newcmp) { auto type_id = vt_ref<word_t>(r.src); const auto &types = r.module_ref->type_section; assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); auto type = types[type_id]; _newc_(r, vm, type, _channel_movmp); } EXEC_DECL(send) { auto channel = at_val<vm_channel_t>(r.dest); if (channel == nullptr) throw dereference_nil{ "Send to channel" }; const auto current_thread_id = r.thread.get_thread_id(); assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); r.request_mutex.pending_request = true; assert(r.current_thread_state == vm_thread_state_t::running); r.current_thread_state = vm_thread_state_t::blocked_sending; // Setup the request auto request = vm_channel_request_t{ current_thread_id, r.request_mutex }; request.data = r.src; request.request_handled_callback = [&vm, &r, current_thread_id](const vm_channel_t &) { assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_sending); vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); }; if (channel->send_data(request)) r.current_thread_state = vm_thread_state_t::running; } EXEC_DECL(recv) { auto channel = at_val<vm_channel_t>(r.src); if (channel == nullptr) throw dereference_nil{ "Receive from channel" }; const auto current_thread_id = r.thread.get_thread_id(); assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); r.request_mutex.pending_request = true; assert(r.current_thread_state == vm_thread_state_t::running); r.current_thread_state = vm_thread_state_t::blocked_receiving; // Setup the request auto request = vm_channel_request_t { current_thread_id, r.request_mutex }; request.data = r.dest; request.request_handled_callback = [&vm, &r, current_thread_id](const vm_channel_t &) { assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_receiving); vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); }; if (channel->receive_data(request)) r.current_thread_state = vm_thread_state_t::running; } namespace { std::minstd_rand alt_channel_selector; word_t cancel_outstanding_channel_requests(uint32_t request_thread_id, vm_alt_stack_layout_t *alt, const vm_channel_t &handled_channel) { auto alloc = handled_channel.get_allocation(); auto handled_index = word_t{ -1 }; const auto channel_count = (alt->send_count + alt->receive_count); for (auto i = word_t{ 0 }; i < channel_count; ++i) { auto ch_pointer = reinterpret_cast<pointer_t>(alt->channels[i].alloc); if (ch_pointer == alloc) { assert(handled_index == -1 && "Index should only be set once"); handled_index = i; continue; } auto ch = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&ch_pointer)); ch->cancel_request(request_thread_id); } assert(handled_index != -1 && "Index should be set"); return handled_index; } void exec_alt(vm_registers_t &r, const vm_t &vm, const bool blocking) { auto alt = reinterpret_cast<vm_alt_stack_layout_t *>(r.src); assert(alt != nullptr); const auto current_thread_id = r.thread.get_thread_id(); assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); r.request_mutex.pending_request = true; assert(r.current_thread_state == vm_thread_state_t::running); r.current_thread_state = vm_thread_state_t::blocked_in_alt; // Create request callback auto alt_request_handled_callback = [&vm, &r, alt, current_thread_id](const vm_channel_t &handled_channel) { // Cancel all non-handled requests and determine the // index of the channel that handled this request. auto handled_index = cancel_outstanding_channel_requests(current_thread_id, alt, handled_channel); // Set the index of the channel that handled the request vt_ref<word_t>(r.dest) = handled_index; assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_in_alt); vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); }; const auto channel_count = (alt->send_count + alt->receive_count); assert(channel_count > 0); // Generate a random channel index sequence. auto random_idx_sequence = std::vector<word_t>(channel_count); std::iota(std::begin(random_idx_sequence), std::end(random_idx_sequence), 0); std::shuffle(std::begin(random_idx_sequence), std::end(random_idx_sequence), alt_channel_selector); auto requests_queued = uint32_t{ 0 }; auto handled = bool{}; for (auto rnd_idx : random_idx_sequence) { auto &channel_stack_layout = alt->channels[rnd_idx]; if (channel_stack_layout.alloc == runtime_constants::nil) throw dereference_nil{ "Alt channel" }; auto channel = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&channel_stack_layout.alloc)); auto request = vm_channel_request_t{ current_thread_id, r.request_mutex }; request.data = reinterpret_cast<pointer_t>(channel_stack_layout.data); request.request_handled_callback = alt_request_handled_callback; if (rnd_idx < alt->send_count) handled = channel->send_data(request); else handled = channel->receive_data(request); if (handled) { // Check if requests need to be handled if (requests_queued > 0) { auto handled_index = cancel_outstanding_channel_requests(current_thread_id, alt, *channel); assert(handled_index == rnd_idx); } vt_ref<word_t>(r.dest) = rnd_idx; r.current_thread_state = vm_thread_state_t::running; return; } else { requests_queued++; } } // If this is a non-blocking alt request and we are here no channels // were immediately able to handle the request. Try to lock the request // so we can cancel it in all channels. if (!blocking && r.request_mutex.pending_request_lock.try_lock()) { std::lock_guard<std::mutex> lock{ r.request_mutex.pending_request_lock, std::adopt_lock }; // Check if a channel handled the request between the initial queueing and now. if (r.request_mutex.pending_request) { r.request_mutex.pending_request = false; if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "alt: request: non-blocking\n"); // Cancel request in all channels. for (auto i = word_t{ 0 }; i < channel_count; ++i) { auto ch_pointer = reinterpret_cast<pointer_t>(alt->channels[i].alloc); auto ch = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&ch_pointer)); ch->cancel_request(current_thread_id); } // Index of a non-blocking request is the final index of the alt. vt_ref<word_t>(r.dest) = channel_count; r.current_thread_state = vm_thread_state_t::running; } } } } EXEC_DECL(alt) { exec_alt(r, vm, true /* blocking */); } EXEC_DECL(nbalt) { exec_alt(r, vm, false /* blocking */); } // // Dynamic allocation operations // namespace { void new_instance(vm_registers_t &r, const vm_t &vm, const bool zero_memory) { const auto type_id = vt_ref<word_t>(r.src); const auto &types = r.module_ref->type_section; assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); auto type_desc = types[type_id]; auto new_alloc = !zero_memory ? vm_alloc_t::allocate(type_desc) : vm_alloc_t::allocate(type_desc, vm_alloc_t::zero_memory); if (type_desc->map_in_bytes > 0) vm.get_garbage_collector().track_allocation(new_alloc); pt_ref(r.dest) = new_alloc->get_allocation(); } } EXEC_DECL(new_) { new_instance(r, vm, false /* zero memory */); } EXEC_DECL(newz) { new_instance(r, vm, true /* zero memory */); } EXEC_DECL(mnewz) { auto target_module = at_val<vm_module_ref_t>(r.src); if (target_module == nullptr) throw vm_user_exception{ "Module not loaded" }; const auto type_id = vt_ref<word_t>(r.mid); const auto &types = target_module->type_section; assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); auto type_desc = types[type_id]; auto new_alloc = vm_alloc_t::allocate(type_desc, vm_alloc_t::zero_memory); if (type_desc->map_in_bytes > 0) vm.get_garbage_collector().track_allocation(new_alloc); pt_ref(r.dest) = new_alloc->get_allocation(); } namespace { void new_array(vm_registers_t &r, const vm_t &vm, const bool zero_memory) { assert(r.module_ref != nullptr); const auto &types = r.module_ref->type_section; const auto type_id = vt_ref<word_t>(r.mid); assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); auto type_desc = types[type_id]; dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); const auto array_size = vt_ref<word_t>(r.src); auto new_array = !zero_memory ? new vm_array_t(type_desc, array_size) : new vm_array_t(type_desc, array_size, vm_alloc_t::zero_memory); if (type_desc->map_in_bytes > 0) vm.get_garbage_collector().track_allocation(new_array); pt_ref(r.dest) = new_array->get_allocation(); } } EXEC_DECL(newa) { new_array(r, vm, false /* zero memory */); } EXEC_DECL(newaz) { new_array(r, vm, true /* zero memory */); } EXEC_DECL(tcmp) { auto s = at_val<vm_alloc_t>(r.src); auto d = at_val<vm_alloc_t>(r.dest); if (s == nullptr) return; if (d == nullptr || s->alloc_type != d->alloc_type) throw type_violation{}; } // // Stack operations // // [SPEC] 'runt' op code // Undocumented: (http://www.vitanuova.com/inferno/papers/dis.html) // // Inferno implementation: (libinterp/xec.c) // No operands and appears to be a type of NOP instruction. EXEC_DECL(runt) { /* NOP */ } EXEC_DECL(lea) { pt_ref(r.dest) = r.src; } EXEC_DECL(frame) { const auto &types = r.module_ref->type_section; const auto frame_type_id = vt_ref<word_t>(r.src); assert(0 <= frame_type_id && frame_type_id < static_cast<word_t>(types.size())); const auto frame_type = types[frame_type_id]; pt_ref(r.dest) = r.stack.alloc_frame(frame_type)->base(); } EXEC_DECL(mframe) { const auto target_module = at_val<vm_module_ref_t>(r.src); if (target_module == nullptr) throw vm_user_exception{ "Module not loaded" }; const auto &types = target_module->type_section; // Get the function ref index for this module. const auto function_id = vt_ref<word_t>(r.mid); const auto &function_ref = target_module->get_function_ref(function_id); const auto frame_type_id = function_ref.frame_type; assert(0 <= frame_type_id && frame_type_id < static_cast<word_t>(types.size())); const auto frame_type = types[frame_type_id]; pt_ref(r.dest) = r.stack.alloc_frame(frame_type)->base(); } EXEC_DECL(ret) { const auto current_top_frame = r.stack.peek_frame(); assert(current_top_frame != nullptr); r.pc = current_top_frame->prev_pc(); if (current_top_frame->prev_module_ref() != nullptr) { dec_ref_count_and_free(r.module_ref); r.module_ref = current_top_frame->prev_module_ref(); current_top_frame->prev_module_ref() = nullptr; dec_ref_count_and_free(r.mp_base); r.mp_base = r.module_ref->mp_base; } r.stack.pop_frame(); if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>()) debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "exit: function\n"); } EXEC_DECL(call) { auto top_frame = r.stack.push_frame(); top_frame->prev_pc() = r.pc; #ifndef NDEBUG // Validate the stack state const auto frame_base_pointer = pt_ref(r.src); assert(frame_base_pointer != nullptr); assert(top_frame->base() == frame_base_pointer);#endif r.pc = vt_ref<vm_pc_t>(r.dest); debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "enter: function\n"); } EXEC_DECL(mcall) { auto target_module = at_val<vm_module_ref_t>(r.dest); if (target_module == nullptr) throw vm_user_exception{ "Module not loaded" }; // Get the pc for the function call into this module const auto export_id = vt_ref<word_t>(r.mid); const auto &function_ref = target_module->get_function_ref(export_id);Lines 1001–1500 of 1969. Previous lines Load more
{ auto list = at_val<vm_list_t>(r.src); if (list == nullptr) throw dereference_nil{ "Head of list" }; r.src = list->value(); movp(r, vm); } EXEC_DECL(headmp) { auto list = at_val<vm_list_t>(r.src); if (list == nullptr) throw dereference_nil{ "Head of list" }; r.src = list->value(); movmp(r, vm); } EXEC_DECL(tail) { auto list = at_val<vm_list_t>(r.src); if (list == nullptr) throw dereference_nil{ "Tail of list" }; assert(list->alloc_type == vm_list_t::type_desc()); auto tail = pointer_t{}; auto tail_maybe = list->get_tail(); if (tail_maybe != nullptr) tail = tail_maybe->get_allocation(); // Use the address of the local variable. r.src = reinterpret_cast<pointer_t>(&tail); movp(r, vm); } // // Channel operations // namespace { void _channel_movm(pointer_t dest, pointer_t src, const type_descriptor_t *type) { assert(type != nullptr); std::memmove(dest, src, type->size_in_bytes); } void _channel_movmp(pointer_t dest, pointer_t src, const type_descriptor_t *type) { assert(type != nullptr); destroy_memory(*type, dest); inc_ref_count_in_memory(*type, src); std::memmove(dest, src, type->size_in_bytes); } // [SPEC] 'Create new channel' op codes // Documented: (http://www.vitanuova.com/inferno/papers/dis.html) // As documented the src2 register is not used, however the Inferno // implementation does use the src2 register. It is checked against // the dst register and if they are not equal the src2 register // contains the internal buffer size of the channel. // // See Inferno implementation: (libinterp/xec.c) void _newc_( vm_registers_t &r, const vm_t &vm, std::shared_ptr<const type_descriptor_t> type_desc, vm_channel_t::data_transfer_func_t transfer) { auto buffer_len = word_t{ 0 }; if (r.mid != r.dest) { buffer_len = vt_ref<word_t>(r.mid); if (buffer_len < 0) throw out_of_range_memory{}; } dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); auto new_channel = new vm_channel_t{ type_desc, transfer, buffer_len }; if (type_desc->map_in_bytes > 0) vm.get_garbage_collector().track_allocation(new_channel); pt_ref(r.dest) = new_channel->get_allocation(); } } EXEC_DECL(newcb) { _newc_(r, vm, intrinsic_type_desc::type<byte_t>(), _mov_<byte_t>); } EXEC_DECL(newcw) { _newc_(r, vm, intrinsic_type_desc::type<word_t>(), _mov_<word_t>); } EXEC_DECL(newcf) { _newc_(r, vm, intrinsic_type_desc::type<real_t>(), _mov_<real_t>); } EXEC_DECL(newcl) { _newc_(r, vm, intrinsic_type_desc::type<big_t>(), _mov_<big_t>); } EXEC_DECL(newcp) { _newc_(r, vm, intrinsic_type_desc::type<pointer_t>(), _mov_<vm_alloc_t>); } EXEC_DECL(newcm) { auto memory_size = vt_ref<word_t>(r.src); auto channel_data_type = type_descriptor_t::create(memory_size); _newc_(r, vm, channel_data_type, _channel_movm); } EXEC_DECL(newcmp) { auto type_id = vt_ref<word_t>(r.src); const auto &types = r.module_ref->type_section; assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); auto type = types[type_id]; _newc_(r, vm, type, _channel_movmp); } EXEC_DECL(send) { auto channel = at_val<vm_channel_t>(r.dest); if (channel == nullptr) throw dereference_nil{ "Send to channel" }; const auto current_thread_id = r.thread.get_thread_id(); assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); r.request_mutex.pending_request = true; assert(r.current_thread_state == vm_thread_state_t::running); r.current_thread_state = vm_thread_state_t::blocked_sending; // Setup the request auto request = vm_channel_request_t{ current_thread_id, r.request_mutex }; request.data = r.src; request.request_handled_callback = [&vm, &r, current_thread_id](const vm_channel_t &) { assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_sending); vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); }; if (channel->send_data(request)) r.current_thread_state = vm_thread_state_t::running; } EXEC_DECL(recv) { auto channel = at_val<vm_channel_t>(r.src); if (channel == nullptr) throw dereference_nil{ "Receive from channel" }; const auto current_thread_id = r.thread.get_thread_id(); assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); r.request_mutex.pending_request = true; assert(r.current_thread_state == vm_thread_state_t::running); r.current_thread_state = vm_thread_state_t::blocked_receiving; // Setup the request auto request = vm_channel_request_t { current_thread_id, r.request_mutex }; request.data = r.dest; request.request_handled_callback = [&vm, &r, current_thread_id](const vm_channel_t &) { assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_receiving); vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); }; if (channel->receive_data(request)) r.current_thread_state = vm_thread_state_t::running; } namespace { std::minstd_rand alt_channel_selector; word_t cancel_outstanding_channel_requests(uint32_t request_thread_id, vm_alt_stack_layout_t *alt, const vm_channel_t &handled_channel) { auto alloc = handled_channel.get_allocation(); auto handled_index = word_t{ -1 }; const auto channel_count = (alt->send_count + alt->receive_count); for (auto i = word_t{ 0 }; i < channel_count; ++i) { auto ch_pointer = reinterpret_cast<pointer_t>(alt->channels[i].alloc); if (ch_pointer == alloc) { assert(handled_index == -1 && "Index should only be set once"); handled_index = i; continue; } auto ch = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&ch_pointer)); ch->cancel_request(request_thread_id); } assert(handled_index != -1 && "Index should be set"); return handled_index; } void exec_alt(vm_registers_t &r, const vm_t &vm, const bool blocking) { auto alt = reinterpret_cast<vm_alt_stack_layout_t *>(r.src); assert(alt != nullptr); const auto current_thread_id = r.thread.get_thread_id(); assert(!r.request_mutex.pending_request && "There should be no pending request for the thread"); r.request_mutex.pending_request = true; assert(r.current_thread_state == vm_thread_state_t::running); r.current_thread_state = vm_thread_state_t::blocked_in_alt; // Create request callback auto alt_request_handled_callback = [&vm, &r, alt, current_thread_id](const vm_channel_t &handled_channel) { // Cancel all non-handled requests and determine the // index of the channel that handled this request. auto handled_index = cancel_outstanding_channel_requests(current_thread_id, alt, handled_channel); // Set the index of the channel that handled the request vt_ref<word_t>(r.dest) = handled_index; assert(!r.request_mutex.pending_request && r.current_thread_state == vm_thread_state_t::blocked_in_alt); vm.get_scheduler_control().enqueue_blocked_thread(current_thread_id); }; const auto channel_count = (alt->send_count + alt->receive_count); assert(channel_count > 0); // Generate a random channel index sequence. auto random_idx_sequence = std::vector<word_t>(channel_count); std::iota(std::begin(random_idx_sequence), std::end(random_idx_sequence), 0); std::shuffle(std::begin(random_idx_sequence), std::end(random_idx_sequence), alt_channel_selector); auto requests_queued = uint32_t{ 0 }; auto handled = bool{}; for (auto rnd_idx : random_idx_sequence) { auto &channel_stack_layout = alt->channels[rnd_idx]; if (channel_stack_layout.alloc == runtime_constants::nil) throw dereference_nil{ "Alt channel" }; auto channel = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&channel_stack_layout.alloc)); auto request = vm_channel_request_t{ current_thread_id, r.request_mutex }; request.data = reinterpret_cast<pointer_t>(channel_stack_layout.data); request.request_handled_callback = alt_request_handled_callback; if (rnd_idx < alt->send_count) handled = channel->send_data(request); else handled = channel->receive_data(request); if (handled) { // Check if requests need to be handled if (requests_queued > 0) { auto handled_index = cancel_outstanding_channel_requests(current_thread_id, alt, *channel); assert(handled_index == rnd_idx); } vt_ref<word_t>(r.dest) = rnd_idx; r.current_thread_state = vm_thread_state_t::running; return; } else { requests_queued++; } } // If this is a non-blocking alt request and we are here no channels // were immediately able to handle the request. Try to lock the request // so we can cancel it in all channels. if (!blocking && r.request_mutex.pending_request_lock.try_lock()) { std::lock_guard<std::mutex> lock{ r.request_mutex.pending_request_lock, std::adopt_lock }; // Check if a channel handled the request between the initial queueing and now. if (r.request_mutex.pending_request) { r.request_mutex.pending_request = false; if (debug::is_component_tracing_enabled<debug::component_trace_t::channel>()) debug::log_msg(debug::component_trace_t::channel, debug::log_level_t::debug, "alt: request: non-blocking\n"); // Cancel request in all channels. for (auto i = word_t{ 0 }; i < channel_count; ++i) { auto ch_pointer = reinterpret_cast<pointer_t>(alt->channels[i].alloc); auto ch = at_val<vm_channel_t>(reinterpret_cast<pointer_t>(&ch_pointer)); ch->cancel_request(current_thread_id); } // Index of a non-blocking request is the final index of the alt. vt_ref<word_t>(r.dest) = channel_count; r.current_thread_state = vm_thread_state_t::running; } } } } EXEC_DECL(alt) { exec_alt(r, vm, true /* blocking */); } EXEC_DECL(nbalt) { exec_alt(r, vm, false /* blocking */); } // // Dynamic allocation operations // namespace { void new_instance(vm_registers_t &r, const vm_t &vm, const bool zero_memory) { const auto type_id = vt_ref<word_t>(r.src); const auto &types = r.module_ref->type_section; assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); auto type_desc = types[type_id]; auto new_alloc = !zero_memory ? vm_alloc_t::allocate(type_desc) : vm_alloc_t::allocate(type_desc, vm_alloc_t::zero_memory); if (type_desc->map_in_bytes > 0) vm.get_garbage_collector().track_allocation(new_alloc); pt_ref(r.dest) = new_alloc->get_allocation(); } } EXEC_DECL(new_) { new_instance(r, vm, false /* zero memory */); } EXEC_DECL(newz) { new_instance(r, vm, true /* zero memory */); } EXEC_DECL(mnewz) { auto target_module = at_val<vm_module_ref_t>(r.src); if (target_module == nullptr) throw vm_user_exception{ "Module not loaded" }; const auto type_id = vt_ref<word_t>(r.mid); const auto &types = target_module->type_section; assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); auto type_desc = types[type_id]; auto new_alloc = vm_alloc_t::allocate(type_desc, vm_alloc_t::zero_memory); if (type_desc->map_in_bytes > 0) vm.get_garbage_collector().track_allocation(new_alloc); pt_ref(r.dest) = new_alloc->get_allocation(); } namespace { void new_array(vm_registers_t &r, const vm_t &vm, const bool zero_memory) { assert(r.module_ref != nullptr); const auto &types = r.module_ref->type_section; const auto type_id = vt_ref<word_t>(r.mid); assert(0 <= type_id && type_id < static_cast<word_t>(types.size())); auto type_desc = types[type_id]; dec_ref_count_and_free(at_val<vm_alloc_t>(r.dest)); const auto array_size = vt_ref<word_t>(r.src); auto new_array = !zero_memory ? new vm_array_t(type_desc, array_size) : new vm_array_t(type_desc, array_size, vm_alloc_t::zero_memory); if (type_desc->map_in_bytes > 0) vm.get_garbage_collector().track_allocation(new_array); pt_ref(r.dest) = new_array->get_allocation(); } } EXEC_DECL(newa) { new_array(r, vm, false /* zero memory */); } EXEC_DECL(newaz) { new_array(r, vm, true /* zero memory */); } EXEC_DECL(tcmp) { auto s = at_val<vm_alloc_t>(r.src); auto d = at_val<vm_alloc_t>(r.dest); if (s == nullptr) return; if (d == nullptr || s->alloc_type != d->alloc_type) throw type_violation{}; } // // Stack operations // // [SPEC] 'runt' op code // Undocumented: (http://www.vitanuova.com/inferno/papers/dis.html) // // Inferno implementation: (libinterp/xec.c) // No operands and appears to be a type of NOP instruction. EXEC_DECL(runt) { /* NOP */ } EXEC_DECL(lea) { pt_ref(r.dest) = r.src; } EXEC_DECL(frame) { const auto &types = r.module_ref->type_section; const auto frame_type_id = vt_ref<word_t>(r.src); assert(0 <= frame_type_id && frame_type_id < static_cast<word_t>(types.size())); const auto frame_type = types[frame_type_id]; pt_ref(r.dest) = r.stack.alloc_frame(frame_type)->base(); } EXEC_DECL(mframe) { const auto target_module = at_val<vm_module_ref_t>(r.src); if (target_module == nullptr) throw vm_user_exception{ "Module not loaded" }; const auto &types = target_module->type_section; // Get the function ref index for this module. const auto function_id = vt_ref<word_t>(r.mid); const auto &function_ref = target_module->get_function_ref(function_id); const auto frame_type_id = function_ref.frame_type; assert(0 <= frame_type_id && frame_type_id < static_cast<word_t>(types.size())); const auto frame_type = types[frame_type_id]; pt_ref(r.dest) = r.stack.alloc_frame(frame_type)->base(); } EXEC_DECL(ret) { const auto current_top_frame = r.stack.peek_frame(); assert(current_top_frame != nullptr); r.pc = current_top_frame->prev_pc(); if (current_top_frame->prev_module_ref() != nullptr) { dec_ref_count_and_free(r.module_ref); r.module_ref = current_top_frame->prev_module_ref(); current_top_frame->prev_module_ref() = nullptr; dec_ref_count_and_free(r.mp_base); r.mp_base = r.module_ref->mp_base; } r.stack.pop_frame(); if (debug::is_component_tracing_enabled<debug::component_trace_t::stack>()) debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "exit: function\n"); } EXEC_DECL(call) { auto top_frame = r.stack.push_frame(); top_frame->prev_pc() = r.pc; #ifndef NDEBUG // Validate the stack state const auto frame_base_pointer = pt_ref(r.src); assert(frame_base_pointer != nullptr); assert(top_frame->base() == frame_base_pointer); #endif r.pc = vt_ref<vm_pc_t>(r.dest); debug::log_msg(debug::component_trace_t::stack, debug::log_level_t::debug, "enter: function\n"); } EXEC_DECL(mcall) { auto target_module = at_val<vm_module_ref_t>(r.dest); if (target_module == nullptr) throw vm_user_exception{ "Module not loaded" }; // Get the pc for the function call into this module const auto export_id = vt_ref<word_t>(r.mid); const auto &function_ref = target_module->get_function_ref(export_id);