1 // Copyright (c) 2012- PPSSPP Project.
2
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
11
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18 #include <set>
19
20 #include "ext/xxhash.h"
21 #include "Common/Profiler/Profiler.h"
22
23 #include "Common/Log.h"
24 #include "Common/Serialize/Serializer.h"
25 #include "Common/StringUtils.h"
26
27 #include "Core/Core.h"
28 #include "Core/CoreTiming.h"
29 #include "Core/HLE/sceKernelMemory.h"
30 #include "Core/MemMap.h"
31 #include "Core/MIPS/MIPS.h"
32 #include "Core/MIPS/MIPSCodeUtils.h"
33 #include "Core/MIPS/MIPSInt.h"
34 #include "Core/MIPS/MIPSTables.h"
35 #include "Core/MIPS/IR/IRRegCache.h"
36 #include "Core/MIPS/IR/IRJit.h"
37 #include "Core/MIPS/IR/IRPassSimplify.h"
38 #include "Core/MIPS/IR/IRInterpreter.h"
39 #include "Core/MIPS/JitCommon/JitCommon.h"
40 #include "Core/Reporting.h"
41
42 namespace MIPSComp {
43
IRJit(MIPSState * mipsState)44 IRJit::IRJit(MIPSState *mipsState) : frontend_(mipsState->HasDefaultPrefix()), mips_(mipsState) {
45 // u32 size = 128 * 1024;
46 // blTrampolines_ = kernelMemory.Alloc(size, true, "trampoline");
47 InitIR();
48
49 IROptions opts{};
50 opts.disableFlags = g_Config.uJitDisableFlags;
51 opts.unalignedLoadStore = opts.disableFlags & (uint32_t)JitDisable::LSU_UNALIGNED;
52 frontend_.SetOptions(opts);
53 }
54
~IRJit()55 IRJit::~IRJit() {
56 }
57
DoState(PointerWrap & p)58 void IRJit::DoState(PointerWrap &p) {
59 frontend_.DoState(p);
60 }
61
UpdateFCR31()62 void IRJit::UpdateFCR31() {
63 }
64
ClearCache()65 void IRJit::ClearCache() {
66 INFO_LOG(JIT, "IRJit: Clearing the cache!");
67 blocks_.Clear();
68 }
69
InvalidateCacheAt(u32 em_address,int length)70 void IRJit::InvalidateCacheAt(u32 em_address, int length) {
71 blocks_.InvalidateICache(em_address, length);
72 }
73
Compile(u32 em_address)74 void IRJit::Compile(u32 em_address) {
75 PROFILE_THIS_SCOPE("jitc");
76
77 if (g_Config.bPreloadFunctions) {
78 // Look to see if we've preloaded this block.
79 int block_num = blocks_.FindPreloadBlock(em_address);
80 if (block_num != -1) {
81 IRBlock *b = blocks_.GetBlock(block_num);
82 // Okay, let's link and finalize the block now.
83 b->Finalize(block_num);
84 if (b->IsValid()) {
85 // Success, we're done.
86 return;
87 }
88 }
89 }
90
91 std::vector<IRInst> instructions;
92 u32 mipsBytes;
93 if (!CompileBlock(em_address, instructions, mipsBytes, false)) {
94 // Ran out of block numbers - need to reset.
95 ERROR_LOG(JIT, "Ran out of block numbers, clearing cache");
96 ClearCache();
97 CompileBlock(em_address, instructions, mipsBytes, false);
98 }
99
100 if (frontend_.CheckRounding(em_address)) {
101 // Our assumptions are all wrong so it's clean-slate time.
102 ClearCache();
103 CompileBlock(em_address, instructions, mipsBytes, false);
104 }
105 }
106
CompileBlock(u32 em_address,std::vector<IRInst> & instructions,u32 & mipsBytes,bool preload)107 bool IRJit::CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload) {
108 frontend_.DoJit(em_address, instructions, mipsBytes, preload);
109 if (instructions.empty()) {
110 _dbg_assert_(preload);
111 // We return true when preloading so it doesn't abort.
112 return preload;
113 }
114
115 int block_num = blocks_.AllocateBlock(em_address);
116 if ((block_num & ~MIPS_EMUHACK_VALUE_MASK) != 0) {
117 // Out of block numbers. Caller will handle.
118 return false;
119 }
120
121 IRBlock *b = blocks_.GetBlock(block_num);
122 b->SetInstructions(instructions);
123 b->SetOriginalSize(mipsBytes);
124 if (preload) {
125 // Hash, then only update page stats, don't link yet.
126 b->UpdateHash();
127 blocks_.FinalizeBlock(block_num, true);
128 } else {
129 // Overwrites the first instruction, and also updates stats.
130 // TODO: Should we always hash? Then we can reuse blocks.
131 blocks_.FinalizeBlock(block_num);
132 }
133
134 return true;
135 }
136
CompileFunction(u32 start_address,u32 length)137 void IRJit::CompileFunction(u32 start_address, u32 length) {
138 PROFILE_THIS_SCOPE("jitc");
139
140 // Note: we don't actually write emuhacks yet, so we can validate hashes.
141 // This way, if the game changes the code afterward, we'll catch even without icache invalidation.
142
143 // We may go up and down from branches, so track all block starts done here.
144 std::set<u32> doneAddresses;
145 std::vector<u32> pendingAddresses;
146 pendingAddresses.push_back(start_address);
147 while (!pendingAddresses.empty()) {
148 u32 em_address = pendingAddresses.back();
149 pendingAddresses.pop_back();
150
151 // To be safe, also check if a real block is there. This can be a runtime module load.
152 u32 inst = Memory::ReadUnchecked_U32(em_address);
153 if (MIPS_IS_RUNBLOCK(inst) || doneAddresses.find(em_address) != doneAddresses.end()) {
154 // Already compiled this address.
155 continue;
156 }
157
158 std::vector<IRInst> instructions;
159 u32 mipsBytes;
160 if (!CompileBlock(em_address, instructions, mipsBytes, true)) {
161 // Ran out of block numbers - let's hope there's no more code it needs to run.
162 // Will flush when actually compiling.
163 ERROR_LOG(JIT, "Ran out of block numbers while compiling function");
164 return;
165 }
166
167 doneAddresses.insert(em_address);
168
169 for (const IRInst &inst : instructions) {
170 u32 exit = 0;
171
172 switch (inst.op) {
173 case IROp::ExitToConst:
174 case IROp::ExitToConstIfEq:
175 case IROp::ExitToConstIfNeq:
176 case IROp::ExitToConstIfGtZ:
177 case IROp::ExitToConstIfGeZ:
178 case IROp::ExitToConstIfLtZ:
179 case IROp::ExitToConstIfLeZ:
180 case IROp::ExitToConstIfFpTrue:
181 case IROp::ExitToConstIfFpFalse:
182 exit = inst.constant;
183 break;
184
185 case IROp::ExitToPC:
186 case IROp::Break:
187 // Don't add any, we'll do block end anyway (for jal, etc.)
188 exit = 0;
189 break;
190
191 default:
192 exit = 0;
193 break;
194 }
195
196 // Only follow jumps internal to the function.
197 if (exit != 0 && exit >= start_address && exit < start_address + length) {
198 // Even if it's a duplicate, we check at loop start.
199 pendingAddresses.push_back(exit);
200 }
201 }
202
203 // Also include after the block for jal returns.
204 if (em_address + mipsBytes < start_address + length) {
205 pendingAddresses.push_back(em_address + mipsBytes);
206 }
207 }
208 }
209
RunLoopUntil(u64 globalticks)210 void IRJit::RunLoopUntil(u64 globalticks) {
211 PROFILE_THIS_SCOPE("jit");
212
213 // ApplyRoundingMode(true);
214 // IR Dispatcher
215
216 while (true) {
217 // RestoreRoundingMode(true);
218 CoreTiming::Advance();
219 // ApplyRoundingMode(true);
220 if (coreState != 0) {
221 break;
222 }
223 while (mips_->downcount >= 0) {
224 u32 inst = Memory::ReadUnchecked_U32(mips_->pc);
225 u32 opcode = inst & 0xFF000000;
226 if (opcode == MIPS_EMUHACK_OPCODE) {
227 u32 data = inst & 0xFFFFFF;
228 IRBlock *block = blocks_.GetBlock(data);
229 mips_->pc = IRInterpret(mips_, block->GetInstructions(), block->GetNumInstructions());
230 if (!Memory::IsValidAddress(mips_->pc)) {
231 Core_ExecException(mips_->pc, mips_->pc, ExecExceptionType::JUMP);
232 break;
233 }
234 } else {
235 // RestoreRoundingMode(true);
236 Compile(mips_->pc);
237 // ApplyRoundingMode(true);
238 }
239 }
240 }
241
242 // RestoreRoundingMode(true);
243 }
244
DescribeCodePtr(const u8 * ptr,std::string & name)245 bool IRJit::DescribeCodePtr(const u8 *ptr, std::string &name) {
246 // Used in target disassembly viewer.
247 return false;
248 }
249
LinkBlock(u8 * exitPoint,const u8 * checkedEntry)250 void IRJit::LinkBlock(u8 *exitPoint, const u8 *checkedEntry) {
251 Crash();
252 }
253
UnlinkBlock(u8 * checkedEntry,u32 originalAddress)254 void IRJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
255 Crash();
256 }
257
ReplaceJalTo(u32 dest)258 bool IRJit::ReplaceJalTo(u32 dest) {
259 Crash();
260 return false;
261 }
262
Clear()263 void IRBlockCache::Clear() {
264 for (int i = 0; i < (int)blocks_.size(); ++i) {
265 blocks_[i].Destroy(i);
266 }
267 blocks_.clear();
268 byPage_.clear();
269 }
270
InvalidateICache(u32 address,u32 length)271 void IRBlockCache::InvalidateICache(u32 address, u32 length) {
272 u32 startPage = AddressToPage(address);
273 u32 endPage = AddressToPage(address + length);
274
275 for (u32 page = startPage; page <= endPage; ++page) {
276 const auto iter = byPage_.find(page);
277 if (iter == byPage_.end())
278 continue;
279
280 const std::vector<int> &blocksInPage = iter->second;
281 for (int i : blocksInPage) {
282 if (blocks_[i].OverlapsRange(address, length)) {
283 // Not removing from the page, hopefully doesn't build up with small recompiles.
284 blocks_[i].Destroy(i);
285 }
286 }
287 }
288 }
289
FinalizeBlock(int i,bool preload)290 void IRBlockCache::FinalizeBlock(int i, bool preload) {
291 if (!preload) {
292 blocks_[i].Finalize(i);
293 }
294
295 u32 startAddr, size;
296 blocks_[i].GetRange(startAddr, size);
297
298 u32 startPage = AddressToPage(startAddr);
299 u32 endPage = AddressToPage(startAddr + size);
300
301 for (u32 page = startPage; page <= endPage; ++page) {
302 byPage_[page].push_back(i);
303 }
304 }
305
AddressToPage(u32 addr) const306 u32 IRBlockCache::AddressToPage(u32 addr) const {
307 // Use relatively small pages since basic blocks are typically small.
308 return (addr & 0x3FFFFFFF) >> 10;
309 }
310
FindPreloadBlock(u32 em_address)311 int IRBlockCache::FindPreloadBlock(u32 em_address) {
312 u32 page = AddressToPage(em_address);
313 auto iter = byPage_.find(page);
314 if (iter == byPage_.end())
315 return -1;
316
317 const std::vector<int> &blocksInPage = iter->second;
318 for (int i : blocksInPage) {
319 u32 start, mipsBytes;
320 blocks_[i].GetRange(start, mipsBytes);
321
322 if (start == em_address) {
323 if (blocks_[i].HashMatches()) {
324 return i;
325 }
326 }
327 }
328
329 return -1;
330 }
331
SaveAndClearEmuHackOps()332 std::vector<u32> IRBlockCache::SaveAndClearEmuHackOps() {
333 std::vector<u32> result;
334 result.resize(blocks_.size());
335
336 for (int number = 0; number < (int)blocks_.size(); ++number) {
337 IRBlock &b = blocks_[number];
338 if (b.IsValid() && b.RestoreOriginalFirstOp(number)) {
339 result[number] = number;
340 } else {
341 result[number] = 0;
342 }
343 }
344
345 return result;
346 }
347
RestoreSavedEmuHackOps(std::vector<u32> saved)348 void IRBlockCache::RestoreSavedEmuHackOps(std::vector<u32> saved) {
349 if ((int)blocks_.size() != (int)saved.size()) {
350 ERROR_LOG(JIT, "RestoreSavedEmuHackOps: Wrong saved block size.");
351 return;
352 }
353
354 for (int number = 0; number < (int)blocks_.size(); ++number) {
355 IRBlock &b = blocks_[number];
356 // Only if we restored it, write it back.
357 if (b.IsValid() && saved[number] != 0 && b.HasOriginalFirstOp()) {
358 b.Finalize(number);
359 }
360 }
361 }
362
GetBlockDebugInfo(int blockNum) const363 JitBlockDebugInfo IRBlockCache::GetBlockDebugInfo(int blockNum) const {
364 const IRBlock &ir = blocks_[blockNum];
365 JitBlockDebugInfo debugInfo{};
366 uint32_t start, size;
367 ir.GetRange(start, size);
368 debugInfo.originalAddress = start; // TODO
369
370 for (u32 addr = start; addr < start + size; addr += 4) {
371 char temp[256];
372 MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, true);
373 std::string mipsDis = temp;
374 debugInfo.origDisasm.push_back(mipsDis);
375 }
376
377 for (int i = 0; i < ir.GetNumInstructions(); i++) {
378 IRInst inst = ir.GetInstructions()[i];
379 char buffer[256];
380 DisassembleIR(buffer, sizeof(buffer), inst);
381 debugInfo.irDisasm.push_back(buffer);
382 }
383 return debugInfo;
384 }
385
ComputeStats(BlockCacheStats & bcStats) const386 void IRBlockCache::ComputeStats(BlockCacheStats &bcStats) const {
387 double totalBloat = 0.0;
388 double maxBloat = 0.0;
389 double minBloat = 1000000000.0;
390 for (const auto &b : blocks_) {
391 double codeSize = (double)b.GetNumInstructions() * sizeof(IRInst);
392 if (codeSize == 0)
393 continue;
394
395 u32 origAddr, mipsBytes;
396 b.GetRange(origAddr, mipsBytes);
397 double origSize = (double)mipsBytes;
398 double bloat = codeSize / origSize;
399 if (bloat < minBloat) {
400 minBloat = bloat;
401 bcStats.minBloatBlock = origAddr;
402 }
403 if (bloat > maxBloat) {
404 maxBloat = bloat;
405 bcStats.maxBloatBlock = origAddr;
406 }
407 totalBloat += bloat;
408 bcStats.bloatMap[bloat] = origAddr;
409 }
410 bcStats.numBlocks = (int)blocks_.size();
411 bcStats.minBloat = minBloat;
412 bcStats.maxBloat = maxBloat;
413 bcStats.avgBloat = totalBloat / (double)blocks_.size();
414 }
415
GetBlockNumberFromStartAddress(u32 em_address,bool realBlocksOnly) const416 int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly) const {
417 u32 page = AddressToPage(em_address);
418
419 const auto iter = byPage_.find(page);
420 if (iter == byPage_.end())
421 return -1;
422
423 const std::vector<int> &blocksInPage = iter->second;
424 int best = -1;
425 for (int i : blocksInPage) {
426 uint32_t start, size;
427 blocks_[i].GetRange(start, size);
428 if (start == em_address) {
429 best = i;
430 if (blocks_[i].IsValid()) {
431 return i;
432 }
433 }
434 }
435 return best;
436 }
437
HasOriginalFirstOp() const438 bool IRBlock::HasOriginalFirstOp() const {
439 return Memory::ReadUnchecked_U32(origAddr_) == origFirstOpcode_.encoding;
440 }
441
RestoreOriginalFirstOp(int number)442 bool IRBlock::RestoreOriginalFirstOp(int number) {
443 const u32 emuhack = MIPS_EMUHACK_OPCODE | number;
444 if (Memory::ReadUnchecked_U32(origAddr_) == emuhack) {
445 Memory::Write_Opcode_JIT(origAddr_, origFirstOpcode_);
446 return true;
447 }
448 return false;
449 }
450
Finalize(int number)451 void IRBlock::Finalize(int number) {
452 // Check it wasn't invalidated, in case this is after preload.
453 // TODO: Allow reusing blocks when the code matches hash_ again, instead.
454 if (origAddr_) {
455 origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_);
456 MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number);
457 Memory::Write_Opcode_JIT(origAddr_, opcode);
458 }
459 }
460
Destroy(int number)461 void IRBlock::Destroy(int number) {
462 if (origAddr_) {
463 MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number);
464 if (Memory::ReadUnchecked_U32(origAddr_) == opcode.encoding)
465 Memory::Write_Opcode_JIT(origAddr_, origFirstOpcode_);
466
467 // Let's mark this invalid so we don't try to clear it again.
468 origAddr_ = 0;
469 }
470 }
471
CalculateHash() const472 u64 IRBlock::CalculateHash() const {
473 if (origAddr_) {
474 // This is unfortunate. In case of emuhacks, we have to make a copy.
475 std::vector<u32> buffer;
476 buffer.resize(origSize_ / 4);
477 size_t pos = 0;
478 for (u32 off = 0; off < origSize_; off += 4) {
479 // Let's actually hash the replacement, if any.
480 MIPSOpcode instr = Memory::ReadUnchecked_Instruction(origAddr_ + off, false);
481 buffer[pos++] = instr.encoding;
482 }
483
484 return XXH3_64bits(&buffer[0], origSize_);
485 }
486
487 return 0;
488 }
489
OverlapsRange(u32 addr,u32 size) const490 bool IRBlock::OverlapsRange(u32 addr, u32 size) const {
491 addr &= 0x3FFFFFFF;
492 u32 origAddr = origAddr_ & 0x3FFFFFFF;
493 return addr + size > origAddr && addr < origAddr + origSize_;
494 }
495
GetOriginalOp(MIPSOpcode op)496 MIPSOpcode IRJit::GetOriginalOp(MIPSOpcode op) {
497 IRBlock *b = blocks_.GetBlock(op.encoding & 0xFFFFFF);
498 if (b) {
499 return b->GetOriginalFirstOp();
500 }
501 return op;
502 }
503
504 } // namespace MIPSComp
505