Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #9377 from lioncash/analyzer
DSPAnalyzer: Migrate off file-scope state
  • Loading branch information
leoetlino committed Dec 29, 2020
2 parents 5ff2cb9 + 8aecaf7 commit ee048ad
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 77 deletions.
76 changes: 39 additions & 37 deletions Source/Core/Core/DSP/DSPAnalyzer.cpp
Expand Up @@ -12,15 +12,8 @@
#include "Core/DSP/DSPCore.h"
#include "Core/DSP/DSPTables.h"

namespace DSP::Analyzer
namespace DSP
{
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.
Expand Down Expand Up @@ -65,18 +58,38 @@ 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},
};

Analyzer::Analyzer() = default;
Analyzer::~Analyzer() = default;

void Reset()
void Analyzer::Analyze(const SDSP& dsp)
{
code_flags.fill(0);
Reset();
AnalyzeRange(dsp, 0x0000, 0x1000); // IRAM
AnalyzeRange(dsp, 0x8000, 0x9000); // IROM
}

void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
void Analyzer::Reset()
{
m_code_flags.fill(0);
}

void Analyzer::AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
{
// First we run an extremely simplified version of a disassembler to find
// where all instructions start.
FindInstructionStarts(dsp, start_addr, end_addr);

// Next, we'll scan for potential idle skips.
FindIdleSkips(dsp, start_addr, end_addr);

INFO_LOG_FMT(DSPLLE, "Finished analysis.");
}

void Analyzer::FindInstructionStarts(const SDSP& dsp, u16 start_addr, u16 end_addr)
{
// This may not be 100% accurate in case of jump tables!
// It could get desynced, which would be bad. We'll see if that's an issue.
u16 last_arithmetic = 0;
Expand All @@ -89,20 +102,20 @@ void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
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;
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.
Expand All @@ -114,20 +127,24 @@ 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
// instruction as needing to check for exceptions
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_EXC;
}

addr += opcode->size;
}
}

// Next, we'll scan for potential idle skips.
void Analyzer::FindIdleSkips(const SDSP& dsp, u16 start_addr, u16 end_addr)
{
for (size_t s = 0; s < NUM_IDLE_SIGS; s++)
{
for (u16 addr = start_addr; addr < end_addr; addr++)
Expand All @@ -145,24 +162,9 @@ void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr)
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
} // namespace DSP
108 changes: 88 additions & 20 deletions Source/Core/Core/DSP/DSPAnalyzer.h
Expand Up @@ -4,38 +4,106 @@

#pragma once

#include <array>
#include "Common/CommonTypes.h"

namespace DSP
{
struct SDSP;
}

// Basic code analysis.
namespace DSP::Analyzer
namespace DSP
{
// Useful things to detect:
// * Loop endpoints - so that we can avoid checking for loops every cycle.

enum
class Analyzer
{
CODE_START_OF_INST = 1,
CODE_IDLE_SKIP = 2,
CODE_LOOP_START = 4,
CODE_LOOP_END = 8,
CODE_UPDATE_SR = 16,
CODE_CHECK_INT = 32,
};
public:
explicit Analyzer();
~Analyzer();

Analyzer(const Analyzer&) = default;
Analyzer& operator=(const Analyzer&) = default;

Analyzer(Analyzer&&) = default;
Analyzer& operator=(Analyzer&&) = default;

// 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);

// Whether or not the given address indicates the start of an instruction.
[[nodiscard]] bool IsStartOfInstruction(u16 address) const
{
return (GetCodeFlags(address) & CODE_START_OF_INST) != 0;
}

// Whether or not the address indicates an idle skip location.
[[nodiscard]] bool IsIdleSkip(u16 address) const
{
return (GetCodeFlags(address) & CODE_IDLE_SKIP) != 0;
}

// Whether or not the address indicates the start of a loop.
[[nodiscard]] bool IsLoopStart(u16 address) const
{
return (GetCodeFlags(address) & CODE_LOOP_START) != 0;
}

// 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);
// Whether or not the address indicates the end of a loop.
[[nodiscard]] bool IsLoopEnd(u16 address) const
{
return (GetCodeFlags(address) & CODE_LOOP_END) != 0;
}

// Retrieves the flags set during analysis for code in memory.
u8 GetCodeFlags(u16 address);
// Whether or not the address describes an instruction that requires updating the SR register.
[[nodiscard]] bool IsUpdateSR(u16 address) const
{
return (GetCodeFlags(address) & CODE_UPDATE_SR) != 0;
}

} // namespace DSP::Analyzer
// Whether or not the address describes instructions that potentially raise exceptions.
[[nodiscard]] bool IsCheckExceptions(u16 address) const
{
return (GetCodeFlags(address) & CODE_CHECK_EXC) != 0;
}

private:
enum CodeFlags : u8
{
CODE_NONE = 0,
CODE_START_OF_INST = 1,
CODE_IDLE_SKIP = 2,
CODE_LOOP_START = 4,
CODE_LOOP_END = 8,
CODE_UPDATE_SR = 16,
CODE_CHECK_EXC = 32,
};

// Flushes all analyzed state.
void Reset();

// Analyzes a region of DSP memory.
// Note: start is inclusive, end is exclusive.
void AnalyzeRange(const SDSP& dsp, u16 start_addr, u16 end_addr);

// Finds addresses in the range [start_addr, end_addr) that are the start of an
// instruction. During this process other attributes may be detected as well
// for relevant instructions (loop start/end, etc).
void FindInstructionStarts(const SDSP& dsp, u16 start_addr, u16 end_addr);

// Finds locations within the range [start_addr, end_addr) that may contain idle skips.
void FindIdleSkips(const SDSP& dsp, u16 start_addr, u16 end_addr);

// Retrieves the flags set during analysis for code in memory.
[[nodiscard]] u8 GetCodeFlags(u16 address) const { return m_code_flags[address]; }

// Holds data about all instructions in RAM.
std::array<u8, 65536> m_code_flags{};
};
} // namespace DSP
2 changes: 1 addition & 1 deletion Source/Core/Core/DSP/DSPCore.cpp
Expand Up @@ -483,7 +483,7 @@ void DSPCore::Step()
void DSPCore::Reset()
{
m_dsp.Reset();
Analyzer::Analyze(m_dsp);
m_dsp.GetAnalyzer().Analyze(m_dsp);
}

void DSPCore::ClearIRAM()
Expand Down
6 changes: 6 additions & 0 deletions Source/Core/Core/DSP/DSPCore.h
Expand Up @@ -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"

Expand Down Expand Up @@ -398,6 +399,10 @@ struct SDSP
// Saves and loads any necessary state.
void DoState(PointerWrap& p);

// DSP static analyzer.
Analyzer& GetAnalyzer() { return m_analyzer; }
const Analyzer& GetAnalyzer() const { return m_analyzer; }

DSP_Regs r{};
u16 pc = 0;

Expand Down Expand Up @@ -451,6 +456,7 @@ struct SDSP
u16 ReadIFXImpl(u16 address);

DSPCore& m_dsp_core;
Analyzer m_analyzer;
};

enum class State
Expand Down
16 changes: 8 additions & 8 deletions Source/Core/Core/DSP/Interpreter/DSPInterpreter.cpp
Expand Up @@ -46,14 +46,16 @@ void Interpreter::ExecuteInstruction(const UDSPInstruction inst)

void Interpreter::Step()
{
auto& state = m_dsp_core.DSPState();

m_dsp_core.CheckExceptions();
m_dsp_core.DSPState().step_counter++;
state.step_counter++;

const u16 opc = m_dsp_core.DSPState().FetchInstruction();
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().IsLoopEnd(static_cast<u16>(pc - 1)))
HandleLoop();
}

Expand Down Expand Up @@ -117,8 +119,7 @@ int Interpreter::RunCyclesDebug(int cycles)
return cycles;
}

// Idle skipping.
if ((Analyzer::GetCodeFlags(state.pc) & Analyzer::CODE_IDLE_SKIP) != 0)
if (state.GetAnalyzer().IsIdleSkip(state.pc))
return 0;

Step();
Expand Down Expand Up @@ -173,8 +174,7 @@ int Interpreter::RunCycles(int cycles)
if ((state.cr & CR_HALT) != 0)
return 0;

// Idle skipping.
if ((Analyzer::GetCodeFlags(state.pc) & Analyzer::CODE_IDLE_SKIP) != 0)
if (state.GetAnalyzer().IsIdleSkip(state.pc))
return 0;

Step();
Expand Down

0 comments on commit ee048ad

Please sign in to comment.