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