#pragma once #include #include namespace Interpreter { class ExecutionContextWithDefaultMemory; /** * Interface to access memory using virtual addresses. * * Exposes HostMemoryBackedPages that can directly be written to, and a * direct mapping from virtual addresses to physical ones to be used for slow * fallback paths in case the memory is not host memory backed. */ struct PageTable { PageTable() noexcept; void Insert(Memory::PhysicalMemory& mem, uint32_t vstart, uint32_t pstart, uint32_t size); void Remove(uint32_t vstart, uint32_t size); Memory::HostMemoryBackedPage LookupHostMemory(uint32_t vaddr) const { return host_memory[vaddr >> 12]; } static constexpr uint32_t num_pages = (1 << 20); // List mapping virtual memory page indexes to their corresponding physical memory page // (one entry per page in the entire 32-bit address space) std::array host_memory; // List mapping virtual memory page indexes to their corresponding physical memory address std::array physical_addresses; // 0xffffffff signalizes an unmapped address /** * List mapping physical memory page indexes to a list of virtual memory addresses that are mapped to them * Typically, for each 3DS process there are at most 2 (usually just 1) virtual pages that map to the same physical page */ std::array, num_pages> virtual_addresses_for; }; class ProcessorWithDefaultMemory : public Processor, Memory::PhysicalMemorySubscriber { protected: Setup& setup; PageTable page_table; ProcessorWithDefaultMemory(Interpreter::Setup& setup); friend class ExecutionContextWithDefaultMemory; static std::optional TranslateVirtualAddress(const PageTable& page_table, uint32_t vaddr) { auto paddr_start = page_table.physical_addresses[vaddr >> 12]; if (paddr_start == 0xffffffff) { throw std::runtime_error("Virtual address " + fmt::format("{:#x}", vaddr) + " is not mapped"); } return paddr_start + (vaddr & 0xfff); } template static void WriteVirtualMemory(Memory::PhysicalMemory& mem, const PageTable& page_table, uint32_t address, T value) { auto page = page_table.LookupHostMemory(address); if (page) { Memory::Write(page, address & 0xfff, value); return; } // Else fall back to slow handler-based write auto opt_paddr = TranslateVirtualAddress(page_table, address); if (!opt_paddr) { throw std::runtime_error(fmt::format("Invalid virtual address {:#x}", address)); } Memory::WriteLegacy(mem, *opt_paddr, value); } template static T ReadVirtualMemory(Memory::PhysicalMemory& mem, const PageTable& page_table, uint32_t address) { auto page = page_table.LookupHostMemory(address); if (page) { return Memory::Read(page, address & 0xfff); } // Else fall back to slow handler-based read auto opt_paddr = TranslateVirtualAddress(page_table, address); if (!opt_paddr) { throw std::runtime_error(fmt::format("Invalid virtual address {:#x}", address)); } return Memory::ReadLegacy(mem, *opt_paddr); } // Narrow interface to the more refined return type ExecutionContextWithDefaultMemory ExecutionContext* CreateExecutionContextImpl() final; virtual ExecutionContextWithDefaultMemory* CreateExecutionContextImpl2() = 0; void OnBackedByHostMemory(Memory::HostMemoryBackedPage, uint32_t address) override; void OnUnbackedByHostMemory(uint32_t address) override; public: Setup& GetSetup() { return setup; } void WriteVirtualMemory8(uint32_t virt_address, const uint8_t value) override; void WriteVirtualMemory16(uint32_t virt_address, const uint16_t value) override; void WriteVirtualMemory32(uint32_t virt_address, const uint32_t value) override; uint8_t ReadVirtualMemory8(uint32_t virt_address) override; uint16_t ReadVirtualMemory16(uint32_t virt_address) override; uint32_t ReadVirtualMemory32(uint32_t virt_address) override; void OnVirtualMemoryMapped(uint32_t phys_addr, uint32_t size, uint32_t vaddr) override; void OnVirtualMemoryUnmapped(uint32_t vaddr, uint32_t size) override; }; class ExecutionContextWithDefaultMemory : public ExecutionContext { Memory::PhysicalMemory& mem; const PageTable& page_table; friend class ProcessorWithDefaultMemory; protected: ExecutionContextWithDefaultMemory(Processor& parent_, Memory::PhysicalMemory& mem_) : ExecutionContext(parent_), mem(mem_), page_table(static_cast(parent).page_table) { } std::optional TranslateVirtualAddress(uint32_t vaddr) { return ProcessorWithDefaultMemory::TranslateVirtualAddress(page_table, vaddr); } public: // Re-define memory accessors here to get better codegen: // The JIT engines must be able to emit calls to memory read handlers // without them being wrapped in unnecessary layers of function calls. template void WriteVirtualMemory(uint32_t address, T value) { ProcessorWithDefaultMemory::WriteVirtualMemory(mem, page_table, address, value); } template T ReadVirtualMemory(uint32_t address) { return ProcessorWithDefaultMemory::ReadVirtualMemory(mem, page_table, address); } }; inline ExecutionContext* ProcessorWithDefaultMemory::CreateExecutionContextImpl() { return CreateExecutionContextImpl2(); } } // namespace Interpreter