diff --git a/CMakeLists.txt b/CMakeLists.txt
index ad2be076bfc6..a7b642a3ecb8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2342,6 +2342,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/MIPS/MIPSVFPUFallbacks.h
Core/MIPS/MIPSAsm.cpp
Core/MIPS/MIPSAsm.h
+ Core/MIPS/MIPSTracer.cpp
+ Core/MIPS/MIPSTracer.h
Core/MemFault.cpp
Core/MemFault.h
Core/MemMap.cpp
diff --git a/Core/Core.cpp b/Core/Core.cpp
index 5993f44255fd..e30f5825d884 100644
--- a/Core/Core.cpp
+++ b/Core/Core.cpp
@@ -43,6 +43,7 @@
#include "Core/MIPS/MIPS.h"
#include "Core/HLE/sceNetAdhoc.h"
#include "GPU/Debugger/Stepping.h"
+#include "Core/MIPS/MIPSTracer.h"
#ifdef _WIN32
#include "Common/CommonWindows.h"
@@ -334,6 +335,9 @@ bool Core_Run(GraphicsContext *ctx) {
void Core_EnableStepping(bool step, const char *reason, u32 relatedAddress) {
if (step) {
+ // Stop the tracer
+ mipsTracer.stop_tracing();
+
Core_UpdateState(CORE_STEPPING);
steppingCounter++;
_assert_msg_(reason != nullptr, "No reason specified for break");
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index af66592e0114..b73d036dafa4 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -992,6 +992,7 @@
+
true
true
@@ -1406,6 +1407,7 @@
+
true
true
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 2fd6262e0155..e00f5b24ba78 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -123,6 +123,9 @@
MIPS
+
+ MIPS
+
MIPS
@@ -1347,6 +1350,9 @@
MIPS
+
+ MIPS
+
MIPS
diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp
index 024609bf7018..6c8e59fa3864 100644
--- a/Core/MIPS/IR/IRFrontend.cpp
+++ b/Core/MIPS/IR/IRFrontend.cpp
@@ -28,6 +28,9 @@
#include "Core/MIPS/IR/IRRegCache.h"
#include "Core/MIPS/IR/IRPassSimplify.h"
#include "Core/MIPS/IR/IRInterpreter.h"
+#include "Core/MIPS/MIPSTracer.h"
+
+#include
namespace MIPSComp {
@@ -299,7 +302,17 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &m
// logBlocks = 1;
}
- instructions = code->GetInstructions();
+ if (!mipsTracer.tracing_enabled) {
+ instructions = code->GetInstructions();
+ }
+ else {
+ std::vector block_instructions = code->GetInstructions();
+ instructions.reserve(block_instructions.capacity());
+ // The first instruction is "Downcount"
+ instructions.push_back(block_instructions.front());
+ instructions.push_back({ IROp::LogIRBlock, 0, 0, 0, 0 });
+ std::copy(block_instructions.begin() + 1, block_instructions.end(), std::back_inserter(instructions));
+ }
if (logBlocks > 0 && dontLogBlocks == 0) {
char temp2[256];
diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp
index b591cfbd591e..13eb16a52f0f 100644
--- a/Core/MIPS/IR/IRInst.cpp
+++ b/Core/MIPS/IR/IRInst.cpp
@@ -189,6 +189,8 @@ static const IRMeta irMeta[] = {
{ IROp::RestoreRoundingMode, "RestoreRoundingMode", "" },
{ IROp::ApplyRoundingMode, "ApplyRoundingMode", "" },
{ IROp::UpdateRoundingMode, "UpdateRoundingMode", "" },
+
+ { IROp::LogIRBlock, "LogIRBlock", "" },
};
const IRMeta *metaIndex[256];
diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h
index a7762d08824a..0dbfa6790520 100644
--- a/Core/MIPS/IR/IRInst.h
+++ b/Core/MIPS/IR/IRInst.h
@@ -237,6 +237,9 @@ enum class IROp : uint8_t {
ValidateAddress32,
ValidateAddress128,
+ // Tracing support.
+ LogIRBlock,
+
Nop,
Bad,
};
diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp
index 5af7b98dd602..0d482b485d9a 100644
--- a/Core/MIPS/IR/IRInterpreter.cpp
+++ b/Core/MIPS/IR/IRInterpreter.cpp
@@ -32,6 +32,7 @@
#include "Core/MIPS/IR/IRInst.h"
#include "Core/MIPS/IR/IRInterpreter.h"
#include "Core/System.h"
+#include "Core/MIPS/MIPSTracer.h"
#ifdef mips
// Why do MIPS compilers define something so generic? Try to keep defined, at least...
@@ -1236,6 +1237,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
return mips->pc;
}
break;
+ case IROp::LogIRBlock:
+ if (mipsTracer.tracing_enabled) {
+ mipsTracer.executed_blocks.push_back(inst->constant);
+ }
+ break;
case IROp::Nop: // TODO: This shouldn't crash, but for now we should not emit nops, so...
case IROp::Bad:
diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp
index f1d6724f4561..a06d76b65532 100644
--- a/Core/MIPS/IR/IRJit.cpp
+++ b/Core/MIPS/IR/IRJit.cpp
@@ -42,6 +42,8 @@
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/Reporting.h"
#include "Common/TimeUtil.h"
+#include "Core/MIPS/MIPSTracer.h"
+
namespace MIPSComp {
@@ -165,14 +167,20 @@ bool IRJit::CompileBlock(u32 em_address, std::vector &instructions, u32
}
IRBlock *b = blocks_.GetBlock(block_num);
- if (preload) {
+ if (preload || mipsTracer.tracing_enabled) {
// Hash, then only update page stats, don't link yet.
// TODO: Should we always hash? Then we can reuse blocks.
b->UpdateHash();
}
+
if (!CompileNativeBlock(&blocks_, block_num, preload))
return false;
- // Overwrites the first instruction, and also updates stats.
+
+ if (mipsTracer.tracing_enabled) {
+ mipsTracer.prepare_block(b, blocks_);
+ }
+
+ // Updates stats, also patches the first MIPS instruction into an emuhack if 'preload == false'
blocks_.FinalizeBlock(block_num, preload);
if (!preload)
FinalizeNativeBlock(&blocks_, block_num);
diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h
index 39d4f2a24578..c8ef2d5192c3 100644
--- a/Core/MIPS/IR/IRJit.h
+++ b/Core/MIPS/IR/IRJit.h
@@ -91,6 +91,9 @@ class IRBlock {
u32 GetOriginalStart() const {
return origAddr_;
}
+ u64 GetHash() const {
+ return hash_;
+ }
void Finalize(int number);
void Destroy(int number);
diff --git a/Core/MIPS/MIPSTracer.cpp b/Core/MIPS/MIPSTracer.cpp
new file mode 100644
index 000000000000..20289a5d926b
--- /dev/null
+++ b/Core/MIPS/MIPSTracer.cpp
@@ -0,0 +1,207 @@
+// Copyright (c) 2024- PPSSPP Project.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 2.0 or later versions.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License 2.0 for more details.
+
+// A copy of the GPL 2.0 should have been included with the program.
+// If not, see http://www.gnu.org/licenses/
+
+// Official git repository and contact information can be found at
+// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#include "Core/MIPS/MIPSTracer.h"
+
+#include // for std::memcpy
+#include "Core/MIPS/MIPSTables.h" // for MIPSDisAsm
+#include "Core/MemMap.h" // for Memory::GetPointerUnchecked
+#include "Common/File/FileUtil.h" // for the File::OpenCFile
+
+
+bool TraceBlockStorage::save_block(const u32* instructions, u32 size) {
+ // 'size' is measured in bytes
+ const auto indexes_count = size / 4;
+
+ if (cur_index + 1 + indexes_count >= raw_instructions.size()) {
+ return false;
+ }
+
+ // Save the size first
+ *cur_data_ptr = size;
+ ++cur_data_ptr;
+
+ // Now save the MIPS instructions
+ std::memcpy(cur_data_ptr, instructions, size);
+ cur_data_ptr += indexes_count;
+
+ cur_index += 1 + indexes_count;
+ return true;
+}
+
+void TraceBlockStorage::initialize(u32 capacity) {
+ raw_instructions.resize(capacity);
+ cur_index = 0;
+ cur_data_ptr = raw_instructions.data();
+ INFO_LOG(Log::JIT, "TraceBlockStorage initialized: capacity=0x%x", capacity);
+}
+
+void TraceBlockStorage::clear() {
+ raw_instructions.clear();
+ cur_index = 0;
+ cur_data_ptr = nullptr;
+ INFO_LOG(Log::JIT, "TraceBlockStorage cleared");
+}
+
+void MIPSTracer::prepare_block(const MIPSComp::IRBlock* block, MIPSComp::IRBlockCache& blocks) {
+ u32 virt_addr, size;
+ block->GetRange(&virt_addr, &size);
+
+ u64 hash = block->GetHash();
+ auto it = hash_to_storage_index.find(hash);
+
+ u32 storage_index;
+ if (it != hash_to_storage_index.end()) {
+ // We've seen this one before => it's saved in our storage
+ storage_index = it->second;
+ }
+ else {
+ // We haven't seen a block like that before, let's save it
+ auto mips_instructions_ptr = (const u32*)Memory::GetPointerUnchecked(virt_addr);
+
+ storage_index = storage.cur_index;
+ if (!storage.save_block(mips_instructions_ptr, size)) {
+ // We ran out of storage!
+ WARN_LOG(Log::JIT, "The MIPSTracer ran out of storage for the blocks, cannot proceed!");
+ stop_tracing();
+ return;
+ }
+ // Successfully inserted the block at index 'storage_index'!
+
+ hash_to_storage_index.emplace(hash, storage_index);
+ }
+
+ // NB!
+ // If for some reason the blocks get invalidated while tracing, PPSSPP will be forced to recompile
+ // the same code again => the 'trace_info' will be filled with duplicates, because we can't detect that...
+ // If we store the TraceBlockInfo instances in an unordered_map, we won't be able to reference the entries
+ // by using the 4 byte IRInst field 'constant' (the iterators won't fit there).
+ // And, of course, doing a linear search in the vector is not worth the conserved space.
+ trace_info.push_back({ virt_addr, storage_index });
+
+
+ u32 index = trace_info.size() - 1;
+ auto ir_ptr = (IRInst*)blocks.GetBlockInstructionPtr(*block);
+ ir_ptr[1].constant = index;
+}
+
+bool MIPSTracer::flush_to_file() {
+ INFO_LOG(Log::JIT, "Flushing the trace to a file...");
+ output = File::OpenCFile(logging_path, "w");
+
+ if (!output) {
+ WARN_LOG(Log::JIT, "MIPSTracer failed to open the file '%s'", logging_path.c_str());
+ return false;
+ }
+ auto trace = executed_blocks.get_content();
+ for (auto index : trace) {
+ auto& block_info = trace_info[index];
+ flush_block_to_file(block_info);
+ }
+
+ INFO_LOG(Log::JIT, "Trace flushed, closing the file...");
+ std::fclose(output);
+
+ clear();
+ return true;
+}
+
+void MIPSTracer::flush_block_to_file(const TraceBlockInfo& block_info) {
+ char buffer[512];
+
+ // The log format is '{prefix}{disassembled line}', where 'prefix' is '0x{8 hex digits of the address}: '
+ const auto prefix_size = 2 + 8 + 2;
+
+ u32 addr = block_info.virt_address;
+ u32 index = block_info.storage_index;
+
+ u32 size = storage[index];
+ ++index;
+
+ u32 end_addr = addr + size;
+
+
+ for (; addr < end_addr; addr += 4, ++index) {
+ snprintf(buffer, sizeof(buffer), "0x%08x: ", addr);
+ MIPSDisAsm(storage.read_asm(index), addr, buffer + prefix_size, sizeof(buffer) - prefix_size, true);
+
+ std::fprintf(output, "%s\n", buffer);
+ }
+}
+
+void MIPSTracer::start_tracing() {
+ if (!tracing_enabled) {
+ INFO_LOG(Log::JIT, "MIPSTracer enabled");
+ tracing_enabled = true;
+ }
+}
+
+void MIPSTracer::stop_tracing() {
+ if (tracing_enabled) {
+ INFO_LOG(Log::JIT, "MIPSTracer disabled");
+ tracing_enabled = false;
+
+#ifdef _DEBUG
+ print_stats();
+#endif
+ }
+}
+
+inline void MIPSTracer::print_stats() const {
+ // First, the storage
+ INFO_LOG(Log::JIT, "=============== MIPSTracer storage ===============");
+ INFO_LOG(Log::JIT, "Current index = %d, storage size = %d", storage.cur_index, storage.raw_instructions.size());
+
+ // Then the cyclic buffer
+ if (executed_blocks.overflow) {
+ INFO_LOG(Log::JIT, "=============== MIPSTracer cyclic buffer (overflow) ===============");
+ INFO_LOG(Log::JIT, "Trace size = %d, starts from index %d", executed_blocks.buffer.size(), executed_blocks.current_index);
+ }
+ else {
+ INFO_LOG(Log::JIT, "=============== MIPSTracer cyclic buffer (no overflow) ===============");
+ INFO_LOG(Log::JIT, "Trace size = %d, starts from index 0", executed_blocks.current_index);
+ }
+ // Next, the hash-to-index mapping
+ INFO_LOG(Log::JIT, "=============== MIPSTracer hashes ===============");
+ INFO_LOG(Log::JIT, "Number of unique hashes = %d", hash_to_storage_index.size());
+
+ // Finally, the basic block list
+ INFO_LOG(Log::JIT, "=============== MIPSTracer basic block list ===============");
+ INFO_LOG(Log::JIT, "Number of processed basic blocks = %d", trace_info.size());
+
+ INFO_LOG(Log::JIT, "=============== MIPSTracer stats end ===============");
+}
+
+void MIPSTracer::initialize(u32 storage_capacity, u32 max_trace_size) {
+ executed_blocks.resize(max_trace_size);
+ hash_to_storage_index.reserve(max_trace_size);
+ storage.initialize(storage_capacity);
+ trace_info.reserve(max_trace_size);
+ INFO_LOG(Log::JIT, "MIPSTracer initialized: storage_capacity=0x%x, max_trace_size=%d", storage_capacity, max_trace_size);
+}
+
+void MIPSTracer::clear() {
+ executed_blocks.clear();
+ hash_to_storage_index.clear();
+ storage.clear();
+ trace_info.clear();
+ INFO_LOG(Log::JIT, "MIPSTracer cleared");
+}
+
+MIPSTracer mipsTracer;
+
+
diff --git a/Core/MIPS/MIPSTracer.h b/Core/MIPS/MIPSTracer.h
new file mode 100644
index 000000000000..8ba1bef6e854
--- /dev/null
+++ b/Core/MIPS/MIPSTracer.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2024- PPSSPP Project.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 2.0 or later versions.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License 2.0 for more details.
+
+// A copy of the GPL 2.0 should have been included with the program.
+// If not, see http://www.gnu.org/licenses/
+
+// Official git repository and contact information can be found at
+// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "Common/CommonTypes.h"
+#include "Core/Opcode.h"
+#include "Core/MIPS/IR/IRJit.h"
+#include "Common/Log.h"
+#include "Common/File/Path.h"
+
+
+struct TraceBlockInfo {
+ u32 virt_address;
+ u32 storage_index;
+};
+
+struct TraceBlockStorage {
+ std::vector raw_instructions;
+ u32 cur_index;
+ u32* cur_data_ptr;
+
+ TraceBlockStorage(u32 capacity):
+ raw_instructions(capacity, 0),
+ cur_index(0),
+ cur_data_ptr(raw_instructions.data())
+ {}
+
+ TraceBlockStorage(): raw_instructions(), cur_index(0), cur_data_ptr(nullptr) {}
+
+ bool save_block(const u32* instructions, u32 size);
+
+ void initialize(u32 capacity);
+ void clear();
+
+ u32 operator[](u32 index) {
+ return raw_instructions[index];
+ }
+ Memory::Opcode read_asm(u32 index) {
+ return Memory::Opcode(raw_instructions[index]);
+ }
+};
+
+
+template
+struct CyclicBuffer {
+ std::vector buffer;
+ u32 current_index;
+ bool overflow;
+
+ explicit CyclicBuffer(u32 capacity) : buffer(capacity, T()), current_index(0), overflow(false) {}
+
+ CyclicBuffer(): buffer(), current_index(0), overflow(false) {}
+
+ void push_back(const T& value);
+ void push_back(T&& value);
+
+ void clear();
+ void resize(u32 new_capacity);
+
+ std::vector get_content() const;
+};
+
+template
+std::vector CyclicBuffer::get_content() const {
+ if (!overflow) {
+ return std::vector(buffer.begin(), buffer.begin() + current_index);
+ }
+
+ std::vector ans;
+ ans.reserve(buffer.size());
+ std::copy(buffer.begin() + current_index, buffer.end(), std::back_inserter(ans));
+ std::copy(buffer.begin(), buffer.begin() + current_index, std::back_inserter(ans));
+ return ans;
+}
+
+template
+void CyclicBuffer::push_back(const T& value) {
+ buffer[current_index] = value;
+ ++current_index;
+ if (current_index == buffer.size()) {
+ current_index = 0;
+ overflow = true;
+ }
+}
+
+template
+void CyclicBuffer::push_back(T&& value) {
+ buffer[current_index] = std::move(value);
+ ++current_index;
+ if (current_index == buffer.size()) {
+ current_index = 0;
+ overflow = true;
+ }
+}
+
+template
+void CyclicBuffer::clear() {
+ buffer.clear();
+ current_index = 0;
+ overflow = false;
+}
+
+template
+void CyclicBuffer::resize(u32 new_capacity) {
+ buffer.resize(new_capacity);
+}
+
+
+// This system is meant for trace recording.
+// A trace here stands for a sequence of instructions and their respective addresses in RAM.
+// The register/memory changes (or thread switches) are not included!
+// Note: the tracer stores the basic blocks inside, which causes the last block to be dumped as a whole,
+// despite the fact that it may not have executed to its end by the time the tracer is stopped.
+struct MIPSTracer {
+ std::vector trace_info;
+
+ // The trace might be very big, in that case I don't mind losing the oldest entries.
+ CyclicBuffer executed_blocks;
+
+ std::unordered_map hash_to_storage_index;
+
+ TraceBlockStorage storage;
+
+ Path logging_path;
+ FILE* output;
+ bool tracing_enabled = false;
+
+ int in_storage_capacity = 0x10'0000;
+ int in_max_trace_size = 0x10'0000;
+
+ void start_tracing();
+ void stop_tracing();
+
+ void prepare_block(const MIPSComp::IRBlock* block, MIPSComp::IRBlockCache& blocks);
+ void set_logging_path(std::string path) {
+ logging_path = Path(path);
+ }
+ std::string get_logging_path() const {
+ return logging_path.ToString();
+ }
+
+ bool flush_to_file();
+ void flush_block_to_file(const TraceBlockInfo& block);
+
+ void initialize(u32 storage_capacity, u32 max_trace_size);
+ void clear();
+
+ inline void print_stats() const;
+
+ MIPSTracer(): trace_info(), executed_blocks(), hash_to_storage_index(), storage(), logging_path() {}
+};
+
+extern MIPSTracer mipsTracer;
diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp
index 229354f53c7e..2545438e7109 100644
--- a/UI/GameSettingsScreen.cpp
+++ b/UI/GameSettingsScreen.cpp
@@ -79,6 +79,9 @@
#include "GPU/GPUInterface.h"
#include "GPU/Common/FramebufferManagerCommon.h"
+#include "Core/Core.h" // for Core_IsStepping
+#include "Core/MIPS/MIPSTracer.h"
+
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
#include "UI/DarwinFileSystemServices.h"
#endif
@@ -1887,6 +1890,60 @@ void DeveloperToolsScreen::CreateViews() {
auto displayRefreshRate = list->Add(new PopupSliderChoice(&g_Config.iDisplayRefreshRate, 60, 1000, 60, dev->T("Display refresh rate"), 1, screenManager()));
displayRefreshRate->SetFormat(dev->T("%d Hz"));
+#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
+ list->Add(new ItemHeader(dev->T("MIPSTracer")));
+
+ MIPSTracerEnabled_ = mipsTracer.tracing_enabled;
+ CheckBox *MIPSLoggerEnabled = new CheckBox(&MIPSTracerEnabled_, dev->T("MIPSTracer enabled"));
+ list->Add(MIPSLoggerEnabled)->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerEnabled);
+ MIPSLoggerEnabled->SetEnabledFunc([]() {
+ bool temp = g_Config.iCpuCore == static_cast(CPUCore::IR_INTERPRETER) && PSP_IsInited();
+ return temp && Core_IsStepping() && coreState != CORE_POWERDOWN;
+ });
+
+ Choice *MIPSlogging_path = list->Add(new Choice(dev->T("Select the output logging file")));
+ MIPSlogging_path->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerPathChanged);
+ MIPSlogging_path->SetEnabledFunc([]() {
+ if (!PSP_IsInited())
+ return false;
+ return true;
+ });
+
+ MIPSTracerPath_ = mipsTracer.get_logging_path();
+ MIPSTracerPath = list->Add(new InfoItem(dev->T("Current log file"), MIPSTracerPath_));
+
+ PopupSliderChoice* storage_capacity = list->Add(
+ new PopupSliderChoice(
+ &mipsTracer.in_storage_capacity, 0x4'0000, 0x40'0000, 0x10'0000, dev->T("Storage capacity"), 0x10000, screenManager()
+ )
+ );
+ storage_capacity->SetFormat("0x%x asm opcodes");
+ storage_capacity->OnChange.Add([&](UI::EventParams &) {
+ INFO_LOG(Log::JIT, "User changed the tracer's storage capacity to 0x%x", mipsTracer.in_storage_capacity);
+ return UI::EVENT_CONTINUE;
+ });
+
+ PopupSliderChoice* trace_max_size = list->Add(
+ new PopupSliderChoice(
+ &mipsTracer.in_max_trace_size, 0x1'0000, 0x40'0000, 0x10'0000, dev->T("Max allowed trace size"), 0x10000, screenManager()
+ )
+ );
+ trace_max_size->SetFormat("%d basic blocks");
+ trace_max_size->OnChange.Add([&](UI::EventParams &) {
+ INFO_LOG(Log::JIT, "User changed the tracer's max trace size to %d", mipsTracer.in_max_trace_size);
+ return UI::EVENT_CONTINUE;
+ });
+
+ Button *FlushTrace = list->Add(new Button(dev->T("Flush the trace")));
+ FlushTrace->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerFlushTrace);
+
+ Button *InvalidateJitCache = list->Add(new Button(dev->T("Clear the JIT cache")));
+ InvalidateJitCache->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerClearJitCache);
+
+ Button *ClearMIPSTracer = list->Add(new Button(dev->T("Clear the MIPSTracer")));
+ ClearMIPSTracer->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerClearTracer);
+#endif
+
Draw::DrawContext *draw = screenManager()->getDrawContext();
list->Add(new ItemHeader(dev->T("Ubershaders")));
@@ -2076,6 +2133,51 @@ UI::EventReturn DeveloperToolsScreen::OnRemoteDebugger(UI::EventParams &e) {
return UI::EVENT_DONE;
}
+UI::EventReturn DeveloperToolsScreen::OnMIPSTracerEnabled(UI::EventParams &e) {
+ if (MIPSTracerEnabled_) {
+ u32 capacity = mipsTracer.in_storage_capacity;
+ u32 trace_size = mipsTracer.in_max_trace_size;
+
+ mipsTracer.initialize(capacity, trace_size);
+ mipsTracer.start_tracing();
+ }
+ else {
+ mipsTracer.stop_tracing();
+ }
+ return UI::EVENT_DONE;
+}
+
+UI::EventReturn DeveloperToolsScreen::OnMIPSTracerPathChanged(UI::EventParams &e) {
+ auto dev = GetI18NCategory(I18NCat::DEVELOPER);
+ System_BrowseForFile(GetRequesterToken(), dev->T("Select the log file"), BrowseFileType::ANY,
+ [this](const std::string &value, int) {
+ mipsTracer.set_logging_path(value);
+ MIPSTracerPath_ = value;
+ MIPSTracerPath->SetRightText(MIPSTracerPath_);
+ });
+ return UI::EVENT_DONE;
+}
+
+UI::EventReturn DeveloperToolsScreen::OnMIPSTracerFlushTrace(UI::EventParams &e) {
+ bool success = mipsTracer.flush_to_file();
+ if (!success) {
+ WARN_LOG(Log::JIT, "Error: cannot flush the trace to the specified file!");
+ }
+ return UI::EVENT_DONE;
+}
+
+UI::EventReturn DeveloperToolsScreen::OnMIPSTracerClearJitCache(UI::EventParams &e) {
+ INFO_LOG(Log::JIT, "Clearing the jit cache...");
+ System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT);
+ return UI::EVENT_DONE;
+}
+
+UI::EventReturn DeveloperToolsScreen::OnMIPSTracerClearTracer(UI::EventParams &e) {
+ INFO_LOG(Log::JIT, "Clearing the MIPSTracer...");
+ mipsTracer.clear();
+ return UI::EVENT_DONE;
+}
+
void DeveloperToolsScreen::update() {
UIDialogScreenWithBackground::update();
allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER);
diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h
index 94be03002527..87ad8eca4475 100644
--- a/UI/GameSettingsScreen.h
+++ b/UI/GameSettingsScreen.h
@@ -150,6 +150,11 @@ class DeveloperToolsScreen : public UIDialogScreenWithGameBackground {
UI::EventReturn OnJitAffectingSetting(UI::EventParams &e);
UI::EventReturn OnJitDebugTools(UI::EventParams &e);
UI::EventReturn OnRemoteDebugger(UI::EventParams &e);
+ UI::EventReturn OnMIPSTracerEnabled(UI::EventParams &e);
+ UI::EventReturn OnMIPSTracerPathChanged(UI::EventParams &e);
+ UI::EventReturn OnMIPSTracerFlushTrace(UI::EventParams &e);
+ UI::EventReturn OnMIPSTracerClearJitCache(UI::EventParams &e);
+ UI::EventReturn OnMIPSTracerClearTracer(UI::EventParams &e);
UI::EventReturn OnGPUDriverTest(UI::EventParams &e);
UI::EventReturn OnFramedumpTest(UI::EventParams &e);
UI::EventReturn OnMemstickTest(UI::EventParams &e);
@@ -164,6 +169,10 @@ class DeveloperToolsScreen : public UIDialogScreenWithGameBackground {
MAYBE,
};
HasIni hasTexturesIni_ = HasIni::MAYBE;
+
+ bool MIPSTracerEnabled_ = false;
+ std::string MIPSTracerPath_ = "";
+ UI::InfoItem* MIPSTracerPath = nullptr;
};
class HostnameSelectScreen : public PopupScreen {
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj
index 5a8eafb8dd0b..0a64ea0a2935 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj
+++ b/UWP/CoreUWP/CoreUWP.vcxproj
@@ -305,6 +305,7 @@
+
@@ -583,6 +584,7 @@
+
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters
index 591347ecc226..0434c2142bdd 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj.filters
+++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters
@@ -555,6 +555,9 @@
MIPS
+
+ MIPS
+
Dialog
@@ -1624,6 +1627,9 @@
MIPS
+
+ MIPS
+
Dialog
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 8ad6ea1aacd9..8e45dc5eb4d0 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -447,6 +447,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/MIPS/MIPSVFPUFallbacks.cpp.arm \
$(SRC)/Core/MIPS/MIPSCodeUtils.cpp.arm \
$(SRC)/Core/MIPS/MIPSDebugInterface.cpp \
+ $(SRC)/Core/MIPS/MIPSTracer.cpp \
$(SRC)/Core/MIPS/IR/IRAnalysis.cpp \
$(SRC)/Core/MIPS/IR/IRFrontend.cpp \
$(SRC)/Core/MIPS/IR/IRJit.cpp \
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index 74e06c3c387d..eb771382c418 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -758,6 +758,7 @@ SOURCES_CXX += \
$(COREDIR)/MIPS/MIPSStackWalk.cpp \
$(COREDIR)/MIPS/MIPSVFPUUtils.cpp \
$(COREDIR)/MIPS/MIPSVFPUFallbacks.cpp \
+ $(COREDIR)/MIPS/MIPSTracer.cpp \
$(COREDIR)/MemFault.cpp \
$(COREDIR)/MemMap.cpp \
$(COREDIR)/MemMapFunctions.cpp \