Files
SourMesen a33b8b2712 Debugger: GSU - Added profiler support (#261)
This is implemented by pretending every 16 bytes in ROM is a different function, allowing the profiler to display cycle counts for each individual "function" in the UI
Also fixes a missing call to ResetFlags for the merge opcode for FX3
2026-08-27 19:00:55 +09:00

196 lines
5.2 KiB
C++

#include "pch.h"
#include "Debugger/Profiler.h"
#include "Debugger/DebugBreakHelper.h"
#include "Debugger/Debugger.h"
#include "Debugger/IDebugger.h"
#include "Debugger/DebugTypes.h"
static constexpr int32_t ResetFunctionIndex = -1;
static constexpr int32_t HaltFunctionIndex = (uint8_t)MemoryType::None << 24;
Profiler::Profiler(Debugger* debugger, IDebugger* cpuDebugger)
{
_debugger = debugger;
_cpuDebugger = cpuDebugger;
InternalReset();
}
Profiler::~Profiler()
{
}
void Profiler::StackFunction(AddressInfo& addr, StackFrameFlags stackFlag)
{
if(addr.Address >= 0) {
uint32_t key = addr.Address | ((uint8_t)addr.Type << 24);
if(_functions.find(key) == _functions.end()) {
_functions[key] = ProfiledFunction();
_functions[key].Address = addr;
}
UpdateCycles();
_stackFlags.push_back(_functions[_currentFunction].Flags);
_cycleCountStack.push_back(_currentCycleCount);
_functionStack.push_back(_currentFunction);
if(_functionStack.size() > 100) {
//Keep stack to 100 functions at most (to prevent performance issues, esp. in debug builds)
//Only happens when software doesn't use JSR/RTS normally to enter/leave functions
_functionStack.pop_front();
_cycleCountStack.pop_front();
_stackFlags.pop_front();
}
ProfiledFunction& func = _functions[key];
func.CallCount++;
func.Flags = stackFlag;
_currentFunction = key;
_currentCycleCount = 0;
}
}
void Profiler::UpdateCycles()
{
uint64_t masterClock = _cpuDebugger->GetCpuCycleCount(true);
ProfiledFunction& func = _functions[_currentFunction];
uint64_t clockGap = masterClock - _prevMasterClock;
func.ExclusiveCycles += clockGap;
func.InclusiveCycles += clockGap;
if(func.Flags == StackFrameFlags::None) {
int32_t len = (int32_t)_functionStack.size();
for(int32_t i = len - 1; i >= 0; i--) {
_functions[_functionStack[i]].InclusiveCycles += clockGap;
if(_stackFlags[i] != StackFrameFlags::None) {
//Don't apply inclusive times to stack frames before an IRQ/NMI
break;
}
}
}
_currentCycleCount += clockGap;
_prevMasterClock = masterClock;
}
void Profiler::UnstackFunction()
{
if(!_functionStack.empty()) {
UpdateCycles();
//Return to the previous function
ProfiledFunction& func = _functions[_currentFunction];
func.MinCycles = std::min(func.MinCycles, _currentCycleCount);
func.MaxCycles = std::max(func.MaxCycles, _currentCycleCount);
_currentFunction = _functionStack.back();
_functionStack.pop_back();
_stackFlags.pop_back();
if(func.Flags == StackFrameFlags::None) {
//Add the subroutine's cycle count to the current routine's cycle count
_currentCycleCount = _cycleCountStack.back() + _currentCycleCount;
} else {
//If the current call stack was the start of NMI/IRQ, don't add the
//cycle count for NMI/IRQ to the previous function on the call stack.
_currentCycleCount = _cycleCountStack.back();
}
_cycleCountStack.pop_back();
}
}
void Profiler::RecordSample(AddressInfo addr)
{
//Used by the GSU to behave as if the current function changes
//every time the program counter crosses a 16-byte boundary,
//which allows using the profiler with the GSU even though
//the GSU doesn't actually ever call "functions"
if(addr.Address >= 0) {
uint32_t key = addr.Address | ((uint8_t)addr.Type << 24);
if(_functions.find(key) == _functions.end()) {
_functions[key] = ProfiledFunction();
_functions[key].Address = addr;
}
uint64_t masterClock = _cpuDebugger->GetCpuCycleCount(true);
uint64_t clockGap = masterClock - _prevMasterClock;
ProfiledFunction& func = _functions[_currentFunction];
func.InclusiveCycles += clockGap;
func.ExclusiveCycles += clockGap;
func.CallCount++;
_prevMasterClock = masterClock;
_currentFunction = key;
}
}
void Profiler::Reset()
{
DebugBreakHelper helper(_debugger);
InternalReset();
}
void Profiler::ResetState()
{
_prevMasterClock = _cpuDebugger->GetCpuCycleCount(true);
_currentCycleCount = 0;
_functionStack.clear();
_stackFlags.clear();
_cycleCountStack.clear();
_currentFunction = ResetFunctionIndex;
_prevCpuUsage = 0;
_prevUsageCycleCount = 0;
_prevUsageHaltedCycles = 0;
}
void Profiler::InternalReset()
{
ResetState();
_functions.clear();
_functions[ResetFunctionIndex] = ProfiledFunction();
_functions[ResetFunctionIndex].Address = { ResetFunctionIndex, MemoryType::None };
}
void Profiler::GetProfilerData(ProfiledFunction* profilerData, uint32_t& functionCount)
{
DebugBreakHelper helper(_debugger);
UpdateCycles();
functionCount = 0;
for(auto& func : _functions) {
profilerData[functionCount] = func.second;
functionCount++;
if(functionCount >= 100000) {
break;
}
}
}
void Profiler::UpdateCpuUsage()
{
DebugBreakHelper helper(_debugger);
UpdateCycles();
auto result = _functions.find(HaltFunctionIndex);
if(result != _functions.end()) {
uint64_t cycleCount = _cpuDebugger->GetCpuCycleCount(true);
uint64_t haltedCycles = result->second.ExclusiveCycles;
uint64_t elapsed = cycleCount - _prevUsageCycleCount;
uint64_t halted = haltedCycles - _prevUsageHaltedCycles;
_prevCpuUsage = ((double)(elapsed - halted) / elapsed * 100) + 1;
_prevUsageCycleCount = cycleCount;
_prevUsageHaltedCycles = result->second.ExclusiveCycles;
} else {
_prevCpuUsage = 0;
}
}