Path: blob/master/Core/MIPS/JitCommon/JitBlockCache.cpp
5669 views
// Copyright (c) 2012- PPSSPP Project / Dolphin Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "ppsspp_config.h"18#include <cstddef>19#include <algorithm>2021#include "ext/xxhash.h"22#include "Common/CommonTypes.h"23#include "Common/Profiler/Profiler.h"2425#ifdef _WIN3226#include "Common/CommonWindows.h"27#endif2829#include "Core/Core.h"30#include "Core/MemMap.h"31#include "Core/CoreTiming.h"32#include "Core/Reporting.h"33#include "Core/Config.h"3435#include "Core/MIPS/MIPS.h"36#include "Core/MIPS/MIPSTables.h"37#include "Core/MIPS/MIPSAnalyst.h"3839#include "Core/MIPS/JitCommon/JitBlockCache.h"40#include "Core/MIPS/JitCommon/JitCommon.h"4142constexpr u32 INVALID_EXIT = 0xFFFFFFFF;4344static uint64_t HashJitBlock(const JitBlock &b) {45PROFILE_THIS_SCOPE("jithash");46if (JIT_USE_COMPILEDHASH) {47// Includes the emuhack (or emuhacks) in memory.48if (Memory::IsValidRange(b.originalAddress, b.originalSize * 4)) {49return XXH3_64bits(Memory::GetPointerUnchecked(b.originalAddress), b.originalSize * 4);50} else {51// Hm, this would be bad.52return 0;53}54}55return 0;56}5758JitBlockCache::JitBlockCache(MIPSState *mipsState, CodeBlockCommon *codeBlock) :59codeBlock_(codeBlock) {60}6162JitBlockCache::~JitBlockCache() {63Shutdown();64}6566bool JitBlock::ContainsAddress(u32 em_address) const {67// WARNING - THIS DOES NOT WORK WITH JIT INLINING ENABLED.68// However, that doesn't exist yet so meh.69return em_address >= originalAddress && em_address < originalAddress + 4 * originalSize;70}7172bool JitBlockCache::IsFull() const {73// Subtract some amount to safely leave space for some proxy blocks, which we don't check before we allocate (not ideal, but should be enough).74return num_blocks_ >= MAX_NUM_BLOCKS - 512;75}7677void JitBlockCache::Init() {78blocks_ = new JitBlock[MAX_NUM_BLOCKS];79Clear();80}8182void JitBlockCache::Shutdown() {83Clear(); // Make sure proxy block links are deleted84delete [] blocks_;85blocks_ = 0;86num_blocks_ = 0;87}8889// This clears the JIT cache. It's called from JitCache.cpp when the JIT cache90// is full and when saving and loading states.91void JitBlockCache::Clear() {92// Note: We intentionally clear the block_map_ first to avoid O(N^2) behavior in RemoveBlockMap93block_map_.clear();94for (int i = 0; i < num_blocks_; i++) {95DestroyBlock(i, DestroyType::CLEAR);96}97proxyBlockMap_.clear();98links_to_.clear();99num_blocks_ = 0;100101blockMemRanges_[JITBLOCK_RANGE_SCRATCH] = std::make_pair(0xFFFFFFFF, 0x00000000);102blockMemRanges_[JITBLOCK_RANGE_RAMBOTTOM] = std::make_pair(0xFFFFFFFF, 0x00000000);103blockMemRanges_[JITBLOCK_RANGE_RAMTOP] = std::make_pair(0xFFFFFFFF, 0x00000000);104}105106void JitBlockCache::Reset() {107Shutdown();108Init();109}110111JitBlock *JitBlockCache::GetBlock(int no) {112return &blocks_[no];113}114115const JitBlock *JitBlockCache::GetBlock(int no) const {116return &blocks_[no];117}118119int JitBlockCache::AllocateBlock(u32 startAddress) {120_assert_(num_blocks_ < MAX_NUM_BLOCKS);121122JitBlock &b = blocks_[num_blocks_];123124b.proxyFor = 0;125// If there's an existing pure proxy block at the address, we need to ditch it and create a new one,126// taking over the proxied blocks.127int num = GetBlockNumberFromStartAddress(startAddress, false);128if (num >= 0) {129if (blocks_[num].IsPureProxy()) {130RemoveBlockMap(num);131blocks_[num].invalid = true;132b.proxyFor = new std::vector<u32>();133*b.proxyFor = *blocks_[num].proxyFor;134blocks_[num].proxyFor->clear();135delete blocks_[num].proxyFor;136blocks_[num].proxyFor = 0;137}138}139140b.invalid = false;141b.originalAddress = startAddress;142for (int i = 0; i < MAX_JIT_BLOCK_EXITS; ++i) {143b.exitAddress[i] = INVALID_EXIT;144b.exitPtrs[i] = 0;145b.linkStatus[i] = false;146}147b.blockNum = num_blocks_;148num_blocks_++; //commit the current block149return num_blocks_ - 1;150}151152void JitBlockCache::ProxyBlock(u32 rootAddress, u32 startAddress, u32 size, const u8 *codePtr) {153_assert_(num_blocks_ < MAX_NUM_BLOCKS);154155// If there's an existing block at the startAddress, add rootAddress as a proxy root of that block156// instead of creating a new block.157int num = GetBlockNumberFromStartAddress(startAddress, false);158if (num != -1) {159DEBUG_LOG(Log::HLE, "Adding proxy root %08x to block at %08x", rootAddress, startAddress);160if (!blocks_[num].proxyFor) {161blocks_[num].proxyFor = new std::vector<u32>();162}163blocks_[num].proxyFor->push_back(rootAddress);164}165166JitBlock &b = blocks_[num_blocks_];167b.invalid = false;168b.originalAddress = startAddress;169b.originalSize = size;170for (int i = 0; i < MAX_JIT_BLOCK_EXITS; ++i) {171b.exitAddress[i] = INVALID_EXIT;172b.exitPtrs[i] = 0;173b.linkStatus[i] = false;174}175b.exitAddress[0] = rootAddress;176b.blockNum = num_blocks_;177b.proxyFor = new std::vector<u32>();178b.SetPureProxy(); // flag as pure proxy block.179180// Make binary searches and stuff work ok181b.normalEntry = codePtr;182b.checkedEntry = codePtr;183proxyBlockMap_.emplace(startAddress, num_blocks_);184AddBlockMap(num_blocks_);185186num_blocks_++; //commit the current block187}188189void JitBlockCache::AddBlockMap(int block_num) {190const JitBlock &b = blocks_[block_num];191// Convert the logical address to a physical address for the block map192u32 pAddr = b.originalAddress & 0x1FFFFFFF;193block_map_[std::make_pair(pAddr + 4 * b.originalSize, pAddr)] = block_num;194}195196void JitBlockCache::RemoveBlockMap(int block_num) {197const JitBlock &b = blocks_[block_num];198if (b.invalid) {199return;200}201202const u32 pAddr = b.originalAddress & 0x1FFFFFFF;203auto it = block_map_.find(std::make_pair(pAddr + 4 * b.originalSize, pAddr));204if (it != block_map_.end() && it->second == (u32)block_num) {205block_map_.erase(it);206} else {207// It wasn't in there, or it has the wrong key. Let's search...208// TODO: This is O(n), so O(n^2) when called for every block.209for (auto it = block_map_.begin(); it != block_map_.end(); ++it) {210if (it->second == (u32)block_num) {211_dbg_assert_(false);212block_map_.erase(it);213break;214}215}216}217}218219static void ExpandRange(std::pair<u32, u32> &range, u32 newStart, u32 newEnd) {220range.first = std::min(range.first, newStart);221range.second = std::max(range.second, newEnd);222}223224void JitBlockCache::FinalizeBlock(int block_num, bool block_link) {225JitBlock &b = blocks_[block_num];226_assert_msg_(Memory::IsValidAddress(b.originalAddress), "FinalizeBlock: Bad originalAddress %08x in block %d (b.num: %d) proxy: %s sz: %d", b.originalAddress, block_num, b.blockNum, b.proxyFor ? "y" : "n", b.codeSize);227228b.originalFirstOpcode = Memory::Read_Opcode_JIT(b.originalAddress);229MIPSOpcode opcode = GetEmuHackOpForBlock(block_num);230Memory::Write_Opcode_JIT(b.originalAddress, opcode);231232// Note that this hashes the emuhack too, which is intentional.233b.compiledHash = HashJitBlock(b);234235AddBlockMap(block_num);236237if (block_link) {238for (int i = 0; i < MAX_JIT_BLOCK_EXITS; i++) {239if (b.exitAddress[i] != INVALID_EXIT) {240links_to_.emplace(b.exitAddress[i], block_num);241}242}243244LinkBlock(block_num);245LinkBlockExits(block_num);246}247248const u32 blockEnd = b.originalAddress + b.originalSize * 4 - 4;249if (Memory::IsScratchpadAddress(b.originalAddress)) {250ExpandRange(blockMemRanges_[JITBLOCK_RANGE_SCRATCH], b.originalAddress, blockEnd);251}252const u32 halfUserMemory = (PSP_GetUserMemoryEnd() - PSP_GetUserMemoryBase()) / 2;253if (b.originalAddress < PSP_GetUserMemoryBase() + halfUserMemory) {254ExpandRange(blockMemRanges_[JITBLOCK_RANGE_RAMBOTTOM], b.originalAddress, blockEnd);255}256if (blockEnd > PSP_GetUserMemoryBase() + halfUserMemory) {257ExpandRange(blockMemRanges_[JITBLOCK_RANGE_RAMTOP], b.originalAddress, blockEnd);258}259}260261bool JitBlockCache::RangeMayHaveEmuHacks(u32 start, u32 end) const {262for (int i = 0; i < JITBLOCK_RANGE_COUNT; ++i) {263if (end >= blockMemRanges_[i].first && start <= blockMemRanges_[i].second) {264return true;265}266}267return false;268}269270static int binary_search(const JitBlock blocks_[], const u8 *baseoff, int imin, int imax) {271while (imin < imax) {272int imid = (imin + imax) >> 1;273if (blocks_[imid].normalEntry < baseoff)274imin = imid + 1;275else276imax = imid;277}278if ((imax == imin) && (blocks_[imin].normalEntry == baseoff))279return imin;280else281return -1;282}283284int JitBlockCache::GetBlockNumberFromEmuHackOp(MIPSOpcode inst, bool ignoreBad) const {285if (!num_blocks_ || !MIPS_IS_EMUHACK(inst)) // definitely not a JIT block286return -1;287int off = (inst & MIPS_EMUHACK_VALUE_MASK);288289const u8 *baseoff = codeBlock_->GetBasePtr() + off;290if (baseoff < codeBlock_->GetBasePtr() || baseoff >= codeBlock_->GetCodePtr()) {291if (!ignoreBad) {292ERROR_LOG(Log::JIT, "JitBlockCache: Invalid Emuhack Op %08x", inst.encoding);293}294return -1;295}296297int bl = binary_search(blocks_, baseoff, 0, num_blocks_ - 1);298if (bl >= 0 && blocks_[bl].invalid) {299return -1;300} else {301return bl;302}303}304305MIPSOpcode JitBlockCache::GetEmuHackOpForBlock(int blockNum) const {306int off = (int)(blocks_[blockNum].normalEntry - codeBlock_->GetBasePtr());307return MIPSOpcode(MIPS_EMUHACK_OPCODE | off);308}309310int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr, bool realBlocksOnly) const {311if (!blocks_ || !Memory::IsValidAddress(addr))312return -1;313314MIPSOpcode inst = MIPSOpcode(Memory::Read_U32(addr));315int bl = GetBlockNumberFromEmuHackOp(inst);316if (bl < 0) {317if (!realBlocksOnly) {318// Wasn't an emu hack op, look through proxyBlockMap_.319auto range = proxyBlockMap_.equal_range(addr);320for (auto it = range.first; it != range.second; ++it) {321const int blockIndex = it->second;322if (blocks_[blockIndex].originalAddress == addr && !blocks_[blockIndex].proxyFor && !blocks_[blockIndex].invalid)323return blockIndex;324}325}326return -1;327}328329if (blocks_[bl].originalAddress != addr)330return -1;331332return bl;333}334335void JitBlockCache::GetBlockNumbersFromAddress(u32 em_address, std::vector<int> *block_numbers) {336for (int i = 0; i < num_blocks_; i++)337if (blocks_[i].ContainsAddress(em_address))338block_numbers->push_back(i);339}340341int JitBlockCache::GetBlockNumberFromAddress(u32 em_address) {342for (int i = 0; i < num_blocks_; i++) {343if (blocks_[i].ContainsAddress(em_address))344return i;345}346347return -1;348}349350u32 JitBlockCache::GetAddressFromBlockPtr(const u8 *ptr) const {351if (!codeBlock_->IsInSpace(ptr))352return (u32)-1;353354for (int i = 0; i < num_blocks_; ++i) {355const auto &b = blocks_[i];356if (!b.invalid && ptr >= b.checkedEntry && ptr < b.normalEntry + b.codeSize) {357return b.originalAddress;358}359}360361// It's in jit somewhere, but we must have deleted it.362return 0;363}364365MIPSOpcode JitBlockCache::GetOriginalFirstOp(int block_num) {366if (block_num >= num_blocks_ || block_num < 0) {367return MIPSOpcode(block_num);368}369return blocks_[block_num].originalFirstOpcode;370}371372void JitBlockCache::LinkBlockExits(int i) {373JitBlock &b = blocks_[i];374if (b.invalid) {375// This block is dead. Don't relink it.376return;377}378if (b.IsPureProxy()) {379// Pure proxies can't link, since they don't have code.380return;381}382383for (int e = 0; e < MAX_JIT_BLOCK_EXITS; e++) {384if (b.exitAddress[e] != INVALID_EXIT && !b.linkStatus[e]) {385int destinationBlock = GetBlockNumberFromStartAddress(b.exitAddress[e], true);386if (destinationBlock == -1) {387continue;388}389390JitBlock &eb = blocks_[destinationBlock];391// Make sure the destination is not invalid.392if (!eb.invalid) {393MIPSComp::jit->LinkBlock(b.exitPtrs[e], eb.checkedEntry);394b.linkStatus[e] = true;395}396}397}398}399400void JitBlockCache::LinkBlock(int i) {401LinkBlockExits(i);402JitBlock &b = blocks_[i];403// equal_range(b) returns pair<iterator,iterator> representing the range404// of element with key b405auto ppp = links_to_.equal_range(b.originalAddress);406if (ppp.first == ppp.second)407return;408for (auto iter = ppp.first; iter != ppp.second; ++iter) {409// INFO_LOG(Log::JIT, "Linking block %i to block %i", iter->second, i);410LinkBlockExits(iter->second);411}412}413414void JitBlockCache::UnlinkBlock(int i) {415JitBlock &b = blocks_[i];416auto ppp = links_to_.equal_range(b.originalAddress);417if (ppp.first == ppp.second)418return;419for (auto iter = ppp.first; iter != ppp.second; ++iter) {420if ((size_t)iter->second >= num_blocks_) {421// Something probably went very wrong. Try to stumble along nevertheless.422ERROR_LOG(Log::JIT, "UnlinkBlock: Invalid block number %d", iter->second);423continue;424}425JitBlock &sourceBlock = blocks_[iter->second];426for (int e = 0; e < MAX_JIT_BLOCK_EXITS; e++) {427if (sourceBlock.exitAddress[e] == b.originalAddress)428sourceBlock.linkStatus[e] = false;429}430}431}432433std::vector<u32> JitBlockCache::SaveAndClearEmuHackOps() {434std::vector<u32> result;435result.resize(num_blocks_);436437for (int block_num = 0; block_num < num_blocks_; ++block_num) {438JitBlock &b = blocks_[block_num];439if (b.invalid)440continue;441442const u32 emuhack = GetEmuHackOpForBlock(block_num).encoding;443if (Memory::ReadUnchecked_U32(b.originalAddress) == emuhack)444{445result[block_num] = emuhack;446Memory::Write_Opcode_JIT(b.originalAddress, b.originalFirstOpcode);447}448else449result[block_num] = 0;450}451452return result;453}454455void JitBlockCache::RestoreSavedEmuHackOps(const std::vector<u32> &saved) {456if (num_blocks_ != (int)saved.size()) {457ERROR_LOG(Log::JIT, "RestoreSavedEmuHackOps: Wrong saved block size.");458return;459}460461for (int block_num = 0; block_num < num_blocks_; ++block_num) {462const JitBlock &b = blocks_[block_num];463if (b.invalid || saved[block_num] == 0)464continue;465466// Only if we restored it, write it back.467if (Memory::ReadUnchecked_U32(b.originalAddress) == b.originalFirstOpcode.encoding)468Memory::Write_Opcode_JIT(b.originalAddress, MIPSOpcode(saved[block_num]));469}470}471472void JitBlockCache::DestroyBlock(int block_num, DestroyType type) {473if (block_num < 0 || block_num >= num_blocks_) {474ERROR_LOG_REPORT(Log::JIT, "DestroyBlock: Invalid block number %d", block_num);475return;476}477JitBlock *b = &blocks_[block_num];478// No point it being in there anymore.479RemoveBlockMap(block_num);480481// Pure proxy blocks always point directly to a real block, there should be no chains of482// proxy-only blocks pointing to proxy-only blocks.483// Follow a block proxy chain.484// Destroy the block that transitively has this as a proxy. Likely the root block once inlined485// this block or its 'parent', so now that this block has changed, the root block must be destroyed.486if (b->proxyFor) {487for (size_t i = 0; i < b->proxyFor->size(); i++) {488int proxied_blocknum = GetBlockNumberFromStartAddress((*b->proxyFor)[i], false);489// If it was already cleared, we don't know which to destroy.490if (proxied_blocknum != -1) {491DestroyBlock(proxied_blocknum, type);492}493}494b->proxyFor->clear();495delete b->proxyFor;496b->proxyFor = 0;497}498auto range = proxyBlockMap_.equal_range(b->originalAddress);499for (auto it = range.first; it != range.second; ++it) {500if (it->second == block_num) {501// Found it. Delete and bail.502proxyBlockMap_.erase(it);503break;504}505}506507// TODO: Handle the case when there's a proxy block and a regular JIT block at the same location.508// In this case we probably "leak" the proxy block currently (no memory leak but it'll stay enabled).509510if (b->invalid) {511if (type == DestroyType::INVALIDATE)512ERROR_LOG(Log::JIT, "Invalidating invalid block %d", block_num);513return;514}515516b->invalid = true;517if (!b->IsPureProxy()) {518if (Memory::ReadUnchecked_U32(b->originalAddress) == GetEmuHackOpForBlock(block_num).encoding)519Memory::Write_Opcode_JIT(b->originalAddress, b->originalFirstOpcode);520}521522// It's not safe to set normalEntry to 0 here, since we use a binary search523// that looks at that later to find blocks. Marking it invalid is enough.524525UnlinkBlock(block_num);526527// Don't change the jit code when invalidating a pure proxy block.528if (b->IsPureProxy()) {529return;530}531532if (b->checkedEntry) {533// We can skip this if we're clearing anyway, which cuts down on protect back and forth on WX exclusive.534if (type != DestroyType::CLEAR) {535u8 *writableEntry = codeBlock_->GetWritablePtrFromCodePtr(b->checkedEntry);536MIPSComp::jit->UnlinkBlock(writableEntry, b->originalAddress);537}538} else {539ERROR_LOG(Log::JIT, "Unlinking block with no entry: %08x (%d)", b->originalAddress, block_num);540}541}542543void JitBlockCache::InvalidateICache(u32 address, const u32 length) {544// Convert the logical address to a physical address for the block map545const u32 pAddr = address & 0x1FFFFFFF;546const u32 pEnd = pAddr + length;547548if (pEnd < pAddr) {549ERROR_LOG(Log::JIT, "Bad InvalidateICache: %08x with len=%d", address, length);550return;551}552553if (pAddr == 0 && pEnd >= 0x1FFFFFFF) {554InvalidateChangedBlocks();555return;556}557558// Blocks may start and end in overlapping ways, and destroying one invalidates iterators.559// So after destroying one, we start over.560do {561restart:562auto next = block_map_.lower_bound(std::make_pair(pAddr, 0));563auto last = block_map_.upper_bound(std::make_pair(pEnd + MAX_BLOCK_INSTRUCTIONS, 0));564// Note that if next is end(), last will be end() too (equal.)565for (; next != last; ++next) {566const u32 blockStart = next->first.second;567const u32 blockEnd = next->first.first;568if (blockStart < pEnd && blockEnd > pAddr) {569DestroyBlock(next->second, DestroyType::INVALIDATE);570// Our iterator is now invalid. Break and search again.571// Most of the time there shouldn't be a bunch of matching blocks.572goto restart;573}574}575// We got here - it wasn't in the map at all (or anymore.)576} while (false);577}578579void JitBlockCache::InvalidateChangedBlocks() {580// The primary goal of this is to make sure block linking is cleared up.581for (int block_num = 0; block_num < num_blocks_; ++block_num) {582JitBlock &b = blocks_[block_num];583if (b.invalid || b.IsPureProxy())584continue;585586bool changed = false;587if (JIT_USE_COMPILEDHASH) {588changed = b.compiledHash != HashJitBlock(b);589} else {590const u32 emuhack = GetEmuHackOpForBlock(block_num).encoding;591changed = Memory::ReadUnchecked_U32(b.originalAddress) != emuhack;592}593594if (changed) {595DEBUG_LOG(Log::JIT, "Invalidating changed block at %08x", b.originalAddress);596DestroyBlock(block_num, DestroyType::INVALIDATE);597}598}599}600601int JitBlockCache::GetBlockExitSize() {602#if PPSSPP_ARCH(ARM)603// Will depend on the sequence found to encode the destination address.604return 0;605#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)606return 15;607#elif PPSSPP_ARCH(ARM64)608// Will depend on the sequence found to encode the destination address.609return 0;610#elif PPSSPP_ARCH(RISCV64)611// Will depend on the sequence found to encode the destination address.612return 0;613#elif PPSSPP_ARCH(LOONGARCH64)614// Will depend on the sequence found to encode the destination address.615return 0;616#else617#warning GetBlockExitSize unimplemented618return 0;619#endif620}621622void JitBlockCache::ComputeStats(BlockCacheStats &bcStats) const {623double totalBloat = 0.0;624double maxBloat = 0.0;625double minBloat = 1000000000.0;626for (int i = 0; i < num_blocks_; i++) {627const JitBlock *b = GetBlock(i);628double codeSize = (double)b->codeSize;629if (codeSize == 0)630continue;631double origSize = (double)(4 * b->originalSize);632double bloat = codeSize / origSize;633if (bloat < minBloat) {634minBloat = bloat;635bcStats.minBloatBlock = b->originalAddress;636}637if (bloat > maxBloat) {638maxBloat = bloat;639bcStats.maxBloatBlock = b->originalAddress;640}641totalBloat += bloat;642}643bcStats.numBlocks = num_blocks_;644bcStats.minBloat = (float)minBloat;645bcStats.maxBloat = (float)maxBloat;646bcStats.avgBloat = (float)(totalBloat / (double)num_blocks_);647}648649JitBlockDebugInfo JitBlockCache::GetBlockDebugInfo(int blockNum) const {650JitBlockDebugInfo debugInfo{};651const JitBlock *block = GetBlock(blockNum);652debugInfo.originalAddress = block->originalAddress;653debugInfo.origDisasm.reserve(((block->originalAddress + block->originalSize * 4) - block->originalAddress) / 4);654for (u32 addr = block->originalAddress; addr <= block->originalAddress + block->originalSize * 4; addr += 4) {655char temp[256];656MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, sizeof(temp), true);657std::string mipsDis = temp;658debugInfo.origDisasm.push_back(mipsDis);659}660661#if PPSSPP_ARCH(ARM)662debugInfo.targetDisasm = DisassembleArm2(block->normalEntry, block->codeSize);663#elif PPSSPP_ARCH(ARM64)664debugInfo.targetDisasm = DisassembleArm64(block->normalEntry, block->codeSize);665#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)666debugInfo.targetDisasm = DisassembleX86(block->normalEntry, block->codeSize);667#elif PPSSPP_ARCH(RISCV64)668debugInfo.targetDisasm = DisassembleRV64(block->normalEntry, block->codeSize);669#elif PPSSPP_ARCH(LOONGARCH64)670debugInfo.targetDisasm = DisassembleLA64(block->normalEntry, block->codeSize);671#endif672return debugInfo;673}674675676