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