#include "ppsspp_config.h" #include "UI/ImDebugger/ImJitViewer.h" #include "UI/ImDebugger/ImDebugger.h" #include "Core/Config.h" #include "Core/MIPS/JitCommon/JitBlockCache.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/MIPS/JitCommon/JitState.h" #include "Core/MIPS/IR/IRJit.h" void ImJitViewerWindow::GoToBlockAtAddr(u32 addr) { for (auto &block : blockList_) { if (addr >= block.addr && addr < block.addr + (u32)block.sizeInBytes) { curBlockNum_ = block.blockNum; break; } } } void ImJitViewerWindow::Draw(ImConfig &cfg, ImControl &control) { ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); if (!ImGui::Begin("JitViewer", &cfg.jitViewerOpen)) { ImGui::End(); return; } const CPUCore core = (CPUCore)g_Config.iCpuCore; if (core == CPUCore::INTERPRETER) { ImGui::TextUnformatted("JIT Viewer is only available with JIT or IR-based CPU cores."); ImGui::End(); return; } // Options menu if (ImGui::BeginPopup("Options")) { // ImGui::Checkbox("Auto-scroll", &AutoScroll); ImGui::EndPopup(); } #if PPSSPP_ARCH(AMD64) const char *TARGET = "X86-64"; #elif PPSSPP_ARCH(X86) const char *TARGET = "X86"; #elif PPSSPP_ARCH(ARM64) const char *TARGET = "ARM64"; #elif PPSSPP_ARCH(ARM) const char *TARGET = "ARM32"; #else const char *TARGET = "TARGET"; #endif // Three or four columns: One table for blocks that can be sorted in various ways, // then the remaining are either MIPS/IR/TARGET or MIPS/TARGET or MIPS/IR depending on the JIT type. int numColumns = 3; if (core == CPUCore::JIT_IR) { numColumns = 4; } JitBlockCacheDebugInterface *blockCacheDebug = MIPSComp::jit->GetBlockCacheDebugInterface(); if (core_ != (int)core) { // Core changed, need to refresh. refresh_ = true; core_ = (int)core; } if (sortSpecs_ && sortSpecs_->SpecsDirty) { refresh_ = true; sortSpecs_->SpecsDirty = false; } // If the CPU moved, and we're not currently running, update the cached blocklist. const CoreState coreStateCached = coreState; if (coreStateCached == CORE_STEPPING_CPU || coreStateCached == CORE_NEXTFRAME || refresh_) { const int cpuStepCount = Core_GetSteppingCounter(); if (lastCpuStepCount_ != cpuStepCount || refresh_) { refresh_ = false; lastCpuStepCount_ = cpuStepCount; blockList_.clear(); curBlockNum_ = -1; const int blockCount = blockCacheDebug->GetNumBlocks(); blockList_.reserve(blockCount); INFO_LOG(Log::Debugger, "Updating JIT block list... %d blocks", blockCacheDebug->GetNumBlocks()); for (int i = 0; i < blockCount; i++) { if (!blockCacheDebug->IsValidBlock(i)) { continue; } JitBlockMeta meta = blockCacheDebug->GetBlockMeta(i); if (!meta.valid) { continue; } CachedBlock cb; cb.addr = meta.addr; cb.sizeInBytes = meta.sizeInBytes; cb.blockNum = i; #ifdef IR_PROFILING JitBlockProfileStats stats = blockCacheDebug->GetBlockProfileStats(i); cb.profileStats = stats; #endif blockList_.push_back(cb); } std::sort(blockList_.begin(), blockList_.end(), [this](const CachedBlock &a, const CachedBlock &b) { if (!sortSpecs_ || sortSpecs_->SpecsCount <= 0) { return a.addr < b.addr; } const ImGuiTableColumnSortSpecs *spec = &sortSpecs_->Specs[0]; int delta = 0; switch (spec ? spec->ColumnUserID : 0) { case 0: delta = (int)a.blockNum - (int)b.blockNum; break; case 1: delta = (int)a.addr - (int)b.addr; break; case 2: delta = a.sizeInBytes - b.sizeInBytes; break; #ifdef IR_PROFILING case 3: delta = a.profileStats.executions - b.profileStats.executions; break; case 4: delta = a.profileStats.totalNanos - b.profileStats.totalNanos; break; #endif } if (delta == 0) { return a.addr < b.addr; } if (!spec || spec->SortDirection == ImGuiSortDirection_Ascending) { return delta < 0; } else { return delta > 0; } }); } } else { ImGui::Text("Pause to update block list"); } if (ImGui::BeginTable("columns", numColumns, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) { ImGui::TableSetupColumn("Blocks"); ImGui::TableSetupColumn("MIPS"); if (core == CPUCore::JIT_IR || core == CPUCore::IR_INTERPRETER) { ImGui::TableSetupColumn("IR"); } if (core == CPUCore::JIT_IR || core == CPUCore::JIT) { ImGui::TableSetupColumn(TARGET); } ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::TableNextColumn(); // For separate scrolling. ImGui::BeginChild("LeftPane", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar); #ifdef IR_PROFILING const int numColumns = (core == CPUCore::IR_INTERPRETER) ? 5 : 3; #else const int numColumns = 3; #endif if (ImGui::BeginTable("blocks", numColumns, ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) { ImGui::TableSetupColumn("Num", 0, 0, 0); ImGui::TableSetupColumn("Addr", 0, 0, 1); ImGui::TableSetupColumn("Size", 0, 0, 2); #ifdef IR_PROFILING if (numColumns == 5) { ImGui::TableSetupColumn("Exec", 0, 0, 3); ImGui::TableSetupColumn("Ns", 0, 0, 4); } #endif ImGui::TableHeadersRow(); sortSpecs_ = ImGui::TableGetSortSpecs(); ImGuiListClipper clipper; clipper.Begin((int)blockList_.size()); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { const auto &block = blockList_[i]; ImGui::TableNextRow(); ImGui::TableNextColumn(); char label[32]; snprintf(label, sizeof(label), "%d", block.blockNum); if (ImGui::Selectable(label, block.blockNum == curBlockNum_, ImGuiSelectableFlags_SpanAllColumns)) { curBlockNum_ = block.blockNum; if (curBlockNum_ >= 0 && curBlockNum_ < blockCacheDebug->GetNumBlocks()) { debugInfo_ = blockCacheDebug->GetBlockDebugInfo(curBlockNum_); } } ImGui::TableNextColumn(); ImGui::Text("%08x", block.addr); ImGui::TableNextColumn(); ImGui::Text("%d", block.sizeInBytes); #ifdef IR_PROFILING if (numColumns == 5) { ImGui::TableNextColumn(); ImGui::Text("%d", block.profileStats.executions); ImGui::TableNextColumn(); ImGui::Text("%0.3f ms", (double)block.profileStats.totalNanos * 0.000001); } #endif } } clipper.End(); ImGui::EndTable(); } ImGui::EndChild(); ImGui::TableNextColumn(); ImGui::BeginChild("MIPSPane", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar); for (const auto &line : debugInfo_.origDisasm) { ImGui::TextUnformatted(line.c_str()); } ImGui::EndChild(); if (core == CPUCore::JIT_IR || core == CPUCore::IR_INTERPRETER) { ImGui::TableNextColumn(); ImGui::BeginChild("IRPane", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar); // TODO : When we have both target and IR, need a third column. for (const auto &line : debugInfo_.irDisasm) { ImGui::TextUnformatted(line.c_str()); } ImGui::EndChild(); } if (core == CPUCore::JIT_IR || core == CPUCore::JIT) { ImGui::TableNextColumn(); ImGui::BeginChild("TargetPane", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar); for (const auto &line : debugInfo_.targetDisasm) { ImGui::TextUnformatted(line.c_str()); } ImGui::EndChild(); } ImGui::EndTable(); } ImGui::End(); }