DSPAnalyzer: Implement DSP analyzer skeleton and use it

Attempts to simply make use of the interface. Cleanup will follow in
subsequent commits to make for nicer review.
This commit is contained in:
Lioncash 2020-12-28 10:51:19 -05:00
parent 8f4c6ad7b1
commit 5756ece7ce
8 changed files with 59 additions and 67 deletions

View file

@ -14,13 +14,6 @@
namespace DSP::Analyzer
{
namespace
{
constexpr size_t ISPACE = 65536;
// Holds data about all instructions in RAM.
std::array<u8, ISPACE> code_flags;
// Good candidates for idle skipping is mail wait loops. If we're time slicing
// between the main CPU and the DSP, if the DSP runs into one of these, it might
// as well give up its time slice immediately, after executing once.
@ -65,14 +58,28 @@ constexpr u16 idle_skip_sigs[NUM_IDLE_SIGS][MAX_IDLE_SIG_SIZE + 1] = {
{0x00da, 0x0352, // LR $AX0.H, @0x0352
0x8600, // TSTAXH $AX0.H
0x0295, 0xFFFF, // JZ 0x????
0, 0}};
0, 0},
};
void Reset()
Analyzer::Analyzer(const SDSP& dsp) : m_dsp{dsp}
{
code_flags.fill(0);
}
void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
Analyzer::~Analyzer() = default;
void Analyzer::Analyze()
{
Reset();
AnalyzeRange(0x0000, 0x1000); // IRAM
AnalyzeRange(0x8000, 0x9000); // IROM
}
void Analyzer::Reset()
{
m_code_flags.fill(0);
}
void Analyzer::AnalyzeRange(u16 start_addr, u16 end_addr)
{
// First we run an extremely simplified version of a disassembler to find
// where all instructions start.
@ -82,27 +89,27 @@ void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
u16 last_arithmetic = 0;
for (u16 addr = start_addr; addr < end_addr;)
{
const UDSPInstruction inst = dsp.ReadIMEM(addr);
const UDSPInstruction inst = m_dsp.ReadIMEM(addr);
const DSPOPCTemplate* opcode = GetOpTemplate(inst);
if (!opcode)
{
addr++;
continue;
}
code_flags[addr] |= CODE_START_OF_INST;
m_code_flags[addr] |= CODE_START_OF_INST;
// Look for loops.
if ((inst & 0xffe0) == 0x0060 || (inst & 0xff00) == 0x1100)
{
// BLOOP, BLOOPI
const u16 loop_end = dsp.ReadIMEM(addr + 1);
code_flags[addr] |= CODE_LOOP_START;
code_flags[loop_end] |= CODE_LOOP_END;
const u16 loop_end = m_dsp.ReadIMEM(addr + 1);
m_code_flags[addr] |= CODE_LOOP_START;
m_code_flags[loop_end] |= CODE_LOOP_END;
}
else if ((inst & 0xffe0) == 0x0040 || (inst & 0xff00) == 0x1000)
{
// LOOP, LOOPI
code_flags[addr] |= CODE_LOOP_START;
code_flags[static_cast<u16>(addr + 1u)] |= CODE_LOOP_END;
m_code_flags[addr] |= CODE_LOOP_START;
m_code_flags[static_cast<u16>(addr + 1u)] |= CODE_LOOP_END;
}
// Mark the last arithmetic/multiplier instruction before a branch.
@ -114,7 +121,7 @@ void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
if (opcode->branch && !opcode->uncond_branch)
{
code_flags[last_arithmetic] |= CODE_UPDATE_SR;
m_code_flags[last_arithmetic] |= CODE_UPDATE_SR;
}
// If an instruction potentially raises exceptions, mark the following
@ -122,7 +129,9 @@ void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
if (opcode->opcode == 0x00c0 || opcode->opcode == 0x1800 || opcode->opcode == 0x1880 ||
opcode->opcode == 0x1900 || opcode->opcode == 0x1980 || opcode->opcode == 0x2000 ||
opcode->extended)
code_flags[static_cast<u16>(addr + opcode->size)] |= CODE_CHECK_INT;
{
m_code_flags[static_cast<u16>(addr + opcode->size)] |= CODE_CHECK_INT;
}
addr += opcode->size;
}
@ -139,30 +148,16 @@ void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
found = true;
if (idle_skip_sigs[s][i] == 0xFFFF)
continue;
if (idle_skip_sigs[s][i] != dsp.ReadIMEM(static_cast<u16>(addr + i)))
if (idle_skip_sigs[s][i] != m_dsp.ReadIMEM(static_cast<u16>(addr + i)))
break;
}
if (found)
{
INFO_LOG_FMT(DSPLLE, "Idle skip location found at {:02x} (sigNum:{})", addr, s + 1);
code_flags[addr] |= CODE_IDLE_SKIP;
m_code_flags[addr] |= CODE_IDLE_SKIP;
}
}
}
INFO_LOG_FMT(DSPLLE, "Finished analysis.");
}
} // Anonymous namespace
void Analyze(const SDSP& dsp)
{
Reset();
AnalyzeRange(dsp, 0x0000, 0x1000); // IRAM
AnalyzeRange(dsp, 0x8000, 0x9000); // IROM
}
u8 GetCodeFlags(u16 address)
{
return code_flags[address];
}
} // namespace DSP::Analyzer

View file

@ -66,16 +66,4 @@ private:
// DSP context for analysis to be run under.
const SDSP& m_dsp;
};
// This one should be called every time IRAM changes - which is basically
// every time that a new ucode gets uploaded, and never else. At that point,
// we can do as much static analysis as we want - but we should always throw
// all old analysis away. Luckily the entire address space is only 64K code
// words and the actual code space 8K instructions in total, so we can do
// some pretty expensive analysis if necessary.
void Analyze(const SDSP& dsp);
// Retrieves the flags set during analysis for code in memory.
u8 GetCodeFlags(u16 address);
} // namespace DSP::Analyzer

View file

@ -115,7 +115,7 @@ private:
SDSP& m_dsp;
};
SDSP::SDSP(DSPCore& core) : m_dsp_core{core}
SDSP::SDSP(DSPCore& core) : m_dsp_core{core}, m_analyzer{*this}
{
}
@ -487,7 +487,7 @@ void DSPCore::Step()
void DSPCore::Reset()
{
m_dsp.Reset();
Analyzer::Analyze(m_dsp);
m_dsp.GetAnalyzer().Analyze();
}
void DSPCore::ClearIRAM()

View file

@ -12,6 +12,7 @@
#include <string>
#include "Common/Event.h"
#include "Core/DSP/DSPAnalyzer.h"
#include "Core/DSP/DSPBreakpoints.h"
#include "Core/DSP/DSPCaptureLogger.h"
@ -396,6 +397,10 @@ struct SDSP
// Saves and loads any necessary state.
void DoState(PointerWrap& p);
// DSP static analyzer.
Analyzer::Analyzer& GetAnalyzer() { return m_analyzer; }
const Analyzer::Analyzer& GetAnalyzer() const { return m_analyzer; }
DSP_Regs r{};
u16 pc = 0;
@ -449,6 +454,7 @@ private:
u16 ReadIFXImpl(u16 address);
DSPCore& m_dsp_core;
Analyzer::Analyzer m_analyzer;
};
enum class State

View file

@ -42,14 +42,16 @@ void Interpreter::ExecuteInstruction(const UDSPInstruction inst)
void Interpreter::Step()
{
m_dsp_core.CheckExceptions();
m_dsp_core.DSPState().step_counter++;
auto& state = m_dsp_core.DSPState();
const u16 opc = m_dsp_core.DSPState().FetchInstruction();
m_dsp_core.CheckExceptions();
state.step_counter++;
const u16 opc = state.FetchInstruction();
ExecuteInstruction(UDSPInstruction{opc});
const auto pc = m_dsp_core.DSPState().pc;
if ((Analyzer::GetCodeFlags(static_cast<u16>(pc - 1)) & Analyzer::CODE_LOOP_END) != 0)
const auto pc = state.pc;
if ((state.GetAnalyzer().GetCodeFlags(static_cast<u16>(pc - 1)) & Analyzer::CODE_LOOP_END) != 0)
HandleLoop();
}
@ -114,7 +116,7 @@ int Interpreter::RunCyclesDebug(int cycles)
}
// Idle skipping.
if ((Analyzer::GetCodeFlags(state.pc) & Analyzer::CODE_IDLE_SKIP) != 0)
if ((state.GetAnalyzer().GetCodeFlags(state.pc) & Analyzer::CODE_IDLE_SKIP) != 0)
return 0;
Step();
@ -170,7 +172,7 @@ int Interpreter::RunCycles(int cycles)
return 0;
// Idle skipping.
if ((Analyzer::GetCodeFlags(state.pc) & Analyzer::CODE_IDLE_SKIP) != 0)
if ((state.GetAnalyzer().GetCodeFlags(state.pc) & Analyzer::CODE_IDLE_SKIP) != 0)
return 0;
Step();

View file

@ -128,7 +128,7 @@ void DSPEmitter::checkExceptions(u32 retval)
bool DSPEmitter::FlagsNeeded() const
{
const u8 flags = Analyzer::GetCodeFlags(m_compile_pc);
const u8 flags = m_dsp_core.DSPState().GetAnalyzer().GetCodeFlags(m_compile_pc);
return !(flags & Analyzer::CODE_START_OF_INST) || (flags & Analyzer::CODE_UPDATE_SR);
}
@ -242,9 +242,10 @@ void DSPEmitter::Compile(u16 start_addr)
bool fixup_pc = false;
m_block_size[start_addr] = 0;
auto& analyzer = m_dsp_core.DSPState().GetAnalyzer();
while (m_compile_pc < start_addr + MAX_BLOCK_SIZE)
{
if (Analyzer::GetCodeFlags(m_compile_pc) & Analyzer::CODE_CHECK_INT)
if (analyzer.GetCodeFlags(m_compile_pc) & Analyzer::CODE_CHECK_INT)
checkExceptions(m_block_size[start_addr]);
const UDSPInstruction inst = m_dsp_core.DSPState().ReadIMEM(m_compile_pc);
@ -262,7 +263,7 @@ void DSPEmitter::Compile(u16 start_addr)
// Handle loop condition, only if current instruction was flagged as a loop destination
// by the analyzer.
if (Analyzer::GetCodeFlags(static_cast<u16>(m_compile_pc - 1u)) & Analyzer::CODE_LOOP_END)
if ((analyzer.GetCodeFlags(static_cast<u16>(m_compile_pc - 1u)) & Analyzer::CODE_LOOP_END) != 0)
{
MOVZX(32, 16, EAX, M_SDSP_r_st(2));
TEST(32, R(EAX), R(EAX));
@ -283,7 +284,7 @@ void DSPEmitter::Compile(u16 start_addr)
DSPJitRegCache c(m_gpr);
HandleLoop();
m_gpr.SaveRegs();
if (!Host::OnThread() && Analyzer::GetCodeFlags(start_addr) & Analyzer::CODE_IDLE_SKIP)
if (!Host::OnThread() && (analyzer.GetCodeFlags(start_addr) & Analyzer::CODE_IDLE_SKIP) != 0)
{
MOV(16, R(EAX), Imm16(DSP_IDLE_SKIP_CYCLES));
}
@ -319,7 +320,7 @@ void DSPEmitter::Compile(u16 start_addr)
DSPJitRegCache c(m_gpr);
// don't update g_dsp.pc -- the branch insn already did
m_gpr.SaveRegs();
if (!Host::OnThread() && Analyzer::GetCodeFlags(start_addr) & Analyzer::CODE_IDLE_SKIP)
if (!Host::OnThread() && (analyzer.GetCodeFlags(start_addr) & Analyzer::CODE_IDLE_SKIP) != 0)
{
MOV(16, R(EAX), Imm16(DSP_IDLE_SKIP_CYCLES));
}
@ -336,7 +337,7 @@ void DSPEmitter::Compile(u16 start_addr)
}
// End the block if we're before an idle skip address
if (Analyzer::GetCodeFlags(m_compile_pc) & Analyzer::CODE_IDLE_SKIP)
if ((analyzer.GetCodeFlags(m_compile_pc) & Analyzer::CODE_IDLE_SKIP) != 0)
{
break;
}
@ -382,7 +383,7 @@ void DSPEmitter::Compile(u16 start_addr)
}
m_gpr.SaveRegs();
if (!Host::OnThread() && Analyzer::GetCodeFlags(start_addr) & Analyzer::CODE_IDLE_SKIP)
if (!Host::OnThread() && (analyzer.GetCodeFlags(start_addr) & Analyzer::CODE_IDLE_SKIP) != 0)
{
MOV(16, R(EAX), Imm16(DSP_IDLE_SKIP_CYCLES));
}

View file

@ -82,7 +82,8 @@ void DSPEmitter::WriteBranchExit()
{
DSPJitRegCache c(m_gpr);
m_gpr.SaveRegs();
if (Analyzer::GetCodeFlags(m_start_address) & Analyzer::CODE_IDLE_SKIP)
if ((m_dsp_core.DSPState().GetAnalyzer().GetCodeFlags(m_start_address) &
Analyzer::CODE_IDLE_SKIP) != 0)
{
MOV(16, R(EAX), Imm16(0x1000));
}

View file

@ -93,8 +93,7 @@ void CodeLoaded(DSPCore& dsp, const u8* ptr, size_t size)
UpdateDebugger();
dsp.ClearIRAM();
Analyzer::Analyze(state);
state.GetAnalyzer().Analyze();
}
void UpdateDebugger()