Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/MIPS/ARM/ArmJit.cpp
5688 views
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 "ppsspp_config.h"
19
#if PPSSPP_ARCH(ARM)
20
21
#include "Common/Profiler/Profiler.h"
22
23
#include "Common/Log.h"
24
#include "Common/Serialize/Serializer.h"
25
#include "Common/Serialize/SerializeFuncs.h"
26
27
#include "Core/Reporting.h"
28
#include "Core/Config.h"
29
#include "Core/Core.h"
30
#include "Core/CoreTiming.h"
31
#include "Core/Debugger/Breakpoints.h"
32
#include "Core/Debugger/SymbolMap.h"
33
#include "Core/MemMap.h"
34
35
#include "Core/MIPS/MIPS.h"
36
#include "Core/MIPS/MIPSAnalyst.h"
37
#include "Core/MIPS/MIPSCodeUtils.h"
38
#include "Core/MIPS/MIPSInt.h"
39
#include "Core/MIPS/MIPSTables.h"
40
#include "Core/HLE/ReplaceTables.h"
41
#include "Core/MIPS/ARM/ArmRegCache.h"
42
#include "Core/MIPS/ARM/ArmRegCacheFPU.h"
43
44
#include "ArmRegCache.h"
45
#include "ArmJit.h"
46
#include "CPUDetect.h"
47
48
#include "ext/disarm.h"
49
50
using namespace ArmJitConstants;
51
52
void DisassembleArm(const u8 *data, int size) {
53
char temp[256];
54
for (int i = 0; i < size; i += 4) {
55
const u32 *codePtr = (const u32 *)(data + i);
56
u32 inst = codePtr[0];
57
u32 next = (i < size - 4) ? codePtr[1] : 0;
58
// MAGIC SPECIAL CASE for MOVW/MOVT readability!
59
if ((inst & 0x0FF00000) == 0x03000000 && (next & 0x0FF00000) == 0x03400000) {
60
u32 low = ((inst & 0x000F0000) >> 4) | (inst & 0x0FFF);
61
u32 hi = ((next & 0x000F0000) >> 4) | (next & 0x0FFF);
62
int reg0 = (inst & 0x0000F000) >> 12;
63
int reg1 = (next & 0x0000F000) >> 12;
64
if (reg0 == reg1) {
65
snprintf(temp, sizeof(temp), "%08x MOV32 %s, %04x%04x", (u32)inst, ArmRegName(reg0), hi, low);
66
INFO_LOG(Log::JIT, "A: %s", temp);
67
i += 4;
68
continue;
69
}
70
}
71
ArmDis((u32)codePtr, inst, temp, sizeof(temp), true);
72
INFO_LOG(Log::JIT, "A: %s", temp);
73
}
74
}
75
76
static u32 JitBreakpoint(uint32_t addr) {
77
// Should we skip this breakpoint?
78
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc || g_breakpoints.CheckSkipFirst() == addr)
79
return 0;
80
81
BreakAction result = g_breakpoints.ExecBreakPoint(addr);
82
if ((result & BREAK_ACTION_PAUSE) == 0)
83
return 0;
84
85
return 1;
86
}
87
88
static u32 JitMemCheck(u32 pc) {
89
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc)
90
return 0;
91
92
// Note: pc may be the delay slot.
93
const auto op = Memory::Read_Instruction(pc, true);
94
s32 offset = SignExtend16ToS32(op & 0xFFFF);
95
if (MIPSGetInfo(op) & IS_VFPU)
96
offset &= 0xFFFC;
97
u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset;
98
99
g_breakpoints.ExecOpMemCheck(addr, pc);
100
return coreState == CORE_RUNNING_CPU || coreState == CORE_NEXTFRAME ? 0 : 1;
101
}
102
103
namespace MIPSComp
104
{
105
using namespace ArmGen;
106
using namespace ArmJitConstants;
107
108
ArmJit::ArmJit(MIPSState *mipsState) : blocks(mipsState, this), gpr(mipsState, &js, &jo), fpr(mipsState, &js, &jo), mips_(mipsState) {
109
logBlocks = 0;
110
dontLogBlocks = 0;
111
blocks.Init();
112
gpr.SetEmitter(this);
113
fpr.SetEmitter(this);
114
AllocCodeSpace(1024 * 1024 * 16); // 32MB is the absolute max because that's what an ARM branch instruction can reach, backwards and forwards.
115
GenerateFixedCode();
116
117
INFO_LOG(Log::JIT, "ARM JIT initialized: %lld MB of code space", (long long)(GetSpaceLeft() / (1024 * 1024)));
118
119
js.startDefaultPrefix = mips_->HasDefaultPrefix();
120
121
// The debugger sets this so that "go" on a breakpoint will actually... go.
122
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
123
g_breakpoints.SetSkipFirst(0);
124
}
125
126
ArmJit::~ArmJit() {
127
}
128
129
void ArmJit::DoState(PointerWrap &p)
130
{
131
auto s = p.Section("Jit", 1, 2);
132
if (!s)
133
return;
134
135
if (p.mode == PointerWrap::MODE_READ && !js.startDefaultPrefix) {
136
WARN_LOG(Log::CPU, "Jit: An uneaten prefix was previously detected. Jitting in unknown-prefix mode.");
137
}
138
Do(p, js.startDefaultPrefix);
139
if (s >= 2) {
140
Do(p, js.hasSetRounding);
141
if (p.mode == PointerWrap::MODE_READ) {
142
js.lastSetRounding = 0;
143
}
144
} else {
145
js.hasSetRounding = 1;
146
}
147
148
// The debugger sets this so that "go" on a breakpoint will actually... go.
149
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
150
g_breakpoints.SetSkipFirst(0);
151
}
152
153
void ArmJit::UpdateFCR31() {
154
}
155
156
void ArmJit::FlushAll()
157
{
158
gpr.FlushAll();
159
fpr.FlushAll();
160
FlushPrefixV();
161
}
162
163
void ArmJit::FlushPrefixV() {
164
if (js.startDefaultPrefix && !js.blockWrotePrefixes && js.HasNoPrefix()) {
165
// They started default, we never modified in memory, and they're default now.
166
// No reason to modify memory. This is common at end of blocks. Just clear dirty.
167
js.prefixSFlag = (JitState::PrefixState)(js.prefixSFlag & ~JitState::PREFIX_DIRTY);
168
js.prefixTFlag = (JitState::PrefixState)(js.prefixTFlag & ~JitState::PREFIX_DIRTY);
169
js.prefixDFlag = (JitState::PrefixState)(js.prefixDFlag & ~JitState::PREFIX_DIRTY);
170
return;
171
}
172
173
if ((js.prefixSFlag & JitState::PREFIX_DIRTY) != 0) {
174
gpr.SetRegImm(SCRATCHREG1, js.prefixS);
175
STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_SPREFIX]));
176
js.prefixSFlag = (JitState::PrefixState) (js.prefixSFlag & ~JitState::PREFIX_DIRTY);
177
}
178
179
if ((js.prefixTFlag & JitState::PREFIX_DIRTY) != 0) {
180
gpr.SetRegImm(SCRATCHREG1, js.prefixT);
181
STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_TPREFIX]));
182
js.prefixTFlag = (JitState::PrefixState) (js.prefixTFlag & ~JitState::PREFIX_DIRTY);
183
}
184
185
if ((js.prefixDFlag & JitState::PREFIX_DIRTY) != 0) {
186
gpr.SetRegImm(SCRATCHREG1, js.prefixD);
187
STR(SCRATCHREG1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_DPREFIX]));
188
js.prefixDFlag = (JitState::PrefixState) (js.prefixDFlag & ~JitState::PREFIX_DIRTY);
189
}
190
191
// If we got here, we must've written prefixes to memory in this block.
192
js.blockWrotePrefixes = true;
193
}
194
195
void ArmJit::ClearCache()
196
{
197
blocks.Clear();
198
ClearCodeSpace(0);
199
GenerateFixedCode();
200
}
201
202
void ArmJit::InvalidateCacheAt(u32 em_address, int length) {
203
if (blocks.RangeMayHaveEmuHacks(em_address, em_address + length)) {
204
blocks.InvalidateICache(em_address, length);
205
}
206
}
207
208
void ArmJit::EatInstruction(MIPSOpcode op) {
209
MIPSInfo info = MIPSGetInfo(op);
210
if (info & DELAYSLOT) {
211
ERROR_LOG_REPORT_ONCE(ateDelaySlot, Log::JIT, "Ate a branch op.");
212
}
213
if (js.inDelaySlot) {
214
ERROR_LOG_REPORT_ONCE(ateInDelaySlot, Log::JIT, "Ate an instruction inside a delay slot.");
215
}
216
217
CheckJitBreakpoint(GetCompilerPC() + 4, 0);
218
js.numInstructions++;
219
js.compilerPC += 4;
220
js.downcountAmount += MIPSGetInstructionCycleEstimate(op);
221
}
222
223
void ArmJit::CompileDelaySlot(int flags) {
224
// Need to offset the downcount which was already incremented for the branch + delay slot.
225
CheckJitBreakpoint(GetCompilerPC() + 4, -2);
226
227
// preserve flag around the delay slot! Maybe this is not always necessary on ARM where
228
// we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the
229
// delay slot, we're screwed.
230
if (flags & DELAYSLOT_SAFE)
231
MRS(R8); // Save flags register. R8 is preserved through function calls and is not allocated.
232
233
js.inDelaySlot = true;
234
MIPSOpcode op = GetOffsetInstruction(1);
235
MIPSCompileOp(op, this);
236
js.inDelaySlot = false;
237
238
if (flags & DELAYSLOT_FLUSH)
239
FlushAll();
240
if (flags & DELAYSLOT_SAFE)
241
_MSR(true, false, R8); // Restore flags register
242
}
243
244
void ArmJit::Compile(u32 em_address) {
245
PROFILE_THIS_SCOPE("jitc");
246
247
// INFO_LOG(Log::JIT, "Compiling at %08x", em_address);
248
249
if (GetSpaceLeft() < 0x10000 || blocks.IsFull()) {
250
ClearCache();
251
}
252
253
BeginWrite(JitBlockCache::MAX_BLOCK_INSTRUCTIONS * 16);
254
255
int block_num = blocks.AllocateBlock(em_address);
256
JitBlock *b = blocks.GetBlock(block_num);
257
DoJit(em_address, b);
258
_assert_msg_(b->originalAddress == em_address, "original %08x != em_address %08x (block %d)", b->originalAddress, em_address, b->blockNum);
259
blocks.FinalizeBlock(block_num, jo.enableBlocklink);
260
261
EndWrite();
262
263
bool cleanSlate = false;
264
265
if (js.hasSetRounding && !js.lastSetRounding) {
266
WARN_LOG(Log::JIT, "Detected rounding mode usage, rebuilding jit with checks");
267
// Won't loop, since hasSetRounding is only ever set to 1.
268
js.lastSetRounding = js.hasSetRounding;
269
cleanSlate = true;
270
}
271
272
// Drat. The VFPU hit an uneaten prefix at the end of a block.
273
if (js.startDefaultPrefix && js.MayHavePrefix()) {
274
WARN_LOG_REPORT(Log::JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4);
275
js.LogPrefix();
276
277
// Let's try that one more time. We won't get back here because we toggled the value.
278
js.startDefaultPrefix = false;
279
cleanSlate = true;
280
}
281
282
if (cleanSlate) {
283
// Our assumptions are all wrong so it's clean-slate time.
284
ClearCache();
285
Compile(em_address);
286
}
287
}
288
289
void ArmJit::RunLoopUntil(u64 globalticks) {
290
PROFILE_THIS_SCOPE("jit");
291
((void (*)())enterDispatcher)();
292
}
293
294
u32 ArmJit::GetCompilerPC() {
295
return js.compilerPC;
296
}
297
298
MIPSOpcode ArmJit::GetOffsetInstruction(int offset) {
299
return Memory::Read_Instruction(GetCompilerPC() + 4 * offset);
300
}
301
302
const u8 *ArmJit::DoJit(u32 em_address, JitBlock *b)
303
{
304
js.cancel = false;
305
js.blockStart = em_address;
306
js.compilerPC = em_address;
307
js.lastContinuedPC = 0;
308
js.initialBlockSize = 0;
309
js.nextExit = 0;
310
js.downcountAmount = 0;
311
js.curBlock = b;
312
js.compiling = true;
313
js.inDelaySlot = false;
314
js.blockWrotePrefixes = false;
315
js.PrefixStart();
316
317
// We add a downcount flag check before the block, used when entering from a linked block.
318
// The last block decremented downcounter, and the flag should still be available.
319
// Got three variants here of where we position the code, needs detailed benchmarking.
320
321
FixupBranch bail;
322
if (jo.useBackJump) {
323
// Moves the MOVI2R and B *before* checkedEntry, and just branch backwards there.
324
// Speedup seems to be zero unfortunately but I guess it may vary from device to device.
325
// Not intrusive so keeping it around here to experiment with, may help on ARMv6 due to
326
// large/slow construction of 32-bit immediates?
327
JumpTarget backJump = GetCodePtr();
328
gpr.SetRegImm(R0, js.blockStart);
329
B((const void *)outerLoopPCInR0);
330
b->checkedEntry = GetCodePtr();
331
SetCC(CC_LT);
332
B(backJump);
333
SetCC(CC_AL);
334
} else if (jo.useForwardJump) {
335
b->checkedEntry = GetCodePtr();
336
SetCC(CC_LT);
337
bail = B();
338
SetCC(CC_AL);
339
} else {
340
b->checkedEntry = GetCodePtr();
341
SetCC(CC_LT);
342
gpr.SetRegImm(R0, js.blockStart);
343
B((const void *)outerLoopPCInR0);
344
SetCC(CC_AL);
345
}
346
347
b->normalEntry = GetCodePtr();
348
// TODO: this needs work
349
MIPSAnalyst::AnalysisResults analysis; // = MIPSAnalyst::Analyze(em_address);
350
351
gpr.Start(analysis);
352
fpr.Start(analysis);
353
354
js.numInstructions = 0;
355
while (js.compiling)
356
{
357
gpr.SetCompilerPC(GetCompilerPC()); // Let it know for log messages
358
// Jit breakpoints are quite fast, so let's do them in release too.
359
CheckJitBreakpoint(GetCompilerPC(), 0);
360
361
MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC());
362
//MIPSInfo info = MIPSGetInfo(inst);
363
//if (info & IS_VFPU) {
364
// logBlocks = 1;
365
//}
366
367
js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);
368
369
MIPSCompileOp(inst, this);
370
371
js.compilerPC += 4;
372
js.numInstructions++;
373
374
if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
375
gpr.FlushAll();
376
}
377
if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
378
fpr.FlushAll();
379
FlushPrefixV();
380
}
381
382
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
383
if (GetSpaceLeft() < 0x800 || js.numInstructions >= JitBlockCache::MAX_BLOCK_INSTRUCTIONS)
384
{
385
FlushAll();
386
WriteExit(GetCompilerPC(), js.nextExit++);
387
js.compiling = false;
388
}
389
}
390
391
if (jo.useForwardJump) {
392
SetJumpTarget(bail);
393
gpr.SetRegImm(R0, js.blockStart);
394
B((const void *)outerLoopPCInR0);
395
}
396
397
FlushLitPool();
398
399
char temp[256];
400
if (logBlocks > 0 && dontLogBlocks == 0) {
401
INFO_LOG(Log::JIT, "=============== mips ===============");
402
for (u32 cpc = em_address; cpc != GetCompilerPC() + 4; cpc += 4) {
403
MIPSDisAsm(Memory::Read_Opcode_JIT(cpc), cpc, temp, sizeof(temp), true);
404
INFO_LOG(Log::JIT, "M: %08x %s", cpc, temp);
405
}
406
}
407
408
b->codeSize = GetCodePtr() - b->normalEntry;
409
410
if (logBlocks > 0 && dontLogBlocks == 0) {
411
INFO_LOG(Log::JIT, "=============== ARM ===============");
412
DisassembleArm(b->normalEntry, GetCodePtr() - b->normalEntry);
413
}
414
if (logBlocks > 0)
415
logBlocks--;
416
if (dontLogBlocks > 0)
417
dontLogBlocks--;
418
419
// Don't forget to zap the newly written instructions in the instruction cache!
420
FlushIcache();
421
422
if (js.lastContinuedPC == 0)
423
b->originalSize = js.numInstructions;
424
else
425
{
426
// We continued at least once. Add the last proxy and set the originalSize correctly.
427
blocks.ProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
428
b->originalSize = js.initialBlockSize;
429
}
430
return b->normalEntry;
431
}
432
433
void ArmJit::AddContinuedBlock(u32 dest)
434
{
435
// The first block is the root block. When we continue, we create proxy blocks after that.
436
if (js.lastContinuedPC == 0)
437
js.initialBlockSize = js.numInstructions;
438
else
439
blocks.ProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
440
js.lastContinuedPC = dest;
441
}
442
443
bool ArmJit::DescribeCodePtr(const u8 *ptr, std::string &name)
444
{
445
// TODO: Not used by anything yet (except the modified VerySleepy on Windows)
446
return false;
447
}
448
449
void ArmJit::Comp_RunBlock(MIPSOpcode op)
450
{
451
// This shouldn't be necessary, the dispatcher should catch us before we get here.
452
ERROR_LOG(Log::JIT, "Comp_RunBlock should never be reached!");
453
}
454
455
void ArmJit::LinkBlock(u8 *exitPoint, const u8 *checkedEntry) {
456
if (PlatformIsWXExclusive()) {
457
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_WRITE);
458
}
459
460
ARMXEmitter emit(exitPoint);
461
u32 op = *((const u32 *)emit.GetCodePointer());
462
bool prelinked = (op & 0xFF000000) == 0xEA000000;
463
// Jump directly to the block, yay.
464
emit.B(checkedEntry);
465
466
if (!prelinked) {
467
do {
468
op = *((const u32 *)emit.GetCodePointer());
469
// Overwrite whatever is here with a breakpoint.
470
emit.BKPT(1);
471
// Stop after overwriting the next unconditional branch or BKPT.
472
// It can be a BKPT if we unlinked, and are now linking a different one.
473
} while ((op & 0xFF000000) != 0xEA000000 && (op & 0xFFF000F0) != 0xE1200070);
474
}
475
emit.FlushIcache();
476
if (PlatformIsWXExclusive()) {
477
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_EXEC);
478
}
479
}
480
481
void ArmJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
482
if (PlatformIsWXExclusive()) {
483
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_WRITE);
484
}
485
// Send anyone who tries to run this block back to the dispatcher.
486
// Not entirely ideal, but .. pretty good.
487
// I hope there's enough space...
488
// checkedEntry is the only "linked" entrance so it's enough to overwrite that.
489
ARMXEmitter emit(checkedEntry);
490
emit.MOVI2R(R0, originalAddress);
491
emit.STR(R0, CTXREG, offsetof(MIPSState, pc));
492
emit.B(MIPSComp::jit->GetDispatcher());
493
emit.FlushIcache();
494
if (PlatformIsWXExclusive()) {
495
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_EXEC);
496
}
497
}
498
499
bool ArmJit::ReplaceJalTo(u32 dest) {
500
const ReplacementTableEntry *entry = nullptr;
501
u32 funcSize = 0;
502
if (!CanReplaceJalTo(dest, &entry, &funcSize)) {
503
return false;
504
}
505
506
// Warning - this might be bad if the code at the destination changes...
507
if (entry->flags & REPFLAG_ALLOWINLINE) {
508
// Jackpot! Just do it, no flushing. The code will be entirely inlined.
509
510
// First, compile the delay slot. It's unconditional so no issues.
511
CompileDelaySlot(DELAYSLOT_NICE);
512
// Technically, we should write the unused return address to RA, but meh.
513
MIPSReplaceFunc repl = entry->jitReplaceFunc;
514
int cycles = (this->*repl)();
515
js.downcountAmount += cycles;
516
} else {
517
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
518
CompileDelaySlot(DELAYSLOT_NICE);
519
FlushAll();
520
SaveDowncount();
521
RestoreRoundingMode();
522
523
if (BLInRange((const void *)(entry->replaceFunc))) {
524
BL((const void *)(entry->replaceFunc));
525
} else {
526
MOVI2R(R0, (uintptr_t)entry->replaceFunc);
527
BL(R0);
528
}
529
ApplyRoundingMode();
530
RestoreDowncount();
531
532
WriteDownCountR(R0);
533
}
534
535
js.compilerPC += 4;
536
// No writing exits, keep going!
537
538
if (g_breakpoints.HasMemChecks()) {
539
// We could modify coreState, so we need to write PC and check.
540
// Otherwise, PC may end up on the jal. We add 4 to skip the delay slot.
541
FlushAll();
542
WriteExit(GetCompilerPC() + 4, js.nextExit++);
543
js.compiling = false;
544
}
545
546
// Add a trigger so that if the inlined code changes, we invalidate this block.
547
blocks.ProxyBlock(js.blockStart, dest, funcSize / sizeof(u32), GetCodePtr());
548
return true;
549
}
550
551
void ArmJit::Comp_ReplacementFunc(MIPSOpcode op)
552
{
553
// We get here if we execute the first instruction of a replaced function. This means
554
// that we do need to return to RA.
555
556
// Inlined function calls (caught in jal) are handled differently.
557
558
int index = op.encoding & MIPS_EMUHACK_VALUE_MASK;
559
560
const ReplacementTableEntry *entry = GetReplacementFunc(index);
561
if (!entry) {
562
ERROR_LOG_REPORT_ONCE(replFunc, Log::HLE, "Invalid replacement op %08x at %08x", op.encoding, js.compilerPC);
563
return;
564
}
565
566
u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());
567
bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;
568
if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {
569
// We don't need to disable hooks, the code will still run.
570
if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {
571
// Any breakpoint at the func entry was already tripped, so we can still run the replacement.
572
// That's a common case - just to see how often the replacement hits.
573
disabled = g_breakpoints.RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));
574
}
575
}
576
577
if (disabled) {
578
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
579
} else if (entry->jitReplaceFunc) {
580
MIPSReplaceFunc repl = entry->jitReplaceFunc;
581
int cycles = (this->*repl)();
582
583
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
584
// Compile the original instruction at this address. We ignore cycles for hooks.
585
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
586
} else {
587
FlushAll();
588
// Flushed, so R1 is safe.
589
LDR(R1, CTXREG, MIPS_REG_RA * 4);
590
js.downcountAmount += cycles;
591
WriteExitDestInR(R1);
592
js.compiling = false;
593
}
594
} else if (entry->replaceFunc) {
595
FlushAll();
596
SaveDowncount();
597
RestoreRoundingMode();
598
gpr.SetRegImm(SCRATCHREG1, GetCompilerPC());
599
MovToPC(SCRATCHREG1);
600
601
// Standard function call, nothing fancy.
602
// The function returns the number of cycles it took in EAX.
603
if (BLInRange((const void *)(entry->replaceFunc))) {
604
BL((const void *)(entry->replaceFunc));
605
} else {
606
MOVI2R(R0, (uintptr_t)entry->replaceFunc);
607
BL(R0);
608
}
609
610
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
611
// Compile the original instruction at this address. We ignore cycles for hooks.
612
ApplyRoundingMode();
613
RestoreDowncount();
614
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
615
} else {
616
ApplyRoundingMode();
617
RestoreDowncount();
618
619
CMPI2R(R0, 0, SCRATCHREG2);
620
FixupBranch positive = B_CC(CC_GE);
621
622
RSB(R0, R0, Operand2(0));
623
MovFromPC(R1);
624
FixupBranch done = B();
625
626
SetJumpTarget(positive);
627
LDR(R1, CTXREG, MIPS_REG_RA * 4);
628
629
SetJumpTarget(done);
630
WriteDownCountR(R0);
631
WriteExitDestInR(R1);
632
js.compiling = false;
633
}
634
} else {
635
ERROR_LOG(Log::HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
636
}
637
}
638
639
void ArmJit::Comp_Generic(MIPSOpcode op)
640
{
641
FlushAll();
642
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
643
if (func)
644
{
645
SaveDowncount();
646
// TODO: Perhaps keep the rounding mode for interp?
647
RestoreRoundingMode();
648
gpr.SetRegImm(SCRATCHREG1, GetCompilerPC());
649
MovToPC(SCRATCHREG1);
650
gpr.SetRegImm(R0, op.encoding);
651
QuickCallFunction(R1, (void *)func);
652
ApplyRoundingMode();
653
RestoreDowncount();
654
}
655
656
const MIPSInfo info = MIPSGetInfo(op);
657
if ((info & IS_VFPU) != 0 && (info & VFPU_NO_PREFIX) == 0)
658
{
659
// If it does eat them, it'll happen in MIPSCompileOp().
660
if ((info & OUT_EAT_PREFIX) == 0)
661
js.PrefixUnknown();
662
663
// Even if DISABLE'd, we want to set this flag so we overwrite.
664
if ((info & OUT_VFPU_PREFIX) != 0)
665
js.blockWrotePrefixes = true;
666
}
667
}
668
669
void ArmJit::MovFromPC(ARMReg r) {
670
LDR(r, CTXREG, offsetof(MIPSState, pc));
671
}
672
673
void ArmJit::MovToPC(ARMReg r) {
674
STR(r, CTXREG, offsetof(MIPSState, pc));
675
}
676
677
void ArmJit::SaveDowncount() {
678
if (jo.downcountInRegister)
679
STR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
680
}
681
682
void ArmJit::RestoreDowncount() {
683
if (jo.downcountInRegister)
684
LDR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
685
}
686
687
void ArmJit::WriteDownCount(int offset) {
688
if (jo.downcountInRegister) {
689
int theDowncount = js.downcountAmount + offset;
690
Operand2 op2;
691
if (TryMakeOperand2(theDowncount, op2)) {
692
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, op2);
693
} else {
694
// Should be fine to use R2 here, flushed the regcache anyway.
695
// If js.downcountAmount can be expressed as an Imm8, we don't need this anyway.
696
gpr.SetRegImm(R2, theDowncount);
697
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, R2);
698
}
699
} else {
700
int theDowncount = js.downcountAmount + offset;
701
LDR(SCRATCHREG2, CTXREG, offsetof(MIPSState, downcount));
702
Operand2 op2;
703
if (TryMakeOperand2(theDowncount, op2)) {
704
SUBS(SCRATCHREG2, SCRATCHREG2, op2);
705
} else {
706
// Should be fine to use R2 here, flushed the regcache anyway.
707
// If js.downcountAmount can be expressed as an Imm8, we don't need this anyway.
708
gpr.SetRegImm(R2, theDowncount);
709
SUBS(SCRATCHREG2, SCRATCHREG2, R2);
710
}
711
STR(SCRATCHREG2, CTXREG, offsetof(MIPSState, downcount));
712
}
713
}
714
715
// Abuses R2
716
void ArmJit::WriteDownCountR(ARMReg reg) {
717
if (jo.downcountInRegister) {
718
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, reg);
719
} else {
720
LDR(R2, CTXREG, offsetof(MIPSState, downcount));
721
SUBS(R2, R2, reg);
722
STR(R2, CTXREG, offsetof(MIPSState, downcount));
723
}
724
}
725
726
// Destroys SCRATCHREG2. Does not destroy SCRATCHREG1.
727
void ArmJit::RestoreRoundingMode(bool force) {
728
// If the game has never set an interesting rounding mode, we can safely skip this.
729
if (force || js.hasSetRounding) {
730
QuickCallFunction(R1, restoreRoundingMode);
731
}
732
}
733
734
// Does not destroy R0 (SCRATCHREG1). Destroys R14 (SCRATCHREG2).
735
void ArmJit::ApplyRoundingMode(bool force) {
736
// If the game has never set an interesting rounding mode, we can safely skip this.
737
if (force || js.hasSetRounding) {
738
QuickCallFunction(R1, applyRoundingMode);
739
}
740
}
741
742
// Does (must!) not destroy R0 (SCRATCHREG1). Destroys R14 (SCRATCHREG2).
743
void ArmJit::UpdateRoundingMode(u32 fcr31) {
744
// We must set js.hasSetRounding at compile time, or this block will use the wrong rounding mode.
745
// The fcr31 parameter is -1 when not known at compile time, so we just assume it was changed.
746
if (fcr31 & 0x01000003) {
747
js.hasSetRounding = true;
748
}
749
}
750
751
// IDEA - could have a WriteDualExit that takes two destinations and two condition flags,
752
// and just have conditional that set PC "twice". This only works when we fall back to dispatcher
753
// though, as we need to have the SUBS flag set in the end. So with block linking in the mix,
754
// I don't think this gives us that much benefit.
755
void ArmJit::WriteExit(u32 destination, int exit_num)
756
{
757
// NOTE: Can't blindly check for bad destination addresses here, sometimes exits with bad destinations are written intentionally (like breaks).
758
_assert_msg_(exit_num < MAX_JIT_BLOCK_EXITS, "Expected a valid exit_num. dest=%08x", destination);
759
760
WriteDownCount();
761
//If nobody has taken care of this yet (this can be removed when all branches are done)
762
JitBlock *b = js.curBlock;
763
b->exitAddress[exit_num] = destination;
764
b->exitPtrs[exit_num] = GetWritableCodePtr();
765
766
// Link opportunity!
767
int block = blocks.GetBlockNumberFromStartAddress(destination);
768
if (block >= 0 && jo.enableBlocklink) {
769
// It exists! Joy of joy!
770
B(blocks.GetBlock(block)->checkedEntry);
771
b->linkStatus[exit_num] = true;
772
} else {
773
gpr.SetRegImm(R0, destination);
774
B((const void *)dispatcherPCInR0);
775
}
776
}
777
778
void ArmJit::WriteExitDestInR(ARMReg Reg)
779
{
780
// TODO: If not fast memory, check for invalid address in reg and trigger exception.
781
MovToPC(Reg);
782
WriteDownCount();
783
// TODO: shouldn't need an indirect branch here...
784
B((const void *)dispatcher);
785
}
786
787
void ArmJit::WriteSyscallExit()
788
{
789
WriteDownCount();
790
B((const void *)dispatcherCheckCoreState);
791
}
792
793
bool ArmJit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
794
if (g_breakpoints.IsAddressBreakPoint(addr)) {
795
MRS(R8);
796
FlushAll();
797
MOVI2R(SCRATCHREG1, GetCompilerPC());
798
MovToPC(SCRATCHREG1);
799
SaveDowncount();
800
RestoreRoundingMode();
801
MOVI2R(R0, addr);
802
QuickCallFunction(SCRATCHREG2, &JitBreakpoint);
803
804
// If 0, the conditional breakpoint wasn't taken.
805
CMPI2R(R0, 0, SCRATCHREG2);
806
FixupBranch skip = B_CC(CC_EQ);
807
WriteDownCount(downcountOffset);
808
ApplyRoundingMode();
809
RestoreDowncount();
810
B((const void *)dispatcherCheckCoreState);
811
SetJumpTarget(skip);
812
813
ApplyRoundingMode();
814
_MSR(true, false, R8);
815
return true;
816
}
817
818
return false;
819
}
820
821
bool ArmJit::CheckMemoryBreakpoint(int instructionOffset) {
822
if (g_breakpoints.HasMemChecks()) {
823
int off = instructionOffset + (js.inDelaySlot ? 1 : 0);
824
825
MRS(R8);
826
FlushAll();
827
SaveDowncount();
828
RestoreRoundingMode();
829
MOVI2R(R0, GetCompilerPC());
830
MovToPC(R0);
831
if (off != 0)
832
ADDI2R(R0, R0, off * 4, SCRATCHREG2);
833
QuickCallFunction(SCRATCHREG2, &JitMemCheck);
834
835
// If 0, the breakpoint wasn't tripped.
836
CMPI2R(R0, 0, SCRATCHREG2);
837
FixupBranch skip = B_CC(CC_EQ);
838
WriteDownCount(-1 - off);
839
ApplyRoundingMode();
840
RestoreDowncount();
841
B((const void *)dispatcherCheckCoreState);
842
SetJumpTarget(skip);
843
844
ApplyRoundingMode();
845
_MSR(true, false, R8);
846
return true;
847
}
848
849
return false;
850
}
851
852
void ArmJit::Comp_DoNothing(MIPSOpcode op) { }
853
854
MIPSOpcode ArmJit::GetOriginalOp(MIPSOpcode op) {
855
JitBlockCache *bc = GetBlockCache();
856
int block_num = bc->GetBlockNumberFromEmuHackOp(op, true);
857
if (block_num >= 0) {
858
return bc->GetOriginalFirstOp(block_num);
859
} else {
860
return op;
861
}
862
}
863
864
} // namespace
865
866
#endif // PPSSPP_ARCH(ARM)
867
868