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:
parent
8f4c6ad7b1
commit
5756ece7ce
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue