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/x86/X64IRJit.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
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
20
21
#include <cstddef>
22
#include "Common/StringUtils.h"
23
#include "Core/MemMap.h"
24
#include "Core/MIPS/MIPSTables.h"
25
#include "Core/MIPS/x86/X64IRJit.h"
26
#include "Core/MIPS/x86/X64IRRegCache.h"
27
28
namespace MIPSComp {
29
30
using namespace Gen;
31
using namespace X64IRJitConstants;
32
33
// Invalidations just need a MOV and JMP.
34
static constexpr int MIN_BLOCK_NORMAL_LEN = 10;
35
// As long as we can fit a JMP, we should be fine.
36
static constexpr int MIN_BLOCK_EXIT_LEN = 5;
37
38
X64JitBackend::X64JitBackend(JitOptions &jitopt, IRBlockCache &blocks)
39
: IRNativeBackend(blocks), jo(jitopt), regs_(&jo) {
40
// Automatically disable incompatible options.
41
if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {
42
jo.enablePointerify = false;
43
}
44
jo.optimizeForInterpreter = false;
45
46
// Since we store the offset, this is as big as it can be.
47
AllocCodeSpace(1024 * 1024 * 16);
48
49
regs_.Init(this);
50
}
51
52
X64JitBackend::~X64JitBackend() {}
53
54
static void NoBlockExits() {
55
_assert_msg_(false, "Never exited block, invalid IR?");
56
}
57
58
bool X64JitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num, bool preload) {
59
if (GetSpaceLeft() < 0x800)
60
return false;
61
62
IRBlock *block = irBlockCache->GetBlock(block_num);
63
u32 startPC = block->GetOriginalStart();
64
bool wroteCheckedOffset = false;
65
if (jo.enableBlocklink && !jo.useBackJump) {
66
SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));
67
wroteCheckedOffset = true;
68
69
WriteDebugPC(startPC);
70
71
// TODO: See if we can get flags to always have the downcount compare.
72
if (jo.downcountInRegister) {
73
TEST(32, R(DOWNCOUNTREG), R(DOWNCOUNTREG));
74
} else {
75
CMP(32, MDisp(CTXREG, downcountOffset), Imm32(0));
76
}
77
FixupBranch normalEntry = J_CC(CC_NS);
78
MOV(32, R(SCRATCH1), Imm32(startPC));
79
JMP(outerLoopPCInSCRATCH1_, true);
80
SetJumpTarget(normalEntry);
81
}
82
83
// Don't worry, the codespace isn't large enough to overflow offsets.
84
const u8 *blockStart = GetCodePointer();
85
block->SetNativeOffset((int)GetOffset(blockStart));
86
compilingBlockNum_ = block_num;
87
lastConstPC_ = 0;
88
89
regs_.Start(irBlockCache, block_num);
90
91
std::vector<const u8 *> addresses;
92
addresses.reserve(block->GetNumIRInstructions());
93
const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);
94
for (int i = 0; i < block->GetNumIRInstructions(); ++i) {
95
const IRInst &inst = instructions[i];
96
regs_.SetIRIndex(i);
97
addresses.push_back(GetCodePtr());
98
99
CompileIRInst(inst);
100
101
if (jo.Disabled(JitDisable::REGALLOC_GPR) || jo.Disabled(JitDisable::REGALLOC_FPR))
102
regs_.FlushAll(jo.Disabled(JitDisable::REGALLOC_GPR), jo.Disabled(JitDisable::REGALLOC_FPR));
103
104
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
105
if (GetSpaceLeft() < 0x800) {
106
compilingBlockNum_ = -1;
107
return false;
108
}
109
}
110
111
// We should've written an exit above. If we didn't, bad things will happen.
112
// Only check if debug stats are enabled - needlessly wastes jit space.
113
if (DebugStatsEnabled()) {
114
ABI_CallFunction((const void *)&NoBlockExits);
115
JMP(hooks_.crashHandler, true);
116
}
117
118
int len = (int)GetOffset(GetCodePointer()) - block->GetNativeOffset();
119
if (len < MIN_BLOCK_NORMAL_LEN) {
120
// We need at least 10 bytes to invalidate blocks with.
121
ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - len);
122
}
123
124
if (!wroteCheckedOffset) {
125
// Always record this, even if block link disabled - it's used for size calc.
126
SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));
127
}
128
129
if (jo.enableBlocklink && jo.useBackJump) {
130
WriteDebugPC(startPC);
131
132
if (jo.downcountInRegister) {
133
TEST(32, R(DOWNCOUNTREG), R(DOWNCOUNTREG));
134
} else {
135
CMP(32, MDisp(CTXREG, downcountOffset), Imm32(0));
136
}
137
J_CC(CC_NS, blockStart, true);
138
139
MOV(32, R(SCRATCH1), Imm32(startPC));
140
JMP(outerLoopPCInSCRATCH1_, true);
141
}
142
143
if (logBlocks_ > 0) {
144
--logBlocks_;
145
146
std::map<const u8 *, int> addressesLookup;
147
for (int i = 0; i < (int)addresses.size(); ++i)
148
addressesLookup[addresses[i]] = i;
149
150
INFO_LOG(Log::JIT, "=============== x86 (%08x, %d bytes) ===============", startPC, len);
151
const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);
152
for (const u8 *p = blockStart; p < GetCodePointer(); ) {
153
auto it = addressesLookup.find(p);
154
if (it != addressesLookup.end()) {
155
const IRInst &inst = instructions[it->second];
156
157
char temp[512];
158
DisassembleIR(temp, sizeof(temp), inst);
159
INFO_LOG(Log::JIT, "IR: #%d %s", it->second, temp);
160
}
161
162
auto next = std::next(it);
163
const u8 *nextp = next == addressesLookup.end() ? GetCodePointer() : next->first;
164
165
auto lines = DisassembleX86(p, (int)(nextp - p));
166
for (const auto &line : lines)
167
INFO_LOG(Log::JIT, " X: %s", line.c_str());
168
p = nextp;
169
}
170
}
171
172
compilingBlockNum_ = -1;
173
174
return true;
175
}
176
177
void X64JitBackend::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
JMP(GetBasePtr() + nativeBlock->checkedOffset, true);
184
} else {
185
MOV(32, R(SCRATCH1), Imm32(pc));
186
JMP(dispatcherPCInSCRATCH1_, true);
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 X64JitBackend::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
XEmitter emitter(writable);
212
emitter.JMP(GetBasePtr() + nativeBlock->checkedOffset, true);
213
int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);
214
_dbg_assert_(bytesWritten <= MIN_BLOCK_EXIT_LEN);
215
if (bytesWritten < len)
216
emitter.ReserveCodeSpace(len - bytesWritten);
217
218
if (PlatformIsWXExclusive()) {
219
ProtectMemoryPages(writable, 16, MEM_PROT_READ | MEM_PROT_EXEC);
220
}
221
}
222
}
223
224
void X64JitBackend::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
SaveStaticRegisters();
231
WriteDebugProfilerStatus(IRProfilerStatus::IR_INTERPRET);
232
#if PPSSPP_ARCH(AMD64)
233
ABI_CallFunctionP((const void *)&DoIRInst, (void *)value);
234
#else
235
ABI_CallFunctionCC((const void *)&DoIRInst, (u32)(value & 0xFFFFFFFF), (u32)(value >> 32));
236
#endif
237
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
238
LoadStaticRegisters();
239
240
// We only need to check the return value if it's a potential exit.
241
if ((GetIRMeta(inst.op)->flags & IRFLAG_EXIT) != 0) {
242
// Result in RAX aka SCRATCH1.
243
_assert_(RAX == SCRATCH1);
244
CMP(32, R(SCRATCH1), Imm32(0));
245
J_CC(CC_NE, dispatcherPCInSCRATCH1_);
246
}
247
}
248
249
void X64JitBackend::CompIR_Interpret(IRInst inst) {
250
MIPSOpcode op(inst.constant);
251
252
// IR protects us against this being a branching instruction (well, hopefully.)
253
FlushAll();
254
SaveStaticRegisters();
255
WriteDebugProfilerStatus(IRProfilerStatus::INTERPRET);
256
if (DebugStatsEnabled()) {
257
ABI_CallFunctionP((const void *)&NotifyMIPSInterpret, (void *)MIPSGetName(op));
258
}
259
ABI_CallFunctionC((const void *)MIPSGetInterpretFunc(op), inst.constant);
260
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
261
LoadStaticRegisters();
262
}
263
264
void X64JitBackend::FlushAll() {
265
regs_.FlushAll();
266
}
267
268
bool X64JitBackend::DescribeCodePtr(const u8 *ptr, std::string &name) const {
269
// Used in disassembly viewer and profiling tools.
270
// Don't use spaces; profilers get confused or truncate them.
271
if (ptr == dispatcherPCInSCRATCH1_) {
272
name = "dispatcherPCInSCRATCH1";
273
} else if (ptr == outerLoopPCInSCRATCH1_) {
274
name = "outerLoopPCInSCRATCH1";
275
} else if (ptr == dispatcherNoCheck_) {
276
name = "dispatcherNoCheck";
277
} else if (ptr == saveStaticRegisters_) {
278
name = "saveStaticRegisters";
279
} else if (ptr == loadStaticRegisters_) {
280
name = "loadStaticRegisters";
281
} else if (ptr == restoreRoundingMode_) {
282
name = "restoreRoundingMode";
283
} else if (ptr == applyRoundingMode_) {
284
name = "applyRoundingMode";
285
} else if (ptr >= GetBasePtr() && ptr < GetBasePtr() + jitStartOffset_) {
286
if (ptr == constants.noSignMask) {
287
name = "constants.noSignMask";
288
} else if (ptr == constants.signBitAll) {
289
name = "constants.signBitAll";
290
} else if (ptr == constants.positiveZeroes) {
291
name = "constants.positiveZeroes";
292
} else if (ptr == constants.positiveInfinity) {
293
name = "constants.positiveInfinity";
294
} else if (ptr == constants.positiveOnes) {
295
name = "constants.positiveOnes";
296
} else if (ptr == constants.negativeOnes) {
297
name = "constants.negativeOnes";
298
} else if (ptr == constants.qNAN) {
299
name = "constants.qNAN";
300
} else if (ptr == constants.maxIntBelowAsFloat) {
301
name = "constants.maxIntBelowAsFloat";
302
} else if ((const float *)ptr >= constants.mulTableVi2f && (const float *)ptr < constants.mulTableVi2f + 32) {
303
name = StringFromFormat("constants.mulTableVi2f[%d]", (int)((const float *)ptr - constants.mulTableVi2f));
304
} else if ((const float *)ptr >= constants.mulTableVf2i && (const float *)ptr < constants.mulTableVf2i + 32) {
305
name = StringFromFormat("constants.mulTableVf2i[%d]", (int)((const float *)ptr - constants.mulTableVf2i));
306
} else if ((const Float4Constant *)ptr >= constants.vec4InitValues && (const Float4Constant *)ptr < constants.vec4InitValues + 8) {
307
name = StringFromFormat("constants.vec4InitValues[%d]", (int)((const Float4Constant *)ptr - constants.vec4InitValues));
308
} else {
309
name = "fixedCode";
310
}
311
} else {
312
return IRNativeBackend::DescribeCodePtr(ptr, name);
313
}
314
return true;
315
}
316
317
void X64JitBackend::ClearAllBlocks() {
318
ClearCodeSpace(jitStartOffset_);
319
EraseAllLinks(-1);
320
}
321
322
void X64JitBackend::InvalidateBlock(IRBlockCache *irBlockCache, int block_num) {
323
IRBlock *block = irBlockCache->GetBlock(block_num);
324
int offset = block->GetNativeOffset();
325
u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + offset;
326
327
// Overwrite the block with a jump to compile it again.
328
u32 pc = block->GetOriginalStart();
329
if (pc != 0) {
330
// Hopefully we always have at least 16 bytes, which should be all we need.
331
if (PlatformIsWXExclusive()) {
332
ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_WRITE);
333
}
334
335
XEmitter emitter(writable);
336
emitter.MOV(32, R(SCRATCH1), Imm32(pc));
337
emitter.JMP(dispatcherPCInSCRATCH1_, true);
338
int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);
339
if (bytesWritten < MIN_BLOCK_NORMAL_LEN)
340
emitter.ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - bytesWritten);
341
342
if (PlatformIsWXExclusive()) {
343
ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_EXEC);
344
}
345
}
346
347
EraseAllLinks(block_num);
348
}
349
350
void X64JitBackend::RestoreRoundingMode(bool force) {
351
CALL(restoreRoundingMode_);
352
}
353
354
void X64JitBackend::ApplyRoundingMode(bool force) {
355
CALL(applyRoundingMode_);
356
}
357
358
void X64JitBackend::MovFromPC(X64Reg r) {
359
MOV(32, R(r), MDisp(CTXREG, pcOffset));
360
}
361
362
void X64JitBackend::MovToPC(X64Reg r) {
363
MOV(32, MDisp(CTXREG, pcOffset), R(r));
364
}
365
366
void X64JitBackend::WriteDebugPC(uint32_t pc) {
367
if (hooks_.profilerPC)
368
MOV(32, M(hooks_.profilerPC), Imm32(pc));
369
}
370
371
void X64JitBackend::WriteDebugPC(Gen::X64Reg r) {
372
if (hooks_.profilerPC)
373
MOV(32, M(hooks_.profilerPC), R(r));
374
}
375
376
void X64JitBackend::WriteDebugProfilerStatus(IRProfilerStatus status) {
377
if (hooks_.profilerPC)
378
MOV(32, M(hooks_.profilerStatus), Imm32((int32_t)status));
379
}
380
381
void X64JitBackend::SaveStaticRegisters() {
382
if (jo.useStaticAlloc) {
383
//CALL(saveStaticRegisters_);
384
} else if (jo.downcountInRegister) {
385
// Inline the single operation
386
MOV(32, MDisp(CTXREG, downcountOffset), R(DOWNCOUNTREG));
387
}
388
}
389
390
void X64JitBackend::LoadStaticRegisters() {
391
if (jo.useStaticAlloc) {
392
//CALL(loadStaticRegisters_);
393
} else if (jo.downcountInRegister) {
394
MOV(32, R(DOWNCOUNTREG), MDisp(CTXREG, downcountOffset));
395
}
396
}
397
398
void X64JitBackend::EmitConst4x32(const void **c, uint32_t v) {
399
*c = AlignCode16();
400
for (int i = 0; i < 4; ++i)
401
Write32(v);
402
}
403
404
} // namespace MIPSComp
405
406
#endif
407
408