dolphin/Source/Core/Core/PowerPC/PPCCache.cpp
Pokechu22 134397754e PPCCache: Stop storing lookup table in savestates
These lookup tables total 4 megabytes, and contain data that's entirely redundant to the actual cache state (as part of an optimization, though I'm not sure whether the optimization actually is useful). This change instead recomputes these lookup tables when loading the state (which involves filling the lookup table with a marker (0xff), and then setting the 128 * 8 valid entries (1 kilobyte)).
2022-08-01 16:36:59 -07:00

261 lines
6.1 KiB
C++

// Copyright 2009 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/PowerPC/PPCCache.h"
#include <array>
#include "Common/ChunkFile.h"
#include "Common/Swap.h"
#include "Core/Config/MainSettings.h"
#include "Core/DolphinAnalytics.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/PowerPC.h"
namespace PowerPC
{
namespace
{
constexpr std::array<u32, 8> s_plru_mask{
11, 11, 19, 19, 37, 37, 69, 69,
};
constexpr std::array<u32, 8> s_plru_value{
11, 3, 17, 1, 36, 4, 64, 0,
};
constexpr std::array<u32, 255> s_way_from_valid = [] {
std::array<u32, 255> data{};
for (size_t m = 0; m < data.size(); m++)
{
u32 w = 0;
while ((m & (size_t{1} << w)) != 0)
w++;
data[m] = w;
}
return data;
}();
constexpr std::array<u32, 128> s_way_from_plru = [] {
std::array<u32, 128> data{};
for (size_t m = 0; m < data.size(); m++)
{
std::array<u32, 7> b{};
for (size_t i = 0; i < b.size(); i++)
b[i] = u32(m & (size_t{1} << i));
u32 w = 0;
if (b[0] != 0)
{
if (b[2] != 0)
{
if (b[6] != 0)
w = 7;
else
w = 6;
}
else if (b[5] != 0)
{
w = 5;
}
else
{
w = 4;
}
}
else if (b[1] != 0)
{
if (b[4] != 0)
w = 3;
else
w = 2;
}
else if (b[3] != 0)
{
w = 1;
}
else
{
w = 0;
}
data[m] = w;
}
return data;
}();
} // Anonymous namespace
InstructionCache::~InstructionCache()
{
if (m_config_callback_id)
Config::RemoveConfigChangedCallback(*m_config_callback_id);
}
void InstructionCache::Reset()
{
valid.fill(0);
plru.fill(0);
lookup_table.fill(0xFF);
lookup_table_ex.fill(0xFF);
lookup_table_vmem.fill(0xFF);
JitInterface::ClearSafe();
}
void InstructionCache::Init()
{
if (!m_config_callback_id)
m_config_callback_id = Config::AddConfigChangedCallback([this] { RefreshConfig(); });
RefreshConfig();
data.fill({});
tags.fill({});
Reset();
}
void InstructionCache::Invalidate(u32 addr)
{
if (!HID0.ICE || m_disable_icache)
return;
// Invalidates the whole set
const u32 set = (addr >> 5) & 0x7f;
for (size_t i = 0; i < 8; i++)
{
if (valid[set] & (1U << i))
{
if (tags[set][i] & (ICACHE_VMEM_BIT >> 12))
lookup_table_vmem[((tags[set][i] << 7) | set) & 0xfffff] = 0xff;
else if (tags[set][i] & (ICACHE_EXRAM_BIT >> 12))
lookup_table_ex[((tags[set][i] << 7) | set) & 0x1fffff] = 0xff;
else
lookup_table[((tags[set][i] << 7) | set) & 0xfffff] = 0xff;
}
}
valid[set] = 0;
JitInterface::InvalidateICacheLine(addr);
}
u32 InstructionCache::ReadInstruction(u32 addr)
{
if (!HID0.ICE || m_disable_icache) // instruction cache is disabled
return Memory::Read_U32(addr);
u32 set = (addr >> 5) & 0x7f;
u32 tag = addr >> 12;
u32 t;
if (addr & ICACHE_VMEM_BIT)
{
t = lookup_table_vmem[(addr >> 5) & 0xfffff];
}
else if (addr & ICACHE_EXRAM_BIT)
{
t = lookup_table_ex[(addr >> 5) & 0x1fffff];
}
else
{
t = lookup_table[(addr >> 5) & 0xfffff];
}
if (t == 0xff) // load to the cache
{
if (HID0.ILOCK) // instruction cache is locked
return Memory::Read_U32(addr);
// select a way
if (valid[set] != 0xff)
t = s_way_from_valid[valid[set]];
else
t = s_way_from_plru[plru[set]];
// load
Memory::CopyFromEmu(reinterpret_cast<u8*>(data[set][t].data()), (addr & ~0x1f), 32);
if (valid[set] & (1 << t))
{
if (tags[set][t] & (ICACHE_VMEM_BIT >> 12))
lookup_table_vmem[((tags[set][t] << 7) | set) & 0xfffff] = 0xff;
else if (tags[set][t] & (ICACHE_EXRAM_BIT >> 12))
lookup_table_ex[((tags[set][t] << 7) | set) & 0x1fffff] = 0xff;
else
lookup_table[((tags[set][t] << 7) | set) & 0xfffff] = 0xff;
}
if (addr & ICACHE_VMEM_BIT)
lookup_table_vmem[(addr >> 5) & 0xfffff] = t;
else if (addr & ICACHE_EXRAM_BIT)
lookup_table_ex[(addr >> 5) & 0x1fffff] = t;
else
lookup_table[(addr >> 5) & 0xfffff] = t;
tags[set][t] = tag;
valid[set] |= (1 << t);
}
// update plru
plru[set] = (plru[set] & ~s_plru_mask[t]) | s_plru_value[t];
const u32 res = Common::swap32(data[set][t][(addr >> 2) & 7]);
const u32 inmem = Memory::Read_U32(addr);
if (res != inmem)
{
INFO_LOG_FMT(POWERPC,
"ICache read at {:08x} returned stale data: CACHED: {:08x} vs. RAM: {:08x}", addr,
res, inmem);
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::ICACHE_MATTERS);
}
return res;
}
void InstructionCache::DoState(PointerWrap& p)
{
if (p.IsReadMode())
{
// Clear valid parts of the lookup tables (this is done instead of using fill(0xff) to avoid
// loading the entire 4MB of tables into cache)
for (u32 set = 0; set < ICACHE_SETS; set++)
{
for (u32 way = 0; way < ICACHE_WAYS; way++)
{
if ((valid[set] & (1 << way)) != 0)
{
const u32 addr = (tags[set][way] << 12) | (set << 5);
if (addr & ICACHE_VMEM_BIT)
lookup_table_vmem[(addr >> 5) & 0xfffff] = 0xff;
else if (addr & ICACHE_EXRAM_BIT)
lookup_table_ex[(addr >> 5) & 0x1fffff] = 0xff;
else
lookup_table[(addr >> 5) & 0xfffff] = 0xff;
}
}
}
}
p.DoArray(data);
p.DoArray(tags);
p.DoArray(plru);
p.DoArray(valid);
if (p.IsReadMode())
{
// Recompute lookup tables
for (u32 set = 0; set < ICACHE_SETS; set++)
{
for (u32 way = 0; way < ICACHE_WAYS; way++)
{
if ((valid[set] & (1 << way)) != 0)
{
const u32 addr = (tags[set][way] << 12) | (set << 5);
if (addr & ICACHE_VMEM_BIT)
lookup_table_vmem[(addr >> 5) & 0xfffff] = way;
else if (addr & ICACHE_EXRAM_BIT)
lookup_table_ex[(addr >> 5) & 0x1fffff] = way;
else
lookup_table[(addr >> 5) & 0xfffff] = way;
}
}
}
}
}
void InstructionCache::RefreshConfig()
{
m_disable_icache = Config::Get(Config::MAIN_DISABLE_ICACHE);
}
} // namespace PowerPC