core/memory: Migrate over Write{8, 16, 32, 64, Block} to the Memory class
The Write functions are used slightly less than the Read functions, which make these a bit nicer to move over. The only adjustments we really need to make here are to Dynarmic's exclusive monitor instance. We need to keep a reference to the currently active memory instance to perform exclusive read/write operations.
This commit is contained in:
		@@ -45,20 +45,21 @@ public:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void MemoryWrite8(u64 vaddr, u8 value) override {
 | 
			
		||||
        Memory::Write8(vaddr, value);
 | 
			
		||||
        parent.system.Memory().Write8(vaddr, value);
 | 
			
		||||
    }
 | 
			
		||||
    void MemoryWrite16(u64 vaddr, u16 value) override {
 | 
			
		||||
        Memory::Write16(vaddr, value);
 | 
			
		||||
        parent.system.Memory().Write16(vaddr, value);
 | 
			
		||||
    }
 | 
			
		||||
    void MemoryWrite32(u64 vaddr, u32 value) override {
 | 
			
		||||
        Memory::Write32(vaddr, value);
 | 
			
		||||
        parent.system.Memory().Write32(vaddr, value);
 | 
			
		||||
    }
 | 
			
		||||
    void MemoryWrite64(u64 vaddr, u64 value) override {
 | 
			
		||||
        Memory::Write64(vaddr, value);
 | 
			
		||||
        parent.system.Memory().Write64(vaddr, value);
 | 
			
		||||
    }
 | 
			
		||||
    void MemoryWrite128(u64 vaddr, Vector value) override {
 | 
			
		||||
        Memory::Write64(vaddr, value[0]);
 | 
			
		||||
        Memory::Write64(vaddr + 8, value[1]);
 | 
			
		||||
        auto& memory = parent.system.Memory();
 | 
			
		||||
        memory.Write64(vaddr, value[0]);
 | 
			
		||||
        memory.Write64(vaddr + 8, value[1]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
 | 
			
		||||
@@ -266,7 +267,9 @@ void ARM_Dynarmic::PageTableChanged(Common::PageTable& page_table,
 | 
			
		||||
    jit = MakeJit(page_table, new_address_space_size_in_bits);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(std::size_t core_count) : monitor(core_count) {}
 | 
			
		||||
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory_, std::size_t core_count)
 | 
			
		||||
    : monitor(core_count), memory{memory_} {}
 | 
			
		||||
 | 
			
		||||
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
 | 
			
		||||
 | 
			
		||||
void DynarmicExclusiveMonitor::SetExclusive(std::size_t core_index, VAddr addr) {
 | 
			
		||||
@@ -279,29 +282,28 @@ void DynarmicExclusiveMonitor::ClearExclusive() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
 | 
			
		||||
    return monitor.DoExclusiveOperation(core_index, vaddr, 1,
 | 
			
		||||
                                        [&] { Memory::Write8(vaddr, value); });
 | 
			
		||||
    return monitor.DoExclusiveOperation(core_index, vaddr, 1, [&] { memory.Write8(vaddr, value); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
 | 
			
		||||
    return monitor.DoExclusiveOperation(core_index, vaddr, 2,
 | 
			
		||||
                                        [&] { Memory::Write16(vaddr, value); });
 | 
			
		||||
                                        [&] { memory.Write16(vaddr, value); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
 | 
			
		||||
    return monitor.DoExclusiveOperation(core_index, vaddr, 4,
 | 
			
		||||
                                        [&] { Memory::Write32(vaddr, value); });
 | 
			
		||||
                                        [&] { memory.Write32(vaddr, value); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
 | 
			
		||||
    return monitor.DoExclusiveOperation(core_index, vaddr, 8,
 | 
			
		||||
                                        [&] { Memory::Write64(vaddr, value); });
 | 
			
		||||
                                        [&] { memory.Write64(vaddr, value); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
 | 
			
		||||
    return monitor.DoExclusiveOperation(core_index, vaddr, 16, [&] {
 | 
			
		||||
        Memory::Write64(vaddr + 0, value[0]);
 | 
			
		||||
        Memory::Write64(vaddr + 8, value[1]);
 | 
			
		||||
        memory.Write64(vaddr + 0, value[0]);
 | 
			
		||||
        memory.Write64(vaddr + 8, value[1]);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,10 @@
 | 
			
		||||
#include "core/arm/exclusive_monitor.h"
 | 
			
		||||
#include "core/arm/unicorn/arm_unicorn.h"
 | 
			
		||||
 | 
			
		||||
namespace Memory {
 | 
			
		||||
class Memory;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Core {
 | 
			
		||||
 | 
			
		||||
class ARM_Dynarmic_Callbacks;
 | 
			
		||||
@@ -63,7 +67,7 @@ private:
 | 
			
		||||
 | 
			
		||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
 | 
			
		||||
public:
 | 
			
		||||
    explicit DynarmicExclusiveMonitor(std::size_t core_count);
 | 
			
		||||
    explicit DynarmicExclusiveMonitor(Memory::Memory& memory_, std::size_t core_count);
 | 
			
		||||
    ~DynarmicExclusiveMonitor() override;
 | 
			
		||||
 | 
			
		||||
    void SetExclusive(std::size_t core_index, VAddr addr) override;
 | 
			
		||||
@@ -78,6 +82,7 @@ public:
 | 
			
		||||
private:
 | 
			
		||||
    friend class ARM_Dynarmic;
 | 
			
		||||
    Dynarmic::A64::ExclusiveMonitor monitor;
 | 
			
		||||
    Memory::Memory& memory;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace Core
 | 
			
		||||
 
 | 
			
		||||
@@ -66,9 +66,10 @@ Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_ba
 | 
			
		||||
 | 
			
		||||
Cpu::~Cpu() = default;
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
 | 
			
		||||
std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(
 | 
			
		||||
    [[maybe_unused]] Memory::Memory& memory, [[maybe_unused]] std::size_t num_cores) {
 | 
			
		||||
#ifdef ARCHITECTURE_x86_64
 | 
			
		||||
    return std::make_unique<DynarmicExclusiveMonitor>(num_cores);
 | 
			
		||||
    return std::make_unique<DynarmicExclusiveMonitor>(memory, num_cores);
 | 
			
		||||
#else
 | 
			
		||||
    // TODO(merry): Passthrough exclusive monitor
 | 
			
		||||
    return nullptr;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,10 @@ namespace Core::Timing {
 | 
			
		||||
class CoreTiming;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Memory {
 | 
			
		||||
class Memory;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Core {
 | 
			
		||||
 | 
			
		||||
class ARM_Interface;
 | 
			
		||||
@@ -86,7 +90,19 @@ public:
 | 
			
		||||
 | 
			
		||||
    void Shutdown();
 | 
			
		||||
 | 
			
		||||
    static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an exclusive monitor to handle exclusive reads/writes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param memory The current memory subsystem that the monitor may wish
 | 
			
		||||
     *               to keep track of.
 | 
			
		||||
     *
 | 
			
		||||
     * @param num_cores The number of cores to assume about the CPU.
 | 
			
		||||
     *
 | 
			
		||||
     * @returns The constructed exclusive monitor instance, or nullptr if the current
 | 
			
		||||
     *          CPU backend is unable to use an exclusive monitor.
 | 
			
		||||
     */
 | 
			
		||||
    static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
 | 
			
		||||
                                                                  std::size_t num_cores);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void Reschedule();
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ CpuCoreManager::~CpuCoreManager() = default;
 | 
			
		||||
 | 
			
		||||
void CpuCoreManager::Initialize() {
 | 
			
		||||
    barrier = std::make_unique<CpuBarrier>();
 | 
			
		||||
    exclusive_monitor = Cpu::MakeExclusiveMonitor(cores.size());
 | 
			
		||||
    exclusive_monitor = Cpu::MakeExclusiveMonitor(system.Memory(), cores.size());
 | 
			
		||||
 | 
			
		||||
    for (std::size_t index = 0; index < cores.size(); ++index) {
 | 
			
		||||
        cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index);
 | 
			
		||||
 
 | 
			
		||||
@@ -508,8 +508,9 @@ static void RemoveBreakpoint(BreakpointType type, VAddr addr) {
 | 
			
		||||
              bp->second.len, bp->second.addr, static_cast<int>(type));
 | 
			
		||||
 | 
			
		||||
    if (type == BreakpointType::Execute) {
 | 
			
		||||
        Memory::WriteBlock(bp->second.addr, bp->second.inst.data(), bp->second.inst.size());
 | 
			
		||||
        Core::System::GetInstance().InvalidateCpuInstructionCaches();
 | 
			
		||||
        auto& system = Core::System::GetInstance();
 | 
			
		||||
        system.Memory().WriteBlock(bp->second.addr, bp->second.inst.data(), bp->second.inst.size());
 | 
			
		||||
        system.InvalidateCpuInstructionCaches();
 | 
			
		||||
    }
 | 
			
		||||
    p.erase(addr);
 | 
			
		||||
}
 | 
			
		||||
@@ -993,14 +994,14 @@ static void WriteMemory() {
 | 
			
		||||
    const u64 len = HexToLong(start_offset, static_cast<u64>(len_pos - start_offset));
 | 
			
		||||
 | 
			
		||||
    auto& system = Core::System::GetInstance();
 | 
			
		||||
    const auto& memory = system.Memory();
 | 
			
		||||
    auto& memory = system.Memory();
 | 
			
		||||
    if (!memory.IsValidVirtualAddress(addr)) {
 | 
			
		||||
        return SendReply("E00");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> data(len);
 | 
			
		||||
    GdbHexToMem(data.data(), len_pos + 1, len);
 | 
			
		||||
    Memory::WriteBlock(addr, data.data(), len);
 | 
			
		||||
    memory.WriteBlock(addr, data.data(), len);
 | 
			
		||||
    system.InvalidateCpuInstructionCaches();
 | 
			
		||||
    SendReply("OK");
 | 
			
		||||
}
 | 
			
		||||
@@ -1058,13 +1059,14 @@ static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) {
 | 
			
		||||
    breakpoint.addr = addr;
 | 
			
		||||
    breakpoint.len = len;
 | 
			
		||||
 | 
			
		||||
    auto& memory = Core::System::GetInstance().Memory();
 | 
			
		||||
    auto& system = Core::System::GetInstance();
 | 
			
		||||
    auto& memory = system.Memory();
 | 
			
		||||
    memory.ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size());
 | 
			
		||||
 | 
			
		||||
    static constexpr std::array<u8, 4> btrap{0x00, 0x7d, 0x20, 0xd4};
 | 
			
		||||
    if (type == BreakpointType::Execute) {
 | 
			
		||||
        Memory::WriteBlock(addr, btrap.data(), btrap.size());
 | 
			
		||||
        Core::System::GetInstance().InvalidateCpuInstructionCaches();
 | 
			
		||||
        memory.WriteBlock(addr, btrap.data(), btrap.size());
 | 
			
		||||
        system.InvalidateCpuInstructionCaches();
 | 
			
		||||
    }
 | 
			
		||||
    p.insert({addr, breakpoint});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32
 | 
			
		||||
        return ERR_INVALID_STATE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Memory::Write32(address, static_cast<u32>(value + 1));
 | 
			
		||||
    memory.Write32(address, static_cast<u32>(value + 1));
 | 
			
		||||
    return SignalToAddressOnly(address, num_to_wake);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -117,7 +117,7 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
 | 
			
		||||
        return ERR_INVALID_STATE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Memory::Write32(address, static_cast<u32>(updated_value));
 | 
			
		||||
    memory.Write32(address, static_cast<u32>(updated_value));
 | 
			
		||||
    WakeThreads(waiting_threads, num_to_wake);
 | 
			
		||||
    return RESULT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
@@ -151,7 +151,7 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (should_decrement) {
 | 
			
		||||
        Memory::Write32(address, static_cast<u32>(cur_value - 1));
 | 
			
		||||
        memory.Write32(address, static_cast<u32>(cur_value - 1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Short-circuit without rescheduling, if timeout is zero.
 | 
			
		||||
 
 | 
			
		||||
@@ -274,8 +274,8 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Copy the translated command buffer back into the thread's command buffer area.
 | 
			
		||||
    Memory::WriteBlock(owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
 | 
			
		||||
                       dst_cmdbuf.size() * sizeof(u32));
 | 
			
		||||
    memory.WriteBlock(owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
 | 
			
		||||
                      dst_cmdbuf.size() * sizeof(u32));
 | 
			
		||||
 | 
			
		||||
    return RESULT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
@@ -311,10 +311,11 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
 | 
			
		||||
        size = buffer_size; // TODO(bunnei): This needs to be HW tested
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto& memory = Core::System::GetInstance().Memory();
 | 
			
		||||
    if (is_buffer_b) {
 | 
			
		||||
        Memory::WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
 | 
			
		||||
        memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
 | 
			
		||||
    } else {
 | 
			
		||||
        Memory::WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
 | 
			
		||||
        memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return size;
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@ ResultCode Mutex::Release(VAddr address) {
 | 
			
		||||
 | 
			
		||||
    // There are no more threads waiting for the mutex, release it completely.
 | 
			
		||||
    if (thread == nullptr) {
 | 
			
		||||
        Memory::Write32(address, 0);
 | 
			
		||||
        system.Memory().Write32(address, 0);
 | 
			
		||||
        return RESULT_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -132,7 +132,7 @@ ResultCode Mutex::Release(VAddr address) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Grant the mutex to the next waiting thread and resume it.
 | 
			
		||||
    Memory::Write32(address, mutex_value);
 | 
			
		||||
    system.Memory().Write32(address, mutex_value);
 | 
			
		||||
 | 
			
		||||
    ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
 | 
			
		||||
    thread->ResumeFromWait();
 | 
			
		||||
 
 | 
			
		||||
@@ -1120,7 +1120,7 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H
 | 
			
		||||
        std::fill(ctx.vector_registers.begin() + 16, ctx.vector_registers.end(), u128{});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Memory::WriteBlock(thread_context, &ctx, sizeof(ctx));
 | 
			
		||||
    system.Memory().WriteBlock(thread_context, &ctx, sizeof(ctx));
 | 
			
		||||
    return RESULT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1280,20 +1280,21 @@ static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_add
 | 
			
		||||
        return ERR_INVALID_HANDLE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto& memory = system.Memory();
 | 
			
		||||
    const auto& vm_manager = process->VMManager();
 | 
			
		||||
    const MemoryInfo memory_info = vm_manager.QueryMemory(address);
 | 
			
		||||
 | 
			
		||||
    Memory::Write64(memory_info_address, memory_info.base_address);
 | 
			
		||||
    Memory::Write64(memory_info_address + 8, memory_info.size);
 | 
			
		||||
    Memory::Write32(memory_info_address + 16, memory_info.state);
 | 
			
		||||
    Memory::Write32(memory_info_address + 20, memory_info.attributes);
 | 
			
		||||
    Memory::Write32(memory_info_address + 24, memory_info.permission);
 | 
			
		||||
    Memory::Write32(memory_info_address + 32, memory_info.ipc_ref_count);
 | 
			
		||||
    Memory::Write32(memory_info_address + 28, memory_info.device_ref_count);
 | 
			
		||||
    Memory::Write32(memory_info_address + 36, 0);
 | 
			
		||||
    memory.Write64(memory_info_address, memory_info.base_address);
 | 
			
		||||
    memory.Write64(memory_info_address + 8, memory_info.size);
 | 
			
		||||
    memory.Write32(memory_info_address + 16, memory_info.state);
 | 
			
		||||
    memory.Write32(memory_info_address + 20, memory_info.attributes);
 | 
			
		||||
    memory.Write32(memory_info_address + 24, memory_info.permission);
 | 
			
		||||
    memory.Write32(memory_info_address + 32, memory_info.ipc_ref_count);
 | 
			
		||||
    memory.Write32(memory_info_address + 28, memory_info.device_ref_count);
 | 
			
		||||
    memory.Write32(memory_info_address + 36, 0);
 | 
			
		||||
 | 
			
		||||
    // Page info appears to be currently unused by the kernel and is always set to zero.
 | 
			
		||||
    Memory::Write32(page_info_address, 0);
 | 
			
		||||
    memory.Write32(page_info_address, 0);
 | 
			
		||||
 | 
			
		||||
    return RESULT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
@@ -2290,12 +2291,13 @@ static ResultCode GetProcessList(Core::System& system, u32* out_num_processes,
 | 
			
		||||
        return ERR_INVALID_ADDRESS_STATE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto& memory = system.Memory();
 | 
			
		||||
    const auto& process_list = kernel.GetProcessList();
 | 
			
		||||
    const auto num_processes = process_list.size();
 | 
			
		||||
    const auto copy_amount = std::min(std::size_t{out_process_ids_size}, num_processes);
 | 
			
		||||
 | 
			
		||||
    for (std::size_t i = 0; i < copy_amount; ++i) {
 | 
			
		||||
        Memory::Write64(out_process_ids, process_list[i]->GetProcessID());
 | 
			
		||||
        memory.Write64(out_process_ids, process_list[i]->GetProcessID());
 | 
			
		||||
        out_process_ids += sizeof(u64);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -2329,13 +2331,14 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
 | 
			
		||||
        return ERR_INVALID_ADDRESS_STATE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto& memory = system.Memory();
 | 
			
		||||
    const auto& thread_list = current_process->GetThreadList();
 | 
			
		||||
    const auto num_threads = thread_list.size();
 | 
			
		||||
    const auto copy_amount = std::min(std::size_t{out_thread_ids_size}, num_threads);
 | 
			
		||||
 | 
			
		||||
    auto list_iter = thread_list.cbegin();
 | 
			
		||||
    for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) {
 | 
			
		||||
        Memory::Write64(out_thread_ids, (*list_iter)->GetThreadID());
 | 
			
		||||
        memory.Write64(out_thread_ids, (*list_iter)->GetThreadID());
 | 
			
		||||
        out_thread_ids += sizeof(u64);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -58,35 +58,6 @@ u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
 | 
			
		||||
u8* GetPointerFromVMA(VAddr vaddr) {
 | 
			
		||||
    return ::Memory::GetPointerFromVMA(*Core::System::GetInstance().CurrentProcess(), vaddr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
void Write(const VAddr vaddr, const T data) {
 | 
			
		||||
    u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
 | 
			
		||||
    if (page_pointer != nullptr) {
 | 
			
		||||
        // NOTE: Avoid adding any extra logic to this fast-path block
 | 
			
		||||
        std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T));
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
 | 
			
		||||
    switch (type) {
 | 
			
		||||
    case Common::PageType::Unmapped:
 | 
			
		||||
        LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
 | 
			
		||||
                  static_cast<u32>(data), vaddr);
 | 
			
		||||
        return;
 | 
			
		||||
    case Common::PageType::Memory:
 | 
			
		||||
        ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
 | 
			
		||||
        break;
 | 
			
		||||
    case Common::PageType::RasterizerCachedMemory: {
 | 
			
		||||
        u8* const host_ptr{GetPointerFromVMA(vaddr)};
 | 
			
		||||
        Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T));
 | 
			
		||||
        std::memcpy(host_ptr, &data, sizeof(T));
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        UNREACHABLE();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
// Implementation class used to keep the specifics of the memory subsystem hidden
 | 
			
		||||
@@ -195,6 +166,22 @@ struct Memory::Impl {
 | 
			
		||||
        return Read<u64_le>(addr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Write8(const VAddr addr, const u8 data) {
 | 
			
		||||
        Write<u8>(addr, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Write16(const VAddr addr, const u16 data) {
 | 
			
		||||
        Write<u16_le>(addr, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Write32(const VAddr addr, const u32 data) {
 | 
			
		||||
        Write<u32_le>(addr, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Write64(const VAddr addr, const u64 data) {
 | 
			
		||||
        Write<u64_le>(addr, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string ReadCString(VAddr vaddr, std::size_t max_length) {
 | 
			
		||||
        std::string string;
 | 
			
		||||
        string.reserve(max_length);
 | 
			
		||||
@@ -259,6 +246,53 @@ struct Memory::Impl {
 | 
			
		||||
        ReadBlock(*system.CurrentProcess(), src_addr, dest_buffer, size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer,
 | 
			
		||||
                    const std::size_t size) {
 | 
			
		||||
        const auto& page_table = process.VMManager().page_table;
 | 
			
		||||
        std::size_t remaining_size = size;
 | 
			
		||||
        std::size_t page_index = dest_addr >> PAGE_BITS;
 | 
			
		||||
        std::size_t page_offset = dest_addr & PAGE_MASK;
 | 
			
		||||
 | 
			
		||||
        while (remaining_size > 0) {
 | 
			
		||||
            const std::size_t copy_amount =
 | 
			
		||||
                std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
 | 
			
		||||
            const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
 | 
			
		||||
 | 
			
		||||
            switch (page_table.attributes[page_index]) {
 | 
			
		||||
            case Common::PageType::Unmapped: {
 | 
			
		||||
                LOG_ERROR(HW_Memory,
 | 
			
		||||
                          "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
 | 
			
		||||
                          current_vaddr, dest_addr, size);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case Common::PageType::Memory: {
 | 
			
		||||
                DEBUG_ASSERT(page_table.pointers[page_index]);
 | 
			
		||||
 | 
			
		||||
                u8* const dest_ptr = page_table.pointers[page_index] + page_offset;
 | 
			
		||||
                std::memcpy(dest_ptr, src_buffer, copy_amount);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case Common::PageType::RasterizerCachedMemory: {
 | 
			
		||||
                u8* const host_ptr = GetPointerFromVMA(process, current_vaddr);
 | 
			
		||||
                system.GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
 | 
			
		||||
                std::memcpy(host_ptr, src_buffer, copy_amount);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                UNREACHABLE();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            page_index++;
 | 
			
		||||
            page_offset = 0;
 | 
			
		||||
            src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
 | 
			
		||||
            remaining_size -= copy_amount;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) {
 | 
			
		||||
        WriteBlock(*system.CurrentProcess(), dest_addr, src_buffer, size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std::size_t size) {
 | 
			
		||||
        const auto& page_table = process.VMManager().page_table;
 | 
			
		||||
        std::size_t remaining_size = size;
 | 
			
		||||
@@ -501,6 +535,46 @@ struct Memory::Impl {
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a particular data type to memory at the given virtual address.
 | 
			
		||||
     *
 | 
			
		||||
     * @param vaddr The virtual address to write the data type to.
 | 
			
		||||
     *
 | 
			
		||||
     * @tparam T The data type to write to memory. This type *must* be
 | 
			
		||||
     *           trivially copyable, otherwise the behavior of this function
 | 
			
		||||
     *           is undefined.
 | 
			
		||||
     *
 | 
			
		||||
     * @returns The instance of T write to the specified virtual address.
 | 
			
		||||
     */
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    void Write(const VAddr vaddr, const T data) {
 | 
			
		||||
        u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
 | 
			
		||||
        if (page_pointer != nullptr) {
 | 
			
		||||
            // NOTE: Avoid adding any extra logic to this fast-path block
 | 
			
		||||
            std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
 | 
			
		||||
        switch (type) {
 | 
			
		||||
        case Common::PageType::Unmapped:
 | 
			
		||||
            LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
 | 
			
		||||
                      static_cast<u32>(data), vaddr);
 | 
			
		||||
            return;
 | 
			
		||||
        case Common::PageType::Memory:
 | 
			
		||||
            ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
 | 
			
		||||
            break;
 | 
			
		||||
        case Common::PageType::RasterizerCachedMemory: {
 | 
			
		||||
            u8* const host_ptr{GetPointerFromVMA(vaddr)};
 | 
			
		||||
            system.GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T));
 | 
			
		||||
            std::memcpy(host_ptr, &data, sizeof(T));
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
            UNREACHABLE();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Core::System& system;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -562,6 +636,22 @@ u64 Memory::Read64(const VAddr addr) {
 | 
			
		||||
    return impl->Read64(addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Memory::Write8(VAddr addr, u8 data) {
 | 
			
		||||
    impl->Write8(addr, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Memory::Write16(VAddr addr, u16 data) {
 | 
			
		||||
    impl->Write16(addr, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Memory::Write32(VAddr addr, u32 data) {
 | 
			
		||||
    impl->Write32(addr, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Memory::Write64(VAddr addr, u64 data) {
 | 
			
		||||
    impl->Write64(addr, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string Memory::ReadCString(VAddr vaddr, std::size_t max_length) {
 | 
			
		||||
    return impl->ReadCString(vaddr, max_length);
 | 
			
		||||
}
 | 
			
		||||
@@ -575,6 +665,15 @@ void Memory::ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_
 | 
			
		||||
    impl->ReadBlock(src_addr, dest_buffer, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Memory::WriteBlock(const Kernel::Process& process, VAddr dest_addr, const void* src_buffer,
 | 
			
		||||
                        std::size_t size) {
 | 
			
		||||
    impl->WriteBlock(process, dest_addr, src_buffer, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Memory::WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) {
 | 
			
		||||
    impl->WriteBlock(dest_addr, src_buffer, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Memory::ZeroBlock(const Kernel::Process& process, VAddr dest_addr, std::size_t size) {
 | 
			
		||||
    impl->ZeroBlock(process, dest_addr, size);
 | 
			
		||||
}
 | 
			
		||||
@@ -612,67 +711,4 @@ bool IsKernelVirtualAddress(const VAddr vaddr) {
 | 
			
		||||
    return KERNEL_REGION_VADDR <= vaddr && vaddr < KERNEL_REGION_END;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Write8(const VAddr addr, const u8 data) {
 | 
			
		||||
    Write<u8>(addr, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Write16(const VAddr addr, const u16 data) {
 | 
			
		||||
    Write<u16_le>(addr, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Write32(const VAddr addr, const u32 data) {
 | 
			
		||||
    Write<u32_le>(addr, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Write64(const VAddr addr, const u64 data) {
 | 
			
		||||
    Write<u64_le>(addr, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer,
 | 
			
		||||
                const std::size_t size) {
 | 
			
		||||
    const auto& page_table = process.VMManager().page_table;
 | 
			
		||||
    std::size_t remaining_size = size;
 | 
			
		||||
    std::size_t page_index = dest_addr >> PAGE_BITS;
 | 
			
		||||
    std::size_t page_offset = dest_addr & PAGE_MASK;
 | 
			
		||||
 | 
			
		||||
    while (remaining_size > 0) {
 | 
			
		||||
        const std::size_t copy_amount =
 | 
			
		||||
            std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
 | 
			
		||||
        const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
 | 
			
		||||
 | 
			
		||||
        switch (page_table.attributes[page_index]) {
 | 
			
		||||
        case Common::PageType::Unmapped: {
 | 
			
		||||
            LOG_ERROR(HW_Memory,
 | 
			
		||||
                      "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
 | 
			
		||||
                      current_vaddr, dest_addr, size);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case Common::PageType::Memory: {
 | 
			
		||||
            DEBUG_ASSERT(page_table.pointers[page_index]);
 | 
			
		||||
 | 
			
		||||
            u8* dest_ptr = page_table.pointers[page_index] + page_offset;
 | 
			
		||||
            std::memcpy(dest_ptr, src_buffer, copy_amount);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case Common::PageType::RasterizerCachedMemory: {
 | 
			
		||||
            const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
 | 
			
		||||
            Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
 | 
			
		||||
            std::memcpy(host_ptr, src_buffer, copy_amount);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
            UNREACHABLE();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        page_index++;
 | 
			
		||||
        page_offset = 0;
 | 
			
		||||
        src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
 | 
			
		||||
        remaining_size -= copy_amount;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) {
 | 
			
		||||
    WriteBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_buffer, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Memory
 | 
			
		||||
 
 | 
			
		||||
@@ -192,6 +192,50 @@ public:
 | 
			
		||||
     */
 | 
			
		||||
    u64 Read64(VAddr addr);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes an 8-bit unsigned integer to the given virtual address in
 | 
			
		||||
     * the current process' address space.
 | 
			
		||||
     *
 | 
			
		||||
     * @param addr The virtual address to write the 8-bit unsigned integer to.
 | 
			
		||||
     * @param data The 8-bit unsigned integer to write to the given virtual address.
 | 
			
		||||
     *
 | 
			
		||||
     * @post The memory at the given virtual address contains the specified data value.
 | 
			
		||||
     */
 | 
			
		||||
    void Write8(VAddr addr, u8 data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a 16-bit unsigned integer to the given virtual address in
 | 
			
		||||
     * the current process' address space.
 | 
			
		||||
     *
 | 
			
		||||
     * @param addr The virtual address to write the 16-bit unsigned integer to.
 | 
			
		||||
     * @param data The 16-bit unsigned integer to write to the given virtual address.
 | 
			
		||||
     *
 | 
			
		||||
     * @post The memory range [addr, sizeof(data)) contains the given data value.
 | 
			
		||||
     */
 | 
			
		||||
    void Write16(VAddr addr, u16 data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a 32-bit unsigned integer to the given virtual address in
 | 
			
		||||
     * the current process' address space.
 | 
			
		||||
     *
 | 
			
		||||
     * @param addr The virtual address to write the 32-bit unsigned integer to.
 | 
			
		||||
     * @param data The 32-bit unsigned integer to write to the given virtual address.
 | 
			
		||||
     *
 | 
			
		||||
     * @post The memory range [addr, sizeof(data)) contains the given data value.
 | 
			
		||||
     */
 | 
			
		||||
    void Write32(VAddr addr, u32 data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a 64-bit unsigned integer to the given virtual address in
 | 
			
		||||
     * the current process' address space.
 | 
			
		||||
     *
 | 
			
		||||
     * @param addr The virtual address to write the 64-bit unsigned integer to.
 | 
			
		||||
     * @param data The 64-bit unsigned integer to write to the given virtual address.
 | 
			
		||||
     *
 | 
			
		||||
     * @post The memory range [addr, sizeof(data)) contains the given data value.
 | 
			
		||||
     */
 | 
			
		||||
    void Write64(VAddr addr, u64 data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reads a null-terminated string from the given virtual address.
 | 
			
		||||
     * This function will continually read characters until either:
 | 
			
		||||
@@ -247,6 +291,50 @@ public:
 | 
			
		||||
     */
 | 
			
		||||
    void ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a range of bytes into a given process' address space at the specified
 | 
			
		||||
     * virtual address.
 | 
			
		||||
     *
 | 
			
		||||
     * @param process    The process to write data into the address space of.
 | 
			
		||||
     * @param dest_addr  The destination virtual address to begin writing the data at.
 | 
			
		||||
     * @param src_buffer The data to write into the process' address space.
 | 
			
		||||
     * @param size       The size of the data to write, in bytes.
 | 
			
		||||
     *
 | 
			
		||||
     * @post The address range [dest_addr, size) in the process' address space
 | 
			
		||||
     *       contains the data that was within src_buffer.
 | 
			
		||||
     *
 | 
			
		||||
     * @post If an attempt is made to write into an unmapped region of memory, the writes
 | 
			
		||||
     *       will be ignored and an error will be logged.
 | 
			
		||||
     *
 | 
			
		||||
     * @post If a write is performed into a region of memory that is considered cached
 | 
			
		||||
     *       rasterizer memory, will cause the currently active rasterizer to be notified
 | 
			
		||||
     *       and will mark that region as invalidated to caches that the active
 | 
			
		||||
     *       graphics backend may be maintaining over the course of execution.
 | 
			
		||||
     */
 | 
			
		||||
    void WriteBlock(const Kernel::Process& process, VAddr dest_addr, const void* src_buffer,
 | 
			
		||||
                    std::size_t size);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a range of bytes into the current process' address space at the specified
 | 
			
		||||
     * virtual address.
 | 
			
		||||
     *
 | 
			
		||||
     * @param dest_addr  The destination virtual address to begin writing the data at.
 | 
			
		||||
     * @param src_buffer The data to write into the current process' address space.
 | 
			
		||||
     * @param size       The size of the data to write, in bytes.
 | 
			
		||||
     *
 | 
			
		||||
     * @post The address range [dest_addr, size) in the current process' address space
 | 
			
		||||
     *       contains the data that was within src_buffer.
 | 
			
		||||
     *
 | 
			
		||||
     * @post If an attempt is made to write into an unmapped region of memory, the writes
 | 
			
		||||
     *       will be ignored and an error will be logged.
 | 
			
		||||
     *
 | 
			
		||||
     * @post If a write is performed into a region of memory that is considered cached
 | 
			
		||||
     *       rasterizer memory, will cause the currently active rasterizer to be notified
 | 
			
		||||
     *       and will mark that region as invalidated to caches that the active
 | 
			
		||||
     *       graphics backend may be maintaining over the course of execution.
 | 
			
		||||
     */
 | 
			
		||||
    void WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fills the specified address range within a process' address space with zeroes.
 | 
			
		||||
     *
 | 
			
		||||
@@ -320,13 +408,4 @@ void SetCurrentPageTable(Kernel::Process& process);
 | 
			
		||||
/// Determines if the given VAddr is a kernel address
 | 
			
		||||
bool IsKernelVirtualAddress(VAddr vaddr);
 | 
			
		||||
 | 
			
		||||
void Write8(VAddr addr, u8 data);
 | 
			
		||||
void Write16(VAddr addr, u16 data);
 | 
			
		||||
void Write32(VAddr addr, u32 data);
 | 
			
		||||
void Write64(VAddr addr, u64 data);
 | 
			
		||||
 | 
			
		||||
void WriteBlock(const Kernel::Process& process, VAddr dest_addr, const void* src_buffer,
 | 
			
		||||
                std::size_t size);
 | 
			
		||||
void WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size);
 | 
			
		||||
 | 
			
		||||
} // namespace Memory
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
 | 
			
		||||
    WriteBlock(SanitizeAddress(address), data, size);
 | 
			
		||||
    system.Memory().WriteBlock(SanitizeAddress(address), data, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 StandardVmCallbacks::HidKeysDown() {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,16 +34,16 @@ u64 MemoryReadWidth(Memory::Memory& memory, u32 width, VAddr addr) {
 | 
			
		||||
void MemoryWriteWidth(Memory::Memory& memory, u32 width, VAddr addr, u64 value) {
 | 
			
		||||
    switch (width) {
 | 
			
		||||
    case 1:
 | 
			
		||||
        Memory::Write8(addr, static_cast<u8>(value));
 | 
			
		||||
        memory.Write8(addr, static_cast<u8>(value));
 | 
			
		||||
        break;
 | 
			
		||||
    case 2:
 | 
			
		||||
        Memory::Write16(addr, static_cast<u16>(value));
 | 
			
		||||
        memory.Write16(addr, static_cast<u16>(value));
 | 
			
		||||
        break;
 | 
			
		||||
    case 4:
 | 
			
		||||
        Memory::Write32(addr, static_cast<u32>(value));
 | 
			
		||||
        memory.Write32(addr, static_cast<u32>(value));
 | 
			
		||||
        break;
 | 
			
		||||
    case 8:
 | 
			
		||||
        Memory::Write64(addr, value);
 | 
			
		||||
        memory.Write64(addr, value);
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        UNREACHABLE();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user