mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-22 18:59:04 +02:00
Memory::AddressSpace is where a CPU's addresses point (base) and its block shadow, with the unchecked accessors and block shadow helpers as methods. The main CPU's is Memory::g_mainSpace, kept in step with Memory::base and blockShadow, and MIPSState::space points at it. The CPU cores now go through their state's space instead of the globals: the interpreter's and IR interpreter's loads and stores (which also saves the IR interpreter reloading the global base), the IR interpreter's block lookup, the block table's shadow entries, and the native JITs' memory base register, block shadow lookups and pointerified registers. Address validity and everything outside the cores still use the globals. No change for the main CPU; this is so another CPU (the Media Engine) can see memory differently. Benchmarked against master (God of War: Chains of Olympus, Tekken 6): JIT instructions retired within noise, IR interpreter 2% fewer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
706 lines
23 KiB
C++
706 lines
23 KiB
C++
// Copyright (C) 2003 Dolphin Project / 2012 PPSSPP Project
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
#include "Common/CommonWindows.h"
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <mutex>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/MemArena.h"
|
|
#include "Common/MemoryUtil.h"
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
|
|
#include "Core/System.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "Core/Debugger/MemBlockInfo.h"
|
|
#include "Core/HDRemaster.h"
|
|
#include "Core/HLE/ReplaceTables.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/MemFault.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/MIPS/JitCommon/JitCommon.h"
|
|
#include "Common/Thread/ParallelLoop.h"
|
|
|
|
namespace Memory {
|
|
|
|
// The base pointer to the auto-mirrored arena.
|
|
u8* base = nullptr;
|
|
|
|
u8 *blockShadow = nullptr;
|
|
AddressSpace g_mainSpace;
|
|
static const size_t BLOCK_SHADOW_SIZE = (size_t)BLOCK_SHADOW_MASK + 1;
|
|
|
|
// The MemArena class
|
|
MemArena g_arena;
|
|
// ==============
|
|
|
|
u8 *m_pNullPage;
|
|
u8 *m_pPhysicalScratchPad;
|
|
u8 *m_pUncachedScratchPad;
|
|
u8 *m_pKernelScratchPad;
|
|
u8 *m_pUncachedKernelScratchPad;
|
|
// 64-bit: Pointers to high-mem mirrors
|
|
// 32-bit: Same as above
|
|
u8 *m_pPhysicalRAM[3];
|
|
u8 *m_pUncachedRAM[3];
|
|
u8 *m_pKernelRAM[3]; // RAM mirrored up to "kernel space". Fully accessible at all times currently.
|
|
// Technically starts at 0xA0000000, which we don't properly support (but we don't really support kernel code.)
|
|
// This matches how we handle 32-bit masking.
|
|
u8 *m_pUncachedKernelRAM[3];
|
|
|
|
// VRAM is mirrored 4 times. The second and fourth mirrors are swizzled, so actually it's not correct
|
|
// to mirror them like we do here unfortunately.
|
|
// In practice, a game accessing the mirrors most likely is deswizzling the depth buffer, and things mostly work out
|
|
// since when we write to the depth buffer (like with the depth rasterizer or the software renderer)
|
|
// we write unswizzled data anyway. There are some exceptions, Silent Hill abuses the swizzling in ways that break
|
|
// our software renderer.
|
|
static u8 *m_pPhysicalVRAM[4];
|
|
static u8 *m_pUncachedVRAM[4];
|
|
|
|
// Hardware registers are mapped at 0xbc000000 and forwards (physical address 0x1C000000, but we need
|
|
// the 0x80000000 kernel flag and 0x40000000 uncached flag for these). Exception vectors are at 0xbfc00000.
|
|
// Since we do HLE emulation, we currently don't bother with any of that.
|
|
// Some limited documentation here: https://www.psdevwiki.com/psp/Hardware_Registers#Introduction
|
|
|
|
// Holds the ending address of the PSP's user space.
|
|
// Required for HD Remasters to work properly.
|
|
// This replaces RAM_NORMAL_SIZE at runtime.
|
|
u32 g_MemorySize;
|
|
int g_depthTranslationIndex = DepthTranslationIndex(0x400);
|
|
|
|
bool DepthMirrorsActive() {
|
|
return PSP_CoreParameter().gpuCore == GPUCORE_SOFTWARE;
|
|
}
|
|
|
|
u32 DepthMirroredSlow(u32 address) {
|
|
if (!DepthMirrorsActive())
|
|
return address;
|
|
const DepthLayout &layout = DEPTH_LAYOUTS[(address >> 22) & 1][g_depthTranslationIndex];
|
|
return (address & 0x40000000) | 0x04000000 | layout.Stored(address & 0x001FFFFF);
|
|
}
|
|
// Used to store the PSP model on game startup.
|
|
u32 g_PSPModel;
|
|
u32 g_UserPartitionSize;
|
|
|
|
static MemMapSetupFlags g_setupFlags;
|
|
|
|
|
|
// We don't declare the IO region in here since its handled by other means.
|
|
static MemoryView views[] = {
|
|
{&m_pNullPage, 0x00000000, 0x00010000, MV_NULL_PAGE}, // Null page, usually not enabled. Only used for working around some race condition bugs.
|
|
{&m_pPhysicalScratchPad, 0x00010000, SCRATCHPAD_SIZE, 0},
|
|
{&m_pUncachedScratchPad, 0x40010000, SCRATCHPAD_SIZE, MV_MIRROR_PREVIOUS},
|
|
// Kernel-mode code (e.g. flash0:/reboot.bin) sees the scratchpad through these mirrors -
|
|
// same two address bits as RAM below (0x80000000 = kernel, 0x40000000 = uncached,
|
|
// independently combinable), just missing here until this was noticed via a real SIGSEGV
|
|
// writing 0x80010000 (see docs/VSHBootInvestigation.md).
|
|
{&m_pKernelScratchPad, 0x80010000, SCRATCHPAD_SIZE, MV_MIRROR_PREVIOUS | MV_KERNEL},
|
|
{&m_pUncachedKernelScratchPad,0xC0010000, SCRATCHPAD_SIZE, MV_MIRROR_PREVIOUS | MV_KERNEL},
|
|
{&m_pPhysicalVRAM[0], 0x04000000, 0x00200000, 0},
|
|
{&m_pPhysicalVRAM[1], 0x04200000, 0x00200000, MV_MIRROR_PREVIOUS},
|
|
{&m_pPhysicalVRAM[2], 0x04400000, 0x00200000, MV_MIRROR_PREVIOUS},
|
|
{&m_pPhysicalVRAM[3], 0x04600000, 0x00200000, MV_MIRROR_PREVIOUS},
|
|
{&m_pUncachedVRAM[0], 0x44000000, 0x00200000, MV_MIRROR_PREVIOUS},
|
|
{&m_pUncachedVRAM[1], 0x44200000, 0x00200000, MV_MIRROR_PREVIOUS},
|
|
{&m_pUncachedVRAM[2], 0x44400000, 0x00200000, MV_MIRROR_PREVIOUS},
|
|
{&m_pUncachedVRAM[3], 0x44600000, 0x00200000, MV_MIRROR_PREVIOUS},
|
|
{&m_pPhysicalRAM[0], 0x08000000, g_MemorySize, MV_IS_PRIMARY_RAM}, // only from 0x08800000 is it usable (last 24 megs)
|
|
{&m_pUncachedRAM[0], 0x48000000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_PRIMARY_RAM},
|
|
{&m_pKernelRAM[0], 0x88000000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_PRIMARY_RAM | MV_KERNEL},
|
|
{&m_pUncachedKernelRAM[0],0xC8000000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_PRIMARY_RAM | MV_KERNEL},
|
|
// Starts at memory + 31 MB.
|
|
{&m_pPhysicalRAM[1], 0x09F00000, g_MemorySize, MV_IS_EXTRA1_RAM},
|
|
{&m_pUncachedRAM[1], 0x49F00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA1_RAM},
|
|
{&m_pKernelRAM[1], 0x89F00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA1_RAM | MV_KERNEL},
|
|
{&m_pUncachedKernelRAM[1],0xC9F00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA1_RAM | MV_KERNEL},
|
|
// Starts at memory + 31 * 2 MB.
|
|
{&m_pPhysicalRAM[2], 0x0BE00000, g_MemorySize, MV_IS_EXTRA2_RAM},
|
|
{&m_pUncachedRAM[2], 0x4BE00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA2_RAM},
|
|
{&m_pKernelRAM[2], 0x8BE00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA2_RAM | MV_KERNEL},
|
|
{&m_pUncachedKernelRAM[2],0xCBE00000, g_MemorySize, MV_MIRROR_PREVIOUS | MV_IS_EXTRA2_RAM | MV_KERNEL},
|
|
|
|
// TODO: There are a few swizzled mirrors of VRAM, not sure about the best way to
|
|
// implement those.
|
|
};
|
|
|
|
inline static bool CanIgnoreView(const MemoryView &view) {
|
|
#ifdef MASKED_PSP_MEMORY
|
|
// Basically, 32-bit platforms can ignore views that are masked out anyway.
|
|
return (view.flags & MV_MIRROR_PREVIOUS) && (view.virtual_address & ~MEMVIEW32_MASK) != 0;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool SkipView(MemMapSetupFlags flags, u32 viewFlags) {
|
|
#if PPSSPP_PLATFORM(IOS) && PPSSPP_ARCH(64BIT)
|
|
// We use a limited memory map on iOS with masking, we don't need to allocate the kernel space views.
|
|
if (viewFlags & MV_KERNEL) {
|
|
return true;
|
|
}
|
|
#endif
|
|
if ((viewFlags & MV_NULL_PAGE) && !(flags & MemMapSetupFlags::AllocNullPage))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
#if defined(__APPLE__)
|
|
// The address range the views need, from the base.
|
|
static size_t ViewSpan(MemMapSetupFlags flags) {
|
|
size_t span = 0;
|
|
for (int i = 0; i < ARRAY_SIZE(views); i++) {
|
|
if (views[i].size == 0 || SkipView(flags, views[i].flags) || CanIgnoreView(views[i]))
|
|
continue;
|
|
#ifdef MASKED_PSP_MEMORY
|
|
const u32 address = views[i].virtual_address & MEMVIEW32_MASK;
|
|
#else
|
|
const u32 address = views[i].virtual_address;
|
|
#endif
|
|
span = std::max(span, (size_t)address + views[i].size);
|
|
}
|
|
return span;
|
|
}
|
|
#endif
|
|
|
|
static bool Memory_TryBase(MemMapSetupFlags flags) {
|
|
// OK, we know where to find free space. Now grab it!
|
|
// We just mimic the popular BAT setup.
|
|
|
|
size_t position = 0;
|
|
size_t last_position = 0;
|
|
|
|
// Zero all the pointers to be sure.
|
|
for (int i = 0; i < ARRAY_SIZE(views); i++) {
|
|
if (views[i].out_ptr) {
|
|
*views[i].out_ptr = 0;
|
|
}
|
|
}
|
|
|
|
int i; // the final value of i is used in the next loop
|
|
for (i = 0; i < ARRAY_SIZE(views); i++) {
|
|
const MemoryView &view = views[i];
|
|
if (view.size == 0)
|
|
continue;
|
|
if (SkipView(flags, view.flags))
|
|
continue;
|
|
|
|
if (view.flags & MV_MIRROR_PREVIOUS) {
|
|
position = last_position;
|
|
}
|
|
#ifndef MASKED_PSP_MEMORY
|
|
*view.out_ptr = (u8*)g_arena.CreateView(
|
|
position, view.size, base + view.virtual_address);
|
|
if (!*view.out_ptr) {
|
|
ERROR_LOG(Log::MemMap, "Failed at view %d", i);
|
|
goto bail;
|
|
}
|
|
#else
|
|
if (CanIgnoreView(view)) {
|
|
// This is handled by address masking in 32-bit, no view needs to be created.
|
|
*view.out_ptr = *views[i - 1].out_ptr;
|
|
} else {
|
|
*view.out_ptr = (u8*)g_arena.CreateView(
|
|
position, view.size, base + (view.virtual_address & MEMVIEW32_MASK));
|
|
if (!*view.out_ptr) {
|
|
ERROR_LOG(Log::MemMap, "Failed at view %d", i);
|
|
goto bail;
|
|
}
|
|
}
|
|
#endif
|
|
last_position = position;
|
|
position += g_arena.roundup(view.size);
|
|
}
|
|
|
|
return true;
|
|
bail:
|
|
// Argh! ERROR! Free what we grabbed so far so we can try again.
|
|
for (int j = 0; j <= i; j++) {
|
|
if (views[j].size == 0)
|
|
continue;
|
|
if (SkipView(flags, views[j].flags))
|
|
continue;
|
|
if (views[j].out_ptr && *views[j].out_ptr) {
|
|
if (!CanIgnoreView(views[j])) {
|
|
g_arena.ReleaseView(0, *views[j].out_ptr, views[j].size);
|
|
}
|
|
*views[j].out_ptr = nullptr;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MemoryMap_Setup(MemMapSetupFlags flags) {
|
|
g_setupFlags = flags;
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
// We reserve the memory, then simply commit in TryBase.
|
|
base = (u8*)VirtualAllocFromApp(0, 0x10000000, MEM_RESERVE, PAGE_READWRITE);
|
|
#else
|
|
|
|
// Figure out how much memory we need to allocate in total.
|
|
size_t total_mem = 0;
|
|
for (int i = 0; i < ARRAY_SIZE(views); i++) {
|
|
if (views[i].size == 0)
|
|
continue;
|
|
if (SkipView(flags, views[i].flags))
|
|
continue;
|
|
if (!CanIgnoreView(views[i]))
|
|
total_mem += g_arena.roundup(views[i].size);
|
|
}
|
|
|
|
// Grab some pagefile backed memory out of the void ...
|
|
if (!g_arena.GrabMemSpace(total_mem)) {
|
|
// It'll already have logged.
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if defined(__APPLE__)
|
|
// Let the kernel find room for the views, instead of guessing at bases.
|
|
{
|
|
const size_t span = ViewSpan(flags);
|
|
#ifdef MASKED_PSP_MEMORY
|
|
base = g_arena.ReserveViewSpace(span, 0);
|
|
#else
|
|
// A 4GB aligned base lets the ARM64 JIT pointerify guest addresses with a single MOVK.
|
|
base = g_arena.ReserveViewSpace(span, 0xFFFFFFFF);
|
|
if (!base) {
|
|
base = g_arena.ReserveViewSpace(span, 0);
|
|
}
|
|
#endif
|
|
}
|
|
if (base) {
|
|
if (Memory_TryBase(flags)) {
|
|
INFO_LOG(Log::MemMap, "Mapped the memory views into reserved space at %p", base);
|
|
return true;
|
|
}
|
|
WARN_LOG(Log::MemMap, "Couldn't map the memory views into reserved space, falling back");
|
|
g_arena.ReleaseViewSpace();
|
|
}
|
|
#endif
|
|
|
|
#if !PPSSPP_PLATFORM(ANDROID)
|
|
if (g_arena.NeedsProbing()) {
|
|
int base_attempts = 0;
|
|
#if PPSSPP_PLATFORM(WINDOWS) && PPSSPP_ARCH(32BIT)
|
|
// Try a whole range of possible bases. Return once we got a valid one.
|
|
uintptr_t max_base_addr = 0x7FFF0000 - 0x10000000;
|
|
uintptr_t min_base_addr = 0x01000000;
|
|
uintptr_t stride = 0x400000;
|
|
#elif PPSSPP_ARCH(ARM64) && PPSSPP_PLATFORM(IOS)
|
|
// iOS
|
|
uintptr_t max_base_addr = 0x1FFFF0000ULL - 0x80000000ULL;
|
|
uintptr_t min_base_addr = 0x100000000ULL;
|
|
uintptr_t stride = 0x800000;
|
|
#else
|
|
uintptr_t max_base_addr = 0;
|
|
uintptr_t min_base_addr = 0;
|
|
uintptr_t stride = 0;
|
|
ERROR_LOG(Log::MemMap, "MemoryMap_Setup: Hit a wrong path, should not be needed on this platform.");
|
|
return false;
|
|
#endif
|
|
for (uintptr_t base_addr = min_base_addr; base_addr < max_base_addr; base_addr += stride) {
|
|
base_attempts++;
|
|
base = (u8 *)base_addr;
|
|
if (Memory_TryBase(flags)) {
|
|
INFO_LOG(Log::MemMap, "Found valid memory base at %p after %i tries.", base, base_attempts);
|
|
return true;
|
|
}
|
|
}
|
|
ERROR_LOG(Log::MemMap, "MemoryMap_Setup: Failed finding a memory base.");
|
|
return false;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
#if !PPSSPP_PLATFORM(UWP)
|
|
base = g_arena.Find4GBBase();
|
|
if (!base) {
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Should return true...
|
|
return Memory_TryBase(flags);
|
|
}
|
|
|
|
void MemoryMap_Shutdown() {
|
|
size_t position = 0;
|
|
size_t last_position = 0;
|
|
const MemMapSetupFlags flags = g_setupFlags;
|
|
g_setupFlags = MemMapSetupFlags::Default;
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(views); i++) {
|
|
if (views[i].size == 0)
|
|
continue;
|
|
if (SkipView(flags, views[i].flags))
|
|
continue;
|
|
|
|
if (views[i].flags & MV_MIRROR_PREVIOUS) {
|
|
position = last_position;
|
|
}
|
|
|
|
if (*views[i].out_ptr)
|
|
g_arena.ReleaseView(position, *views[i].out_ptr, views[i].size);
|
|
*views[i].out_ptr = nullptr;
|
|
|
|
last_position = position;
|
|
position += g_arena.roundup(views[i].size);
|
|
}
|
|
g_arena.ReleaseSpace();
|
|
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
VirtualFree(base, 0, MEM_RELEASE);
|
|
#endif
|
|
}
|
|
|
|
// Commits the shadow under every view, so exactly the addresses that are valid PSP memory have entries.
|
|
// Mirrors fold onto the same range under the mask, and committing it again is harmless.
|
|
static bool BlockShadow_Setup(MemMapSetupFlags flags) {
|
|
blockShadow = (u8 *)ReserveMemoryPages(BLOCK_SHADOW_SIZE);
|
|
if (!blockShadow) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < ARRAY_SIZE(views); i++) {
|
|
if (views[i].size == 0 || SkipView(flags, views[i].flags))
|
|
continue;
|
|
const u32 start = views[i].virtual_address & BLOCK_SHADOW_MASK;
|
|
_dbg_assert_((size_t)start + views[i].size <= BLOCK_SHADOW_SIZE);
|
|
if (!CommitMemoryPages(blockShadow + start, views[i].size)) {
|
|
FreeMemoryPages(blockShadow, BLOCK_SHADOW_SIZE);
|
|
blockShadow = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// On some 32 bit platforms (like old Android, old iOS, etc.), there are/were restrictions on memory map sizes.
|
|
// This particular size I can't find any sources for though.
|
|
static const int MAX_MMAP_SIZE = 31 * 1024 * 1024;
|
|
|
|
// Every size we actually use (32MB, 64MB, and the 76MB remasters) is a whole number of megabytes.
|
|
static bool IsPlausibleMemorySize(u32 size) {
|
|
return size != 0 && size <= (u32)MAX_MMAP_SIZE * 3 && (size & 0xFFFFF) == 0;
|
|
}
|
|
|
|
bool Init(MemMapSetupFlags flags) {
|
|
_dbg_assert_msg_(g_MemorySize <= MAX_MMAP_SIZE * 3, "ACK - too much memory for three mmap views.");
|
|
for (size_t i = 0; i < ARRAY_SIZE(views); i++) {
|
|
if (views[i].flags & MV_IS_PRIMARY_RAM)
|
|
views[i].size = std::min((int)g_MemorySize, MAX_MMAP_SIZE);
|
|
if (views[i].flags & MV_IS_EXTRA1_RAM)
|
|
views[i].size = std::min(std::max((int)g_MemorySize - MAX_MMAP_SIZE, 0), MAX_MMAP_SIZE);
|
|
if (views[i].flags & MV_IS_EXTRA2_RAM)
|
|
views[i].size = std::min(std::max((int)g_MemorySize - MAX_MMAP_SIZE * 2, 0), MAX_MMAP_SIZE);
|
|
}
|
|
|
|
// The shadow first, so the base search can't put it in a gap of the PSP's 4GB window, where a bad
|
|
// guest access would hit it instead of faulting.
|
|
if (!BlockShadow_Setup(flags)) {
|
|
return false;
|
|
}
|
|
|
|
if (!MemoryMap_Setup(flags)) {
|
|
FreeMemoryPages(blockShadow, BLOCK_SHADOW_SIZE);
|
|
blockShadow = nullptr;
|
|
return false;
|
|
}
|
|
|
|
INFO_LOG(Log::MemMap, "Memory system initialized. Base at %p (RAM at @ %p, uncached @ %p)",
|
|
base, m_pPhysicalRAM, m_pUncachedRAM);
|
|
|
|
g_mainSpace.base = base;
|
|
g_mainSpace.blockShadow = blockShadow;
|
|
MemFault_Init();
|
|
return true;
|
|
}
|
|
|
|
// Returns false if the new map couldn't be set up - in which case there is no memory map at all,
|
|
// and base is null. Callers must not carry on writing to guest memory.
|
|
bool Reinit() {
|
|
_assert_msg_(PSP_GetBootState() == BootState::Complete, "Cannot reinit during startup/shutdown");
|
|
Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITING);
|
|
// Held across both halves: between Shutdown() and Init() there is no memory map at all, and a
|
|
// reader that only saw Shutdown()'s own acquire could slip into that gap.
|
|
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
|
|
MemMapSetupFlags flags = g_setupFlags;
|
|
Shutdown();
|
|
const bool success = Init(flags);
|
|
Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITED);
|
|
return success;
|
|
}
|
|
|
|
// Loads size bytes at start, comparing the parts of it in compare->ranges with what's there as it
|
|
// goes. A chunk that already matches doesn't need copying, and the matching parts are reported in
|
|
// compare->unchanged. The rest is a plain copy.
|
|
static void CopyComparing(uint8_t *dest, const uint8_t *src, u32 start, u32 size, RamCompare *compare) {
|
|
// In the size of the JIT's block lookup pages. One changed byte costs its chunk's blocks.
|
|
constexpr u32 CHUNK_SIZE = 1024;
|
|
|
|
// As offsets into the copy, in order, merged.
|
|
std::vector<std::pair<u32, u32>> ranges;
|
|
for (const auto &[address, length] : compare->ranges) {
|
|
const u32 rangeStart = std::max(address & 0x3FFFFFFF, start);
|
|
const u32 rangeEnd = std::min((address & 0x3FFFFFFF) + length, start + size);
|
|
if (rangeStart < rangeEnd) {
|
|
ranges.emplace_back(rangeStart - start, rangeEnd - start);
|
|
}
|
|
}
|
|
std::sort(ranges.begin(), ranges.end());
|
|
|
|
u32 pos = 0;
|
|
for (const auto &[rangeStart, rangeEnd] : ranges) {
|
|
const u32 from = std::max(rangeStart, pos);
|
|
if (from >= rangeEnd) {
|
|
continue;
|
|
}
|
|
if (from > pos) {
|
|
ParallelMemcpy(&g_threadManager, dest + pos, src + pos, from - pos);
|
|
}
|
|
u32 runStart = 0;
|
|
bool inRun = false;
|
|
for (u32 chunk = from; chunk < rangeEnd; chunk += CHUNK_SIZE) {
|
|
const u32 chunkSize = std::min(CHUNK_SIZE, rangeEnd - chunk);
|
|
const bool same = memcmp(dest + chunk, src + chunk, chunkSize) == 0;
|
|
if (same && !inRun) {
|
|
runStart = chunk;
|
|
inRun = true;
|
|
} else if (!same) {
|
|
memcpy(dest + chunk, src + chunk, chunkSize);
|
|
if (inRun) {
|
|
compare->unchanged.emplace_back(start + runStart, chunk - runStart);
|
|
inRun = false;
|
|
}
|
|
}
|
|
}
|
|
if (inRun) {
|
|
compare->unchanged.emplace_back(start + runStart, rangeEnd - runStart);
|
|
}
|
|
pos = rangeEnd;
|
|
}
|
|
if (pos < size) {
|
|
ParallelMemcpy(&g_threadManager, dest + pos, src + pos, size - pos);
|
|
}
|
|
}
|
|
|
|
static void DoMemoryVoid(PointerWrap &p, uint32_t start, uint32_t size, RamCompare *compare = nullptr) {
|
|
uint8_t *d = GetPointerWriteOrException(start);
|
|
uint8_t *&storage = *p.ptr;
|
|
|
|
// We only handle aligned data and sizes.
|
|
if ((size & 0x3F) != 0 || ((uintptr_t)d & 0x3F) != 0)
|
|
return p.DoVoid(d, size);
|
|
|
|
if ((p.mode == PointerWrap::MODE_READ || p.mode == PointerWrap::MODE_VERIFY) && !p.CheckRead(size)) {
|
|
return;
|
|
}
|
|
|
|
switch (p.mode) {
|
|
case PointerWrap::MODE_READ:
|
|
if (compare && !compare->ranges.empty()) {
|
|
CopyComparing(d, storage, start, size, compare);
|
|
} else {
|
|
ParallelMemcpy(&g_threadManager, d, storage, size);
|
|
}
|
|
break;
|
|
case PointerWrap::MODE_WRITE:
|
|
ParallelMemcpy(&g_threadManager, storage, d, size);
|
|
break;
|
|
case PointerWrap::MODE_MEASURE:
|
|
// Nothing to do here.
|
|
break;
|
|
case PointerWrap::MODE_VERIFY:
|
|
ParallelRangeLoop(&g_threadManager, [&](int l, int h) {
|
|
for (int i = l; i < h; i++)
|
|
_dbg_assert_msg_(d[i] == storage[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", d[i], d[i], &d[i], storage[i], storage[i], &storage[i]);
|
|
}, 0, size, 128, 8);
|
|
break;
|
|
case PointerWrap::MODE_NOOP:
|
|
break;
|
|
}
|
|
storage += size;
|
|
}
|
|
|
|
void DoState(PointerWrap &p, RamCompare *compare) {
|
|
auto s = p.Section("Memory", 1, 3);
|
|
if (!s)
|
|
return;
|
|
|
|
const u32 memorySizeBefore = g_MemorySize;
|
|
|
|
if (s < 2) {
|
|
if (!g_RemasterMode)
|
|
g_MemorySize = RAM_NORMAL_SIZE;
|
|
g_PSPModel = PSP_MODEL_FAT;
|
|
} else if (s == 2) {
|
|
// In version 2, we determine memory size based on PSP model.
|
|
u32 oldMemorySize = g_MemorySize;
|
|
Do(p, g_PSPModel);
|
|
p.DoMarker("PSPModel");
|
|
if (!g_RemasterMode) {
|
|
g_MemorySize = g_PSPModel == PSP_MODEL_FAT ? RAM_NORMAL_SIZE : RAM_DOUBLE_SIZE;
|
|
if (oldMemorySize < g_MemorySize && !Reinit()) {
|
|
ERROR_LOG(Log::MemMap, "Failed to reinit memory to %08x bytes", g_MemorySize);
|
|
p.SetError(PointerWrap::ERROR_FAILURE);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
// In version 3, we started just saving the memory size directly.
|
|
// It's no longer based strictly on the PSP model.
|
|
u32 oldMemorySize = g_MemorySize;
|
|
Do(p, g_PSPModel);
|
|
p.DoMarker("PSPModel");
|
|
Do(p, g_MemorySize);
|
|
if (p.mode == PointerWrap::MODE_READ && !IsPlausibleMemorySize(g_MemorySize)) {
|
|
// Straight out of the file, so don't hand it to Init() - a bogus size makes the map
|
|
// fail to allocate, and we'd carry on writing RAM through a null base.
|
|
ERROR_LOG(Log::MemMap, "Savestate specifies an implausible memory size: %08x", g_MemorySize);
|
|
g_MemorySize = oldMemorySize;
|
|
p.SetError(PointerWrap::ERROR_FAILURE);
|
|
return;
|
|
}
|
|
if (oldMemorySize != g_MemorySize && !Reinit()) {
|
|
ERROR_LOG(Log::MemMap, "Failed to reinit memory to %08x bytes, restoring %08x", g_MemorySize, oldMemorySize);
|
|
g_MemorySize = oldMemorySize;
|
|
Reinit();
|
|
p.SetError(PointerWrap::ERROR_FAILURE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// A new size means Reinit(): RAM is newly mapped, nothing in it to compare with, and the JIT has
|
|
// to start over (its dispatcher points at the old mapping.)
|
|
if (g_MemorySize != memorySizeBefore) {
|
|
compare = nullptr;
|
|
}
|
|
DoMemoryVoid(p, PSP_GetKernelMemoryBase(), g_MemorySize, compare);
|
|
p.DoMarker("RAM");
|
|
|
|
DoMemoryVoid(p, PSP_GetVidMemBase(), VRAM_SIZE);
|
|
p.DoMarker("VRAM");
|
|
DoArray(p, m_pPhysicalScratchPad, SCRATCHPAD_SIZE);
|
|
p.DoMarker("ScratchPad");
|
|
}
|
|
|
|
void Shutdown() {
|
|
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
|
|
u32 flags = 0;
|
|
MemoryMap_Shutdown();
|
|
base = nullptr;
|
|
FreeMemoryPages(blockShadow, BLOCK_SHADOW_SIZE);
|
|
blockShadow = nullptr;
|
|
g_mainSpace = AddressSpace();
|
|
DEBUG_LOG(Log::MemMap, "Memory system shut down.");
|
|
}
|
|
|
|
bool IsActive() {
|
|
return base != nullptr;
|
|
}
|
|
|
|
Opcode Read_Instruction(u32 address) {
|
|
if (!IsValid4AlignedAddress(address)) {
|
|
// BAD!
|
|
_dbg_assert_(false);
|
|
return Opcode(0);
|
|
}
|
|
return Opcode(ReadUnchecked_U32(address));
|
|
}
|
|
|
|
Opcode ReadUnchecked_Instruction(u32 address) {
|
|
_dbg_assert_((address & 3) == 0);
|
|
return Opcode(ReadUnchecked_U32(address));
|
|
}
|
|
|
|
u32 ReadBlockShadow(u32 address) {
|
|
return g_mainSpace.ReadBlockShadow(address);
|
|
}
|
|
|
|
void WriteBlockShadow(u32 address, u32 value) {
|
|
g_mainSpace.WriteBlockShadow(address, value);
|
|
}
|
|
|
|
bool ClearBlockShadow(u32 address, u32 value) {
|
|
return g_mainSpace.ClearBlockShadow(address, value);
|
|
}
|
|
|
|
u32 AddressSpace::ReadBlockShadow(u32 address) const {
|
|
if (!blockShadow || !IsValid4AlignedAddress(address))
|
|
return 0;
|
|
return *GetBlockShadowEntry(address);
|
|
}
|
|
|
|
void AddressSpace::WriteBlockShadow(u32 address, u32 value) const {
|
|
if (!blockShadow || !IsValid4AlignedAddress(address))
|
|
return;
|
|
*GetBlockShadowEntry(address) = value;
|
|
}
|
|
|
|
bool AddressSpace::ClearBlockShadow(u32 address, u32 value) const {
|
|
if (!blockShadow || !IsValid4AlignedAddress(address))
|
|
return false;
|
|
u32 *entry = GetBlockShadowEntry(address);
|
|
if (*entry != value)
|
|
return false;
|
|
// A replacement hook keeps the entry nonzero, see ReplaceTables.h.
|
|
*entry = Replacement_IsHooked(address) ? Replacement_GetBlockShadowHook() : 0;
|
|
return true;
|
|
}
|
|
|
|
void Memset(const u32 addr, const u8 value, const u32 size, const char *tag) {
|
|
if (size == 0) {
|
|
// We ignore invalid addresses etc if the length is zero.
|
|
return;
|
|
}
|
|
|
|
if (IsValidRange(addr, size)) {
|
|
uint8_t *ptr = GetPointerWriteUnchecked(addr);
|
|
memset(ptr, value, size);
|
|
} else {
|
|
// TODO: This mainly seems to be produced by GPUCommon::PerformMemorySet, called from
|
|
// Replace_memset_jak().
|
|
if (Memory::IsValidRange(addr, size)) {
|
|
for (size_t i = 0; i < size; i++) {
|
|
Memory::WriteUnchecked_U8(value, (u32)(addr + i));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tag) {
|
|
NotifyMemInfo(MemBlockFlags::WRITE, addr, size, tag, strlen(tag));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void PSPPointerNotifyRW(int rw, uint32_t ptr, uint32_t bytes, const char * tag, size_t tagLen) {
|
|
if (MemBlockInfoDetailed(bytes)) {
|
|
if (rw & 1)
|
|
NotifyMemInfo(MemBlockFlags::WRITE, ptr, bytes, tag, tagLen);
|
|
if (rw & 2)
|
|
NotifyMemInfo(MemBlockFlags::READ, ptr, bytes, tag, tagLen);
|
|
}
|
|
}
|