mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
174 lines
5.0 KiB
C++
174 lines
5.0 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|||
|
|
// SPDX-License-Identifier: GPL-3.0+
|
||
|
|
|
||
|
|
#include "no-jit-improvements.h"
|
||
|
|
|
||
|
|
#include "Config.h"
|
||
|
|
#include "R5900OpcodeTables.h"
|
||
|
|
#include "vtlb.h"
|
||
|
|
|
||
|
|
#include <array>
|
||
|
|
#include <limits>
|
||
|
|
|
||
|
|
namespace NoJITImprovements
|
||
|
|
{
|
||
|
|
namespace
|
||
|
|
{
|
||
|
|
// Short fixed blocks avoid runtime allocation on the EE execution path.
|
||
|
|
// Branch delay slots remain on the original interpreter path because the
|
||
|
|
// branch handlers execute them directly.
|
||
|
|
static constexpr u32 BLOCK_INSTRUCTION_CAPACITY = 16;
|
||
|
|
static constexpr u32 BLOCK_CACHE_SIZE = 4096;
|
||
|
|
static constexpr u32 INVALID_PC = std::numeric_limits<u32>::max();
|
||
|
|
|
||
|
|
struct alignas(64) EEBlock
|
||
|
|
{
|
||
|
|
u32 start_pc = INVALID_PC;
|
||
|
|
u8 instruction_count = 0;
|
||
|
|
std::array<u32, BLOCK_INSTRUCTION_CAPACITY> code{};
|
||
|
|
std::array<const R5900::OPCODE*, BLOCK_INSTRUCTION_CAPACITY> opcodes{};
|
||
|
|
};
|
||
|
|
|
||
|
|
static std::array<EEBlock, BLOCK_CACHE_SIZE> s_ee_blocks;
|
||
|
|
|
||
|
|
static __fi u32 GetCacheIndex(u32 pc)
|
||
|
|
{
|
||
|
|
const u32 instruction_address = pc >> 2;
|
||
|
|
return (instruction_address ^ (instruction_address >> 11) ^ (instruction_address >> 19)) &
|
||
|
|
(BLOCK_CACHE_SIZE - 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool IsBlockCurrent(const EEBlock& block)
|
||
|
|
{
|
||
|
|
if (block.instruction_count == 0)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
const u32 byte_count = static_cast<u32>(block.instruction_count) * sizeof(u32);
|
||
|
|
return vtlb_memSafeCmpBytes(block.start_pc, block.code.data(), byte_count) == 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool BuildBlock(EEBlock& block, u32 pc)
|
||
|
|
{
|
||
|
|
block.start_pc = INVALID_PC;
|
||
|
|
block.instruction_count = 0;
|
||
|
|
|
||
|
|
u32 instruction_pc = pc;
|
||
|
|
for (u32 i = 0; i < BLOCK_INSTRUCTION_CAPACITY; i++, instruction_pc += sizeof(u32))
|
||
|
|
{
|
||
|
|
bool valid = false;
|
||
|
|
const u32 code = vtlb_ramRead<mem32_t>(instruction_pc, &valid);
|
||
|
|
if (!valid)
|
||
|
|
break;
|
||
|
|
|
||
|
|
const R5900::OPCODE& opcode = R5900::GetInstruction(code);
|
||
|
|
block.code[i] = code;
|
||
|
|
block.opcodes[i] = &opcode;
|
||
|
|
block.instruction_count++;
|
||
|
|
|
||
|
|
// A store can modify a later instruction in the current block.
|
||
|
|
// COP0 operations can alter address translation. End immediately
|
||
|
|
// after either so the next block validates the new memory view.
|
||
|
|
const bool changes_memory_view =
|
||
|
|
(opcode.flags & (IS_BRANCH | IS_STORE)) != 0 || (code >> 26) == 0x10;
|
||
|
|
if (changes_memory_view || instruction_pc > (INVALID_PC - sizeof(u32)))
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (block.instruction_count == 0)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Publish the key last so a partially rebuilt slot is never treated
|
||
|
|
// as a valid hit if execution is interrupted by a VM exit.
|
||
|
|
block.start_pc = pc;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool IsCacheEnabled()
|
||
|
|
{
|
||
|
|
#if defined(PCSX2_DEVBUILD) || defined(PCSX2_RECOMPILER_TESTS)
|
||
|
|
// Debug stepping and the interpreter oracle require the original
|
||
|
|
// one-instruction fetch path.
|
||
|
|
return false;
|
||
|
|
#else
|
||
|
|
// EE cache emulation can intentionally expose instruction bytes
|
||
|
|
// which differ from RAM, so side-effect-free RAM validation cannot
|
||
|
|
// be used while that compatibility option is active.
|
||
|
|
return !CHECK_EEREC && !CHECK_CACHE;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ExecuteEEBlock(u32 pc, ExecuteInstruction execute_instruction)
|
||
|
|
{
|
||
|
|
if (!execute_instruction || !IsCacheEnabled())
|
||
|
|
return false;
|
||
|
|
|
||
|
|
EEBlock& block = s_ee_blocks[GetCacheIndex(pc)];
|
||
|
|
if (block.start_pc != pc || !IsBlockCurrent(block))
|
||
|
|
{
|
||
|
|
if (!BuildBlock(block, pc))
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (u32 i = 0; i < block.instruction_count; i++)
|
||
|
|
{
|
||
|
|
if (!execute_instruction(pc + (i * sizeof(u32)), block.code[i], *block.opcodes[i]))
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ResetEEBlockCache()
|
||
|
|
{
|
||
|
|
for (EEBlock& block : s_ee_blocks)
|
||
|
|
{
|
||
|
|
block.start_pc = INVALID_PC;
|
||
|
|
block.instruction_count = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void InvalidateEEBlockCache(u32 address, u32 size_in_words)
|
||
|
|
{
|
||
|
|
if (size_in_words == 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Large clears are cheaper to handle as a complete cache reset.
|
||
|
|
if (size_in_words >= BLOCK_CACHE_SIZE)
|
||
|
|
{
|
||
|
|
ResetEEBlockCache();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const u64 clear_start = address;
|
||
|
|
const u64 clear_end = clear_start + (static_cast<u64>(size_in_words) * sizeof(u32));
|
||
|
|
if (clear_end > (static_cast<u64>(std::numeric_limits<u32>::max()) + 1))
|
||
|
|
{
|
||
|
|
ResetEEBlockCache();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// A cached block can begin up to fifteen instructions before the
|
||
|
|
// cleared range. Probe only those possible direct-mapped slots instead
|
||
|
|
// of scanning the complete cache.
|
||
|
|
const u64 lookbehind = (BLOCK_INSTRUCTION_CAPACITY - 1) * sizeof(u32);
|
||
|
|
const u64 candidate_start = (clear_start > lookbehind) ? (clear_start - lookbehind) : 0;
|
||
|
|
for (u64 candidate_pc = candidate_start & ~static_cast<u64>(3);
|
||
|
|
candidate_pc < clear_end; candidate_pc += sizeof(u32))
|
||
|
|
{
|
||
|
|
EEBlock& block = s_ee_blocks[GetCacheIndex(static_cast<u32>(candidate_pc))];
|
||
|
|
if (block.instruction_count == 0)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
const u64 block_start = block.start_pc;
|
||
|
|
const u64 block_end =
|
||
|
|
block_start + (static_cast<u64>(block.instruction_count) * sizeof(u32));
|
||
|
|
if (clear_start < block_end && block_start < clear_end)
|
||
|
|
{
|
||
|
|
block.start_pc = INVALID_PC;
|
||
|
|
block.instruction_count = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|