CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/MIPS/RiscV/RiscVJit.cpp
Views: 1401
1
// Copyright (c) 2023- 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 <cstddef>
19
#include "Core/MemMap.h"
20
#include "Core/MIPS/MIPSTables.h"
21
#include "Core/MIPS/RiscV/RiscVJit.h"
22
#include "Core/MIPS/RiscV/RiscVRegCache.h"
23
24
#include <algorithm>
25
// for std::min
26
27
namespace MIPSComp {
28
29
using namespace RiscVGen;
30
using namespace RiscVJitConstants;
31
32
// Needs space for a LI and J which might both be 32-bit offsets.
33
static constexpr int MIN_BLOCK_NORMAL_LEN = 16;
34
static constexpr int MIN_BLOCK_EXIT_LEN = 8;
35
36
RiscVJitBackend::RiscVJitBackend(JitOptions &jitopt, IRBlockCache &blocks)
37
: IRNativeBackend(blocks), jo(jitopt), regs_(&jo) {
38
// Automatically disable incompatible options.
39
if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {
40
jo.enablePointerify = false;
41
}
42
jo.optimizeForInterpreter = false;
43
44
// Since we store the offset, this is as big as it can be.
45
// We could shift off one bit to double it, would need to change RiscVAsm.
46
AllocCodeSpace(1024 * 1024 * 16);
47
SetAutoCompress(true);
48
49
regs_.Init(this);
50
}
51
52
RiscVJitBackend::~RiscVJitBackend() {
53
}
54
55
static void NoBlockExits() {
56
_assert_msg_(false, "Never exited block, invalid IR?");
57
}
58
59
bool RiscVJitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num, bool preload) {
60
if (GetSpaceLeft() < 0x800)
61
return false;
62
63
IRBlock *block = irBlockCache->GetBlock(block_num);
64
BeginWrite(std::min(GetSpaceLeft(), (size_t)block->GetNumIRInstructions() * 32));
65
66
u32 startPC = block->GetOriginalStart();
67
bool wroteCheckedOffset = false;
68
if (jo.enableBlocklink && !jo.useBackJump) {
69
SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));
70
wroteCheckedOffset = true;
71
72
WriteDebugPC(startPC);
73
74
FixupBranch normalEntry = BGE(DOWNCOUNTREG, R_ZERO);
75
LI(SCRATCH1, startPC);
76
QuickJ(R_RA, outerLoopPCInSCRATCH1_);
77
SetJumpTarget(normalEntry);
78
}
79
80
// Don't worry, the codespace isn't large enough to overflow offsets.
81
const u8 *blockStart = GetCodePointer();
82
block->SetNativeOffset((int)GetOffset(blockStart));
83
compilingBlockNum_ = block_num;
84
85
regs_.Start(irBlockCache, block_num);
86
87
std::vector<const u8 *> addresses;
88
const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);
89
for (int i = 0; i < block->GetNumIRInstructions(); ++i) {
90
const IRInst &inst = instructions[i];
91
regs_.SetIRIndex(i);
92
addresses.push_back(GetCodePtr());
93
94
CompileIRInst(inst);
95
96
if (jo.Disabled(JitDisable::REGALLOC_GPR) || jo.Disabled(JitDisable::REGALLOC_FPR))
97
regs_.FlushAll(jo.Disabled(JitDisable::REGALLOC_GPR), jo.Disabled(JitDisable::REGALLOC_FPR));
98
99
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
100
if (GetSpaceLeft() < 0x800) {
101
compilingBlockNum_ = -1;
102
return false;
103
}
104
}
105
106
// We should've written an exit above. If we didn't, bad things will happen.
107
// Only check if debug stats are enabled - needlessly wastes jit space.
108
if (DebugStatsEnabled()) {
109
QuickCallFunction(&NoBlockExits, SCRATCH2);
110
QuickJ(R_RA, hooks_.crashHandler);
111
}
112
113
int len = (int)GetOffset(GetCodePointer()) - block->GetNativeOffset();
114
if (len < MIN_BLOCK_NORMAL_LEN) {
115
// We need at least 16 bytes to invalidate blocks with, but larger doesn't need to align.
116
ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - len);
117
}
118
119
if (!wroteCheckedOffset) {
120
// Always record this, even if block link disabled - it's used for size calc.
121
SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));
122
}
123
124
if (jo.enableBlocklink && jo.useBackJump) {
125
WriteDebugPC(startPC);
126
127
// Most blocks shouldn't be >= 4KB, so usually we can just BGE.
128
if (BInRange(blockStart)) {
129
BGE(DOWNCOUNTREG, R_ZERO, blockStart);
130
} else {
131
FixupBranch skip = BLT(DOWNCOUNTREG, R_ZERO);
132
J(blockStart);
133
SetJumpTarget(skip);
134
}
135
LI(SCRATCH1, startPC);
136
QuickJ(R_RA, outerLoopPCInSCRATCH1_);
137
}
138
139
if (logBlocks_ > 0) {
140
--logBlocks_;
141
142
std::map<const u8 *, int> addressesLookup;
143
for (int i = 0; i < (int)addresses.size(); ++i)
144
addressesLookup[addresses[i]] = i;
145
146
INFO_LOG(Log::JIT, "=============== RISCV (%08x, %d bytes) ===============", startPC, len);
147
const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);
148
for (const u8 *p = blockStart; p < GetCodePointer(); ) {
149
auto it = addressesLookup.find(p);
150
if (it != addressesLookup.end()) {
151
const IRInst &inst = instructions[it->second];
152
153
char temp[512];
154
DisassembleIR(temp, sizeof(temp), inst);
155
INFO_LOG(Log::JIT, "IR: #%d %s", it->second, temp);
156
}
157
158
auto next = std::next(it);
159
const u8 *nextp = next == addressesLookup.end() ? GetCodePointer() : next->first;
160
161
#if PPSSPP_ARCH(RISCV64) || (PPSSPP_PLATFORM(WINDOWS) && !defined(__LIBRETRO__))
162
auto lines = DisassembleRV64(p, (int)(nextp - p));
163
for (const auto &line : lines)
164
INFO_LOG(Log::JIT, "RV: %s", line.c_str());
165
#endif
166
p = nextp;
167
}
168
}
169
170
EndWrite();
171
FlushIcache();
172
compilingBlockNum_ = -1;
173
174
return true;
175
}
176
177
void RiscVJitBackend::WriteConstExit(uint32_t pc) {
178
int block_num = blocks_.GetBlockNumberFromStartAddress(pc);
179
const IRNativeBlock *nativeBlock = GetNativeBlock(block_num);
180
181
int exitStart = (int)GetOffset(GetCodePointer());
182
if (block_num >= 0 && jo.enableBlocklink && nativeBlock && nativeBlock->checkedOffset != 0) {
183
QuickJ(SCRATCH1, GetBasePtr() + nativeBlock->checkedOffset);
184
} else {
185
LI(SCRATCH1, pc);
186
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
187
}
188
189
if (jo.enableBlocklink) {
190
// In case of compression or early link, make sure it's large enough.
191
int len = (int)GetOffset(GetCodePointer()) - exitStart;
192
if (len < MIN_BLOCK_EXIT_LEN) {
193
ReserveCodeSpace(MIN_BLOCK_EXIT_LEN - len);
194
len = MIN_BLOCK_EXIT_LEN;
195
}
196
197
AddLinkableExit(compilingBlockNum_, pc, exitStart, len);
198
}
199
}
200
201
void RiscVJitBackend::OverwriteExit(int srcOffset, int len, int block_num) {
202
_dbg_assert_(len >= MIN_BLOCK_EXIT_LEN);
203
204
const IRNativeBlock *nativeBlock = GetNativeBlock(block_num);
205
if (nativeBlock) {
206
u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + srcOffset;
207
if (PlatformIsWXExclusive()) {
208
ProtectMemoryPages(writable, len, MEM_PROT_READ | MEM_PROT_WRITE);
209
}
210
211
RiscVEmitter emitter(GetBasePtr() + srcOffset, writable);
212
emitter.QuickJ(SCRATCH1, GetBasePtr() + nativeBlock->checkedOffset);
213
int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);
214
if (bytesWritten < len)
215
emitter.ReserveCodeSpace(len - bytesWritten);
216
emitter.FlushIcache();
217
218
if (PlatformIsWXExclusive()) {
219
ProtectMemoryPages(writable, 16, MEM_PROT_READ | MEM_PROT_EXEC);
220
}
221
}
222
}
223
224
void RiscVJitBackend::CompIR_Generic(IRInst inst) {
225
// If we got here, we're going the slow way.
226
uint64_t value;
227
memcpy(&value, &inst, sizeof(inst));
228
229
FlushAll();
230
LI(X10, value, SCRATCH2);
231
SaveStaticRegisters();
232
WriteDebugProfilerStatus(IRProfilerStatus::IR_INTERPRET);
233
QuickCallFunction(&DoIRInst, SCRATCH2);
234
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
235
LoadStaticRegisters();
236
237
// We only need to check the return value if it's a potential exit.
238
if ((GetIRMeta(inst.op)->flags & IRFLAG_EXIT) != 0) {
239
// Result in X10 aka SCRATCH1.
240
_assert_(X10 == SCRATCH1);
241
if (BInRange(dispatcherPCInSCRATCH1_)) {
242
BNE(X10, R_ZERO, dispatcherPCInSCRATCH1_);
243
} else {
244
FixupBranch skip = BEQ(X10, R_ZERO);
245
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
246
SetJumpTarget(skip);
247
}
248
}
249
}
250
251
void RiscVJitBackend::CompIR_Interpret(IRInst inst) {
252
MIPSOpcode op(inst.constant);
253
254
// IR protects us against this being a branching instruction (well, hopefully.)
255
FlushAll();
256
SaveStaticRegisters();
257
WriteDebugProfilerStatus(IRProfilerStatus::INTERPRET);
258
if (DebugStatsEnabled()) {
259
LI(X10, MIPSGetName(op));
260
QuickCallFunction(&NotifyMIPSInterpret, SCRATCH2);
261
}
262
LI(X10, (int32_t)inst.constant);
263
QuickCallFunction((const u8 *)MIPSGetInterpretFunc(op), SCRATCH2);
264
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
265
LoadStaticRegisters();
266
}
267
268
void RiscVJitBackend::FlushAll() {
269
regs_.FlushAll();
270
}
271
272
bool RiscVJitBackend::DescribeCodePtr(const u8 *ptr, std::string &name) const {
273
// Used in disassembly viewer.
274
// Don't use spaces; profilers get confused or truncate them.
275
if (ptr == dispatcherPCInSCRATCH1_) {
276
name = "dispatcherPCInSCRATCH1";
277
} else if (ptr == outerLoopPCInSCRATCH1_) {
278
name = "outerLoopPCInSCRATCH1";
279
} else if (ptr == dispatcherNoCheck_) {
280
name = "dispatcherNoCheck";
281
} else if (ptr == saveStaticRegisters_) {
282
name = "saveStaticRegisters";
283
} else if (ptr == loadStaticRegisters_) {
284
name = "loadStaticRegisters";
285
} else if (ptr == applyRoundingMode_) {
286
name = "applyRoundingMode";
287
} else if (ptr >= GetBasePtr() && ptr < GetBasePtr() + jitStartOffset_) {
288
name = "fixedCode";
289
} else {
290
return IRNativeBackend::DescribeCodePtr(ptr, name);
291
}
292
return true;
293
}
294
295
void RiscVJitBackend::ClearAllBlocks() {
296
ClearCodeSpace(jitStartOffset_);
297
FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_);
298
EraseAllLinks(-1);
299
}
300
301
void RiscVJitBackend::InvalidateBlock(IRBlockCache *irBlockCache, int block_num) {
302
IRBlock *block = irBlockCache->GetBlock(block_num);
303
int offset = block->GetNativeOffset();
304
u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + offset;
305
306
// Overwrite the block with a jump to compile it again.
307
u32 pc = block->GetOriginalStart();
308
if (pc != 0) {
309
// Hopefully we always have at least 16 bytes, which should be all we need.
310
if (PlatformIsWXExclusive()) {
311
ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_WRITE);
312
}
313
314
RiscVEmitter emitter(GetBasePtr() + offset, writable);
315
// We sign extend to ensure it will fit in 32-bit and 8 bytes LI.
316
// TODO: May need to change if dispatcher doesn't reload PC.
317
emitter.LI(SCRATCH1, (int32_t)pc);
318
emitter.QuickJ(R_RA, dispatcherPCInSCRATCH1_);
319
int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);
320
if (bytesWritten < MIN_BLOCK_NORMAL_LEN)
321
emitter.ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - bytesWritten);
322
emitter.FlushIcache();
323
324
if (PlatformIsWXExclusive()) {
325
ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_EXEC);
326
}
327
}
328
329
EraseAllLinks(block_num);
330
}
331
332
void RiscVJitBackend::RestoreRoundingMode(bool force) {
333
FSRMI(Round::NEAREST_EVEN);
334
}
335
336
void RiscVJitBackend::ApplyRoundingMode(bool force) {
337
QuickCallFunction(applyRoundingMode_);
338
}
339
340
void RiscVJitBackend::MovFromPC(RiscVReg r) {
341
LWU(r, CTXREG, offsetof(MIPSState, pc));
342
}
343
344
void RiscVJitBackend::MovToPC(RiscVReg r) {
345
SW(r, CTXREG, offsetof(MIPSState, pc));
346
}
347
348
void RiscVJitBackend::WriteDebugPC(uint32_t pc) {
349
if (hooks_.profilerPC) {
350
int offset = (const u8 *)hooks_.profilerPC - GetBasePtr();
351
LI(SCRATCH2, hooks_.profilerPC);
352
LI(R_RA, (int32_t)pc);
353
SW(R_RA, SCRATCH2, 0);
354
}
355
}
356
357
void RiscVJitBackend::WriteDebugPC(RiscVReg r) {
358
if (hooks_.profilerPC) {
359
int offset = (const u8 *)hooks_.profilerPC - GetBasePtr();
360
LI(SCRATCH2, hooks_.profilerPC);
361
SW(r, SCRATCH2, 0);
362
}
363
}
364
365
void RiscVJitBackend::WriteDebugProfilerStatus(IRProfilerStatus status) {
366
if (hooks_.profilerPC) {
367
int offset = (const u8 *)hooks_.profilerStatus - GetBasePtr();
368
LI(SCRATCH2, hooks_.profilerStatus);
369
LI(R_RA, (int)status);
370
SW(R_RA, SCRATCH2, 0);
371
}
372
}
373
374
void RiscVJitBackend::SaveStaticRegisters() {
375
if (jo.useStaticAlloc) {
376
QuickCallFunction(saveStaticRegisters_);
377
} else {
378
// Inline the single operation
379
SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
380
}
381
}
382
383
void RiscVJitBackend::LoadStaticRegisters() {
384
if (jo.useStaticAlloc) {
385
QuickCallFunction(loadStaticRegisters_);
386
} else {
387
LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
388
}
389
}
390
391
void RiscVJitBackend::NormalizeSrc1(IRInst inst, RiscVReg *reg, RiscVReg tempReg, bool allowOverlap) {
392
*reg = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, tempReg);
393
}
394
395
void RiscVJitBackend::NormalizeSrc12(IRInst inst, RiscVReg *lhs, RiscVReg *rhs, RiscVReg lhsTempReg, RiscVReg rhsTempReg, bool allowOverlap) {
396
*lhs = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, lhsTempReg);
397
*rhs = NormalizeR(inst.src2, allowOverlap ? 0 : inst.dest, rhsTempReg);
398
}
399
400
RiscVReg RiscVJitBackend::NormalizeR(IRReg rs, IRReg rd, RiscVReg tempReg) {
401
// For proper compare, we must sign extend so they both match or don't match.
402
// But don't change pointers, in case one is SP (happens in LittleBigPlanet.)
403
if (regs_.IsGPRImm(rs) && regs_.GetGPRImm(rs) == 0) {
404
return R_ZERO;
405
} else if (regs_.IsGPRMappedAsPointer(rs) || rs == rd) {
406
return regs_.Normalize32(rs, tempReg);
407
} else {
408
return regs_.Normalize32(rs);
409
}
410
}
411
412
} // namespace MIPSComp
413
414