// // Dis VM // File: stack.cpp // Author: arr // #include #include #include #include #include #include using disvm::debug::component_trace_t; using disvm::debug::log_level_t; using disvm::runtime::type_descriptor_t; using disvm::runtime::pointer_t; using disvm::runtime::vm_pc_t; using disvm::runtime::vm_module_ref_t; using disvm::runtime::vm_frame_t; using disvm::runtime::vm_stack_t; using disvm::runtime::vm_alloc_t; namespace { struct vm_stack_page { vm_stack_page *prev_page; std::uintptr_t page_limit_addr; vm_frame_t *page_top_frame; // Do not free. This is used as a tracking pointer. void *stack_data() const { // The beginning of the stack data is at the end of the struct. return reinterpret_cast(const_cast(this)) + sizeof(vm_stack_page); } bool contains_frame(const vm_frame_t *frame) const { return reinterpret_cast(stack_data()) <= reinterpret_cast(frame) && reinterpret_cast(frame) < page_limit_addr; } }; struct vm_stack_layout final : public vm_alloc_t { static std::shared_ptr type_desc() { static auto td = type_descriptor_t::create(0); return td; } vm_stack_layout(std::size_t stack_page_size) : vm_alloc_t(type_desc()) , stack_page_size{ stack_page_size } , top_frame{ nullptr } , top_page{ nullptr } { new_page(); } ~vm_stack_layout() { assert(top_page != nullptr); disvm::runtime::free_memory(top_page); } const std::size_t stack_page_size; vm_stack_page *top_page; vm_frame_t *top_frame; // Do not free. This is used as a tracking pointer. // Create a new 'top page' and return stack data address void *new_page() { auto current_stack_page = top_page; top_page = disvm::runtime::alloc_memory(sizeof(vm_stack_page) + stack_page_size); // Initialize the stack allocation. top_page->prev_page = current_stack_page; // The top frame member is only for tracking and so is initialized to null. top_page->page_top_frame = nullptr; top_page->page_limit_addr = reinterpret_cast(top_page)+stack_page_size; if (disvm::debug::is_component_tracing_enabled()) disvm::debug::log_msg(component_trace_t::memory, log_level_t::debug, "alloc: vm stack alloc: %#" PRIxPTR " %#" PRIxPTR, top_page, top_page->page_limit_addr); return top_page->stack_data(); } // Drop the current page and move to the previous one void drop_page() { auto current_stack_page = top_page->prev_page; assert(current_stack_page != nullptr); disvm::runtime::free_memory(top_page); if (disvm::debug::is_component_tracing_enabled()) disvm::debug::log_msg(component_trace_t::memory, log_level_t::debug, "free: vm stack alloc"); // Set the top page top_page = current_stack_page; } }; } vm_frame_t::vm_frame_t(std::shared_ptr td) : frame_type{ std::move(td) } { assert(frame_type != nullptr); // Initialize the new frame init_memory(*frame_type, base()); prev_pc() = runtime_constants::invalid_program_counter; prev_frame() = nullptr; prev_module_ref() = nullptr; if (disvm::debug::is_component_tracing_enabled()) disvm::debug::log_msg(component_trace_t::memory, log_level_t::debug, "init: vm frame: %#" PRIxPTR " %d", this, frame_type->size_in_bytes); } vm_frame_t::~vm_frame_t() { dec_ref_count_and_free(prev_module_ref()); destroy_memory(*frame_type, base()); if (disvm::debug::is_component_tracing_enabled()) disvm::debug::log_msg(component_trace_t::memory, log_level_t::debug, "destroy: vm frame: %#" PRIxPTR, this); } void vm_frame_t::copy_frame_contents(const vm_frame_t &other) { auto this_frame_base = reinterpret_cast(base()); auto this_frame_contents = this_frame_base + sizeof(vm_frame_base_alloc_t); auto other_frame_base = reinterpret_cast(other.base()); auto other_frame_contents = other_frame_base + sizeof(vm_frame_base_alloc_t); assert(frame_type->size_in_bytes > sizeof(vm_frame_base_alloc_t)); std::memcpy(this_frame_contents, other_frame_contents, frame_type->size_in_bytes - sizeof(vm_frame_base_alloc_t)); inc_ref_count_in_memory(*frame_type, this_frame_base); } pointer_t vm_frame_t::base() const { // The beginning of the frame data is at the end of the frame struct. return reinterpret_cast(reinterpret_cast(const_cast(this)) + sizeof(vm_frame_t)); } vm_pc_t &vm_frame_t::prev_pc() const { return base().p_pc; } vm_frame_t *&vm_frame_t::prev_frame() const { return base().p_fp; } vm_module_ref_t *&vm_frame_t::prev_module_ref() const { return base().p_mr; } pointer_t vm_frame_t::fixed_point_register_1() const { const auto offset = vm_frame_constants::fixed_point_register_1_offset(); assert(offset < frame_type->size_in_bytes); auto p = reinterpret_cast(base()) + offset; return reinterpret_cast(p); } pointer_t vm_frame_t::fixed_point_register_2() const { const auto offset = vm_frame_constants::fixed_point_register_2_offset(); assert(offset < frame_type->size_in_bytes); auto p = reinterpret_cast(base()) + offset; return reinterpret_cast(p); } vm_stack_t::vm_stack_t(std::size_t stack_extent) : _mem{ std::make_unique(stack_extent) } { assert(0 < stack_extent && stack_extent < static_cast(std::numeric_limits::max())); log_msg(component_trace_t::memory, log_level_t::debug, "init: vm stack"); } vm_stack_t::~vm_stack_t() { while (nullptr != pop_frame()) { // Pop all frames off the stack } // Free stack memory _mem->release(); _mem.reset(); log_msg(component_trace_t::memory, log_level_t::debug, "destroy: vm stack"); } vm_frame_t *vm_stack_t::alloc_frame(std::shared_ptr frame_type) { assert(frame_type != nullptr); assert(sizeof(vm_frame_base_alloc_t) < frame_type->size_in_bytes && "Requested frame size less than VM frame base size"); auto layout = static_cast(_mem.get()); auto page_top_frame = layout->top_page->page_top_frame; const auto new_frame_size = sizeof(vm_frame_t) + frame_type->size_in_bytes; if (layout->stack_page_size < new_frame_size) throw vm_system_exception{ "Requested stack frame larger than stack page" }; vm_frame_t *new_frame; if (page_top_frame == nullptr) { auto new_frame_addr = layout->top_page->stack_data(); new_frame = ::new(new_frame_addr)vm_frame_t{ std::move(frame_type) }; } else { auto current_page_top_frame_addr = reinterpret_cast(page_top_frame); auto new_frame_addr_maybe = current_page_top_frame_addr + (sizeof(vm_frame_t) + page_top_frame->frame_type->size_in_bytes); // If the new frame is not going to fit on the current page, create a new page. if ((new_frame_addr_maybe + new_frame_size) >= layout->top_page->page_limit_addr) new_frame_addr_maybe = reinterpret_cast(layout->new_page()); new_frame = ::new(reinterpret_cast(new_frame_addr_maybe))vm_frame_t{ std::move(frame_type) }; } // Record the top frame on the page layout->top_page->page_top_frame = new_frame; return new_frame; } vm_frame_t *vm_stack_t::push_frame() { auto layout = static_cast(_mem.get()); auto current_top_frame = layout->top_frame; auto new_top_frame = layout->top_page->page_top_frame; new_top_frame->prev_frame() = current_top_frame; layout->top_frame = new_top_frame; if (disvm::debug::is_component_tracing_enabled()) log_msg(component_trace_t::stack, log_level_t::debug, "update: push vm frame: %#" PRIxPTR, new_top_frame); return new_top_frame; } vm_frame_t *vm_stack_t::pop_frame() { auto layout = static_cast(_mem.get()); auto current_frame = layout->top_frame; if (current_frame == nullptr) return nullptr; { // Dispose of the current top frame and update the current frame. auto previous_frame = current_frame; current_frame = previous_frame->prev_frame(); // Call destructor on previous frame since we allocated // the frame on the stack page. previous_frame->~vm_frame_t(); } layout->top_frame = current_frame; layout->top_page->page_top_frame = current_frame; // Check if the stack page is no longer needed. if (current_frame != nullptr) { // Clean up the stack page allocation if the current frame is on a previous page if (!layout->top_page->contains_frame(current_frame)) { layout->drop_page(); // Ensure the current frame is in the new page. assert(layout->top_page->contains_frame(current_frame)); } } if (disvm::debug::is_component_tracing_enabled()) log_msg(component_trace_t::stack, log_level_t::debug, "update: pop vm frame: %#" PRIxPTR, current_frame); return current_frame; } vm_frame_t *vm_stack_t::peek_frame() const { auto layout = static_cast(_mem.get()); return layout->top_frame; }