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/X64IRCompSystem.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 "Common/Profiler/Profiler.h"
22
#include "Core/Core.h"
23
#include "Core/Debugger/Breakpoints.h"
24
#include "Core/HLE/HLE.h"
25
#include "Core/HLE/ReplaceTables.h"
26
#include "Core/MemMap.h"
27
#include "Core/MIPS/MIPSAnalyst.h"
28
#include "Core/MIPS/IR/IRInterpreter.h"
29
#include "Core/MIPS/x86/X64IRJit.h"
30
#include "Core/MIPS/x86/X64IRRegCache.h"
31
32
// This file contains compilation for basic PC/downcount accounting, syscalls, debug funcs, etc.
33
//
34
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
35
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
36
37
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
38
#define CONDITIONAL_DISABLE {}
39
#define DISABLE { CompIR_Generic(inst); return; }
40
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
41
42
namespace MIPSComp {
43
44
using namespace Gen;
45
using namespace X64IRJitConstants;
46
47
void X64JitBackend::CompIR_Basic(IRInst inst) {
48
CONDITIONAL_DISABLE;
49
50
switch (inst.op) {
51
case IROp::Downcount:
52
// As long as we don't care about flags, just use LEA.
53
if (jo.downcountInRegister)
54
LEA(32, DOWNCOUNTREG, MDisp(DOWNCOUNTREG, -(s32)inst.constant));
55
else
56
SUB(32, MDisp(CTXREG, downcountOffset), SImmAuto((s32)inst.constant));
57
break;
58
59
case IROp::SetConst:
60
regs_.SetGPRImm(inst.dest, inst.constant);
61
break;
62
63
case IROp::SetConstF:
64
regs_.Map(inst);
65
if (inst.constant == 0) {
66
XORPS(regs_.FX(inst.dest), regs_.F(inst.dest));
67
} else if (inst.constant == 0x7FFFFFFF) {
68
MOVSS(regs_.FX(inst.dest), M(constants.noSignMask)); // rip accessible
69
} else if (inst.constant == 0x80000000) {
70
MOVSS(regs_.FX(inst.dest), M(constants.signBitAll)); // rip accessible
71
} else if (inst.constant == 0x7F800000) {
72
MOVSS(regs_.FX(inst.dest), M(constants.positiveInfinity)); // rip accessible
73
} else if (inst.constant == 0x7FC00000) {
74
MOVSS(regs_.FX(inst.dest), M(constants.qNAN)); // rip accessible
75
} else if (inst.constant == 0x3F800000) {
76
MOVSS(regs_.FX(inst.dest), M(constants.positiveOnes)); // rip accessible
77
} else if (inst.constant == 0xBF800000) {
78
MOVSS(regs_.FX(inst.dest), M(constants.negativeOnes)); // rip accessible
79
} else if (inst.constant == 0x4EFFFFFF) {
80
MOVSS(regs_.FX(inst.dest), M(constants.maxIntBelowAsFloat)); // rip accessible
81
} else {
82
MOV(32, R(SCRATCH1), Imm32(inst.constant));
83
MOVD_xmm(regs_.FX(inst.dest), R(SCRATCH1));
84
}
85
break;
86
87
case IROp::SetPC:
88
regs_.Map(inst);
89
MovToPC(regs_.RX(inst.src1));
90
break;
91
92
case IROp::SetPCConst:
93
lastConstPC_ = inst.constant;
94
MOV(32, R(SCRATCH1), Imm32(inst.constant));
95
MovToPC(SCRATCH1);
96
break;
97
98
default:
99
INVALIDOP;
100
break;
101
}
102
}
103
104
void X64JitBackend::CompIR_Breakpoint(IRInst inst) {
105
CONDITIONAL_DISABLE;
106
107
switch (inst.op) {
108
case IROp::Breakpoint:
109
FlushAll();
110
// Note: the constant could be a delay slot.
111
ABI_CallFunctionC((const void *)&IRRunBreakpoint, inst.constant);
112
TEST(32, R(EAX), R(EAX));
113
J_CC(CC_NZ, dispatcherCheckCoreState_, true);
114
break;
115
116
case IROp::MemoryCheck:
117
if (regs_.IsGPRImm(inst.src1)) {
118
uint32_t iaddr = regs_.GetGPRImm(inst.src1) + inst.constant;
119
uint32_t checkedPC = lastConstPC_ + inst.dest;
120
int size = MIPSAnalyst::OpMemoryAccessSize(checkedPC);
121
if (size == 0) {
122
checkedPC += 4;
123
size = MIPSAnalyst::OpMemoryAccessSize(checkedPC);
124
}
125
bool isWrite = MIPSAnalyst::IsOpMemoryWrite(checkedPC);
126
127
MemCheck check;
128
if (CBreakPoints::GetMemCheckInRange(iaddr, size, &check)) {
129
if (!(check.cond & MEMCHECK_READ) && !isWrite)
130
break;
131
if (!(check.cond & (MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE)) && isWrite)
132
break;
133
134
// We need to flush, or conditions and log expressions will see old register values.
135
FlushAll();
136
137
ABI_CallFunctionCC((const void *)&IRRunMemCheck, checkedPC, iaddr);
138
TEST(32, R(EAX), R(EAX));
139
J_CC(CC_NZ, dispatcherCheckCoreState_, true);
140
}
141
} else {
142
uint32_t checkedPC = lastConstPC_ + inst.dest;
143
int size = MIPSAnalyst::OpMemoryAccessSize(checkedPC);
144
if (size == 0) {
145
checkedPC += 4;
146
size = MIPSAnalyst::OpMemoryAccessSize(checkedPC);
147
}
148
bool isWrite = MIPSAnalyst::IsOpMemoryWrite(checkedPC);
149
150
const auto memchecks = CBreakPoints::GetMemCheckRanges(isWrite);
151
// We can trivially skip if there are no checks for this type (i.e. read vs write.)
152
if (memchecks.empty())
153
break;
154
155
X64Reg addrBase = regs_.MapGPR(inst.src1);
156
LEA(32, SCRATCH1, MDisp(addrBase, inst.constant));
157
158
// We need to flush, or conditions and log expressions will see old register values.
159
FlushAll();
160
161
std::vector<FixupBranch> hitChecks;
162
for (const auto &it : memchecks) {
163
if (it.end != 0) {
164
CMP(32, R(SCRATCH1), Imm32(it.start - size));
165
FixupBranch skipNext = J_CC(CC_BE);
166
167
CMP(32, R(SCRATCH1), Imm32(it.end));
168
hitChecks.push_back(J_CC(CC_B, true));
169
170
SetJumpTarget(skipNext);
171
} else {
172
CMP(32, R(SCRATCH1), Imm32(it.start));
173
hitChecks.push_back(J_CC(CC_E, true));
174
}
175
}
176
177
FixupBranch noHits = J(true);
178
179
// Okay, now land any hit here.
180
for (auto &fixup : hitChecks)
181
SetJumpTarget(fixup);
182
hitChecks.clear();
183
184
ABI_CallFunctionAA((const void *)&IRRunMemCheck, Imm32(checkedPC), R(SCRATCH1));
185
TEST(32, R(EAX), R(EAX));
186
J_CC(CC_NZ, dispatcherCheckCoreState_, true);
187
188
SetJumpTarget(noHits);
189
}
190
break;
191
192
default:
193
INVALIDOP;
194
break;
195
}
196
}
197
198
void X64JitBackend::CompIR_System(IRInst inst) {
199
CONDITIONAL_DISABLE;
200
201
switch (inst.op) {
202
case IROp::Syscall:
203
FlushAll();
204
SaveStaticRegisters();
205
206
WriteDebugProfilerStatus(IRProfilerStatus::SYSCALL);
207
#ifdef USE_PROFILER
208
// When profiling, we can't skip CallSyscall, since it times syscalls.
209
ABI_CallFunctionC((const u8 *)&CallSyscall, inst.constant);
210
#else
211
// Skip the CallSyscall where possible.
212
{
213
MIPSOpcode op(inst.constant);
214
void *quickFunc = GetQuickSyscallFunc(op);
215
if (quickFunc) {
216
ABI_CallFunctionP((const u8 *)quickFunc, (void *)GetSyscallFuncPointer(op));
217
} else {
218
ABI_CallFunctionC((const u8 *)&CallSyscall, inst.constant);
219
}
220
}
221
#endif
222
223
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
224
LoadStaticRegisters();
225
// This is always followed by an ExitToPC, where we check coreState.
226
break;
227
228
case IROp::CallReplacement:
229
FlushAll();
230
SaveStaticRegisters();
231
WriteDebugProfilerStatus(IRProfilerStatus::REPLACEMENT);
232
ABI_CallFunction(GetReplacementFunc(inst.constant)->replaceFunc);
233
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
234
LoadStaticRegisters();
235
236
// Since we flushed above, and we're mapping write, EAX should be safe.
237
regs_.Map(inst);
238
MOV(32, regs_.R(inst.dest), R(EAX));
239
NEG(32, R(EAX));
240
// Set it back if it negate made it negative. That's the absolute value.
241
CMOVcc(32, EAX, regs_.R(inst.dest), CC_S);
242
243
// Now set the dest to the sign bit status.
244
SAR(32, regs_.R(inst.dest), Imm8(31));
245
246
if (jo.downcountInRegister)
247
SUB(32, R(DOWNCOUNTREG), R(EAX));
248
else
249
SUB(32, MDisp(CTXREG, downcountOffset), R(EAX));
250
break;
251
252
case IROp::Break:
253
FlushAll();
254
// This doesn't naturally have restore/apply around it.
255
RestoreRoundingMode(true);
256
SaveStaticRegisters();
257
MovFromPC(SCRATCH1);
258
ABI_CallFunctionR((const void *)&Core_Break, SCRATCH1);
259
LoadStaticRegisters();
260
ApplyRoundingMode(true);
261
MovFromPC(SCRATCH1);
262
LEA(32, SCRATCH1, MDisp(SCRATCH1, 4));
263
JMP(dispatcherPCInSCRATCH1_, true);
264
break;
265
266
default:
267
INVALIDOP;
268
break;
269
}
270
}
271
272
void X64JitBackend::CompIR_Transfer(IRInst inst) {
273
CONDITIONAL_DISABLE;
274
275
switch (inst.op) {
276
case IROp::SetCtrlVFPU:
277
regs_.SetGPRImm(IRREG_VFPU_CTRL_BASE + inst.dest, (int32_t)inst.constant);
278
break;
279
280
case IROp::SetCtrlVFPUReg:
281
regs_.Map(inst);
282
MOV(32, regs_.R(IRREG_VFPU_CTRL_BASE + inst.dest), regs_.R(inst.src1));
283
break;
284
285
case IROp::SetCtrlVFPUFReg:
286
regs_.Map(inst);
287
MOVD_xmm(regs_.R(IRREG_VFPU_CTRL_BASE + inst.dest), regs_.FX(inst.src1));
288
break;
289
290
case IROp::FpCondFromReg:
291
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
292
MOV(32, regs_.R(IRREG_FPCOND), regs_.R(inst.src1));
293
break;
294
295
case IROp::FpCondToReg:
296
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::INIT } });
297
MOV(32, regs_.R(inst.dest), regs_.R(IRREG_FPCOND));
298
break;
299
300
case IROp::FpCtrlFromReg:
301
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
302
// Mask out the unused bits, and store fcr31 (using fpcond as a temp.)
303
MOV(32, regs_.R(IRREG_FPCOND), Imm32(0x0181FFFF));
304
AND(32, regs_.R(IRREG_FPCOND), regs_.R(inst.src1));
305
MOV(32, MDisp(CTXREG, fcr31Offset), regs_.R(IRREG_FPCOND));
306
307
// With that done, grab bit 23, the actual fpcond.
308
SHR(32, regs_.R(IRREG_FPCOND), Imm8(23));
309
AND(32, regs_.R(IRREG_FPCOND), Imm32(1));
310
break;
311
312
case IROp::FpCtrlToReg:
313
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::INIT } });
314
// Start by clearing the fpcond bit (might as well mask while we're here.)
315
MOV(32, regs_.R(inst.dest), Imm32(0x0101FFFF));
316
AND(32, regs_.R(inst.dest), MDisp(CTXREG, fcr31Offset));
317
318
AND(32, regs_.R(IRREG_FPCOND), Imm32(1));
319
if (cpu_info.bBMI2) {
320
RORX(32, SCRATCH1, regs_.R(IRREG_FPCOND), 32 - 23);
321
} else {
322
MOV(32, R(SCRATCH1), regs_.R(IRREG_FPCOND));
323
SHL(32, R(SCRATCH1), Imm8(23));
324
}
325
OR(32, regs_.R(inst.dest), R(SCRATCH1));
326
327
// Update fcr31 while we were here, for consistency.
328
MOV(32, MDisp(CTXREG, fcr31Offset), regs_.R(inst.dest));
329
break;
330
331
case IROp::VfpuCtrlToReg:
332
regs_.Map(inst);
333
MOV(32, regs_.R(inst.dest), regs_.R(IRREG_VFPU_CTRL_BASE + inst.src1));
334
break;
335
336
case IROp::FMovFromGPR:
337
if (regs_.IsGPRImm(inst.src1) && regs_.GetGPRImm(inst.src1) == 0) {
338
regs_.MapFPR(inst.dest, MIPSMap::NOINIT);
339
XORPS(regs_.FX(inst.dest), regs_.F(inst.dest));
340
} else {
341
regs_.Map(inst);
342
MOVD_xmm(regs_.FX(inst.dest), regs_.R(inst.src1));
343
}
344
break;
345
346
case IROp::FMovToGPR:
347
regs_.Map(inst);
348
MOVD_xmm(regs_.R(inst.dest), regs_.FX(inst.src1));
349
break;
350
351
default:
352
INVALIDOP;
353
break;
354
}
355
}
356
357
void X64JitBackend::CompIR_ValidateAddress(IRInst inst) {
358
CONDITIONAL_DISABLE;
359
360
bool isWrite = inst.src2 & 1;
361
int alignment = 0;
362
switch (inst.op) {
363
case IROp::ValidateAddress8:
364
alignment = 1;
365
break;
366
367
case IROp::ValidateAddress16:
368
alignment = 2;
369
break;
370
371
case IROp::ValidateAddress32:
372
alignment = 4;
373
break;
374
375
case IROp::ValidateAddress128:
376
alignment = 16;
377
break;
378
379
default:
380
INVALIDOP;
381
break;
382
}
383
384
if (regs_.IsGPRMappedAsPointer(inst.src1)) {
385
LEA(PTRBITS, SCRATCH1, MDisp(regs_.RXPtr(inst.src1), inst.constant));
386
#if defined(MASKED_PSP_MEMORY)
387
SUB(PTRBITS, R(SCRATCH1), ImmPtr(Memory::base));
388
#else
389
SUB(PTRBITS, R(SCRATCH1), R(MEMBASEREG));
390
#endif
391
} else {
392
regs_.Map(inst);
393
LEA(PTRBITS, SCRATCH1, MDisp(regs_.RX(inst.src1), inst.constant));
394
}
395
AND(32, R(SCRATCH1), Imm32(0x3FFFFFFF));
396
397
std::vector<FixupBranch> validJumps;
398
399
FixupBranch unaligned;
400
if (alignment != 1) {
401
TEST(32, R(SCRATCH1), Imm32(alignment - 1));
402
unaligned = J_CC(CC_NZ);
403
}
404
405
CMP(32, R(SCRATCH1), Imm32(PSP_GetUserMemoryEnd() - alignment));
406
FixupBranch tooHighRAM = J_CC(CC_A);
407
CMP(32, R(SCRATCH1), Imm32(PSP_GetKernelMemoryBase()));
408
validJumps.push_back(J_CC(CC_AE, true));
409
410
CMP(32, R(SCRATCH1), Imm32(PSP_GetVidMemEnd() - alignment));
411
FixupBranch tooHighVid = J_CC(CC_A);
412
CMP(32, R(SCRATCH1), Imm32(PSP_GetVidMemBase()));
413
validJumps.push_back(J_CC(CC_AE, true));
414
415
CMP(32, R(SCRATCH1), Imm32(PSP_GetScratchpadMemoryEnd() - alignment));
416
FixupBranch tooHighScratch = J_CC(CC_A);
417
CMP(32, R(SCRATCH1), Imm32(PSP_GetScratchpadMemoryBase()));
418
validJumps.push_back(J_CC(CC_AE, true));
419
420
if (alignment != 1)
421
SetJumpTarget(unaligned);
422
SetJumpTarget(tooHighRAM);
423
SetJumpTarget(tooHighVid);
424
SetJumpTarget(tooHighScratch);
425
426
// If we got here, something unusual and bad happened, so we'll always go back to the dispatcher.
427
// Because of that, we can avoid flushing outside this case.
428
auto regsCopy = regs_;
429
regsCopy.FlushAll();
430
431
// Ignores the return value, always returns to the dispatcher.
432
// Otherwise would need a thunk to restore regs.
433
ABI_CallFunctionACC((const void *)&ReportBadAddress, R(SCRATCH1), alignment, isWrite);
434
JMP(dispatcherCheckCoreState_, true);
435
436
for (FixupBranch &b : validJumps)
437
SetJumpTarget(b);
438
}
439
440
} // namespace MIPSComp
441
442
#endif
443
444