1969 lines · 64.4 KB · C++
RawDownload
    {        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