mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-23 05:19:56 +00:00
213 lines
6.7 KiB
C++
213 lines
6.7 KiB
C++
// 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 <cstring> // 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 = (u32)(trace_info.size() - 1);
|
|
auto ir_ptr = (IRInst*)blocks.GetBlockInstructionPtr(*block);
|
|
ir_ptr[1].constant = index;
|
|
}
|
|
|
|
bool MIPSTracer::flush_to_file() {
|
|
if (logging_path.empty()) {
|
|
WARN_LOG(Log::JIT, "The path is empty, cannot flush the trace!");
|
|
return false;
|
|
}
|
|
|
|
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, (int)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", (int)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", (int)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", (int)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;
|
|
|
|
|