// Copyright 2013 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/PowerPC/JitInterface.h" #include #include #include #include #include #ifdef _WIN32 #include #else #include "Common/PerformanceCounter.h" #endif #include #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/IOFile.h" #include "Common/MsgHandler.h" #include "Core/Core.h" #include "Core/PowerPC/CPUCoreBase.h" #include "Core/PowerPC/CachedInterpreter/CachedInterpreter.h" #include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/Profiler.h" #if _M_X86 #include "Core/PowerPC/Jit64/Jit.h" #endif #if _M_ARM_64 #include "Core/PowerPC/JitArm64/Jit.h" #endif namespace JitInterface { static JitBase* g_jit = nullptr; void SetJit(JitBase* jit) { g_jit = jit; } void DoState(PointerWrap& p) { if (g_jit && p.GetMode() == PointerWrap::MODE_READ) g_jit->ClearCache(); } CPUCoreBase* InitJitCore(PowerPC::CPUCore core) { switch (core) { #if _M_X86 case PowerPC::CPUCore::JIT64: g_jit = new Jit64(); break; #endif #if _M_ARM_64 case PowerPC::CPUCore::JITARM64: g_jit = new JitArm64(); break; #endif case PowerPC::CPUCore::CachedInterpreter: g_jit = new CachedInterpreter(); break; default: // Under this case the caller overrides the CPU core to the default and logs that // it performed the override. g_jit = nullptr; return nullptr; } g_jit->Init(); return g_jit; } CPUCoreBase* GetCore() { return g_jit; } void SetProfilingState(ProfilingState state) { if (!g_jit) return; g_jit->jo.profile_blocks = state == ProfilingState::Enabled; } void WriteProfileResults(const std::string& filename) { Profiler::ProfileStats prof_stats; GetProfileResults(&prof_stats); File::IOFile f(filename, "w"); if (!f) { PanicAlertFmt("Failed to open {}", filename); return; } f.WriteString("origAddr\tblkName\trunCount\tcost\ttimeCost\tpercent\ttimePercent\tOvAllinBlkTime(" "ms)\tblkCodeSize\n"); for (auto& stat : prof_stats.block_stats) { std::string name = g_symbolDB.GetDescription(stat.addr); double percent = 100.0 * (double)stat.cost / (double)prof_stats.cost_sum; double timePercent = 100.0 * (double)stat.tick_counter / (double)prof_stats.timecost_sum; f.WriteString(fmt::format("{0:08x}\t{1}\t{2}\t{3}\t{4}\t{5:.2f}\t{6:.2f}\t{7:.2f}\t{8}\n", stat.addr, name, stat.run_count, stat.cost, stat.tick_counter, percent, timePercent, static_cast(stat.tick_counter) * 1000.0 / static_cast(prof_stats.countsPerSec), stat.block_size)); } } void GetProfileResults(Profiler::ProfileStats* prof_stats) { // Can't really do this with no g_jit core available if (!g_jit) return; prof_stats->cost_sum = 0; prof_stats->timecost_sum = 0; prof_stats->block_stats.clear(); Core::RunAsCPUThread([&prof_stats] { QueryPerformanceFrequency((LARGE_INTEGER*)&prof_stats->countsPerSec); g_jit->GetBlockCache()->RunOnBlocks([&prof_stats](const JitBlock& block) { const auto& data = block.profile_data; u64 cost = data.downcountCounter; u64 timecost = data.ticCounter; // Todo: tweak. if (data.runCount >= 1) prof_stats->block_stats.emplace_back(block.effectiveAddress, cost, timecost, data.runCount, block.codeSize); prof_stats->cost_sum += cost; prof_stats->timecost_sum += timecost; }); sort(prof_stats->block_stats.begin(), prof_stats->block_stats.end()); }); } int GetHostCode(u32* address, const u8** code, u32* code_size) { if (!g_jit) { *code_size = 0; return 1; } JitBlock* block = g_jit->GetBlockCache()->GetBlockFromStartAddress(*address, MSR.Hex); if (!block) { for (int i = 0; i < 500; i++) { block = g_jit->GetBlockCache()->GetBlockFromStartAddress(*address - 4 * i, MSR.Hex); if (block) break; } if (block) { if (!(block->effectiveAddress <= *address && block->originalSize + block->effectiveAddress >= *address)) block = nullptr; } // Do not merge this "if" with the above - block_num changes inside it. if (!block) { *code_size = 0; return 2; } } *code = block->checkedEntry; *code_size = block->codeSize; *address = block->effectiveAddress; return 0; } bool HandleFault(uintptr_t access_address, SContext* ctx) { // Prevent nullptr dereference on a crash with no JIT present if (!g_jit) { return false; } return g_jit->HandleFault(access_address, ctx); } bool HandleStackFault() { if (!g_jit) { return false; } return g_jit->HandleStackFault(); } void ClearCache() { if (g_jit) g_jit->ClearCache(); } void ClearSafe() { if (g_jit) g_jit->GetBlockCache()->Clear(); } void InvalidateICache(u32 address, u32 size, bool forced) { if (g_jit) g_jit->GetBlockCache()->InvalidateICache(address, size, forced); } void InvalidateICacheLine(u32 address) { if (g_jit) g_jit->GetBlockCache()->InvalidateICacheLine(address); } void InvalidateICacheLines(u32 address, u32 count) { // This corresponds to a PPC code loop that: // - calls some form of dcb* instruction on 'address' // - increments 'address' by the size of a cache line (0x20 bytes) // - decrements 'count' by 1 // - jumps back to the dcb* instruction if 'count' != 0 // with an extra optimization for the case of a single cache line invalidation if (count == 1) InvalidateICacheLine(address); else if (count == 0 || count >= static_cast(0x1'0000'0000 / 32)) InvalidateICache(address & ~0x1f, 0xffffffff, false); else InvalidateICache(address & ~0x1f, 32 * count, false); } void CompileExceptionCheck(ExceptionType type) { if (!g_jit) return; std::unordered_set* exception_addresses = nullptr; switch (type) { case ExceptionType::FIFOWrite: exception_addresses = &g_jit->js.fifoWriteAddresses; break; case ExceptionType::PairedQuantize: exception_addresses = &g_jit->js.pairedQuantizeAddresses; break; case ExceptionType::SpeculativeConstants: exception_addresses = &g_jit->js.noSpeculativeConstantsAddresses; break; } if (PC != 0 && (exception_addresses->find(PC)) == (exception_addresses->end())) { if (type == ExceptionType::FIFOWrite) { // Check in case the code has been replaced since: do we need to do this? const OpType optype = PPCTables::GetOpInfo(PowerPC::HostRead_U32(PC))->type; if (optype != OpType::Store && optype != OpType::StoreFP && optype != OpType::StorePS) return; } exception_addresses->insert(PC); // Invalidate the JIT block so that it gets recompiled with the external exception check // included. g_jit->GetBlockCache()->InvalidateICache(PC, 4, true); } } void Shutdown() { if (g_jit) { g_jit->Shutdown(); delete g_jit; g_jit = nullptr; } } } // namespace JitInterface