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/ARM/ArmJit.cpp
Views: 1401
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 (CBreakPoints::CheckSkipFirst() == currentMIPS->pc || CBreakPoints::CheckSkipFirst() == addr)
79
return 0;
80
81
BreakAction result = CBreakPoints::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 (CBreakPoints::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
CBreakPoints::ExecOpMemCheck(addr, pc);
100
return coreState == CORE_RUNNING || 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
CBreakPoints::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
CBreakPoints::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
#if PPSSPP_ARCH(ARM)
501
const ReplacementTableEntry *entry = nullptr;
502
u32 funcSize = 0;
503
if (!CanReplaceJalTo(dest, &entry, &funcSize)) {
504
return false;
505
}
506
507
// Warning - this might be bad if the code at the destination changes...
508
if (entry->flags & REPFLAG_ALLOWINLINE) {
509
// Jackpot! Just do it, no flushing. The code will be entirely inlined.
510
511
// First, compile the delay slot. It's unconditional so no issues.
512
CompileDelaySlot(DELAYSLOT_NICE);
513
// Technically, we should write the unused return address to RA, but meh.
514
MIPSReplaceFunc repl = entry->jitReplaceFunc;
515
int cycles = (this->*repl)();
516
js.downcountAmount += cycles;
517
} else {
518
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
519
CompileDelaySlot(DELAYSLOT_NICE);
520
FlushAll();
521
SaveDowncount();
522
RestoreRoundingMode();
523
524
if (BLInRange((const void *)(entry->replaceFunc))) {
525
BL((const void *)(entry->replaceFunc));
526
} else {
527
MOVI2R(R0, (uintptr_t)entry->replaceFunc);
528
BL(R0);
529
}
530
ApplyRoundingMode();
531
RestoreDowncount();
532
533
WriteDownCountR(R0);
534
}
535
536
js.compilerPC += 4;
537
// No writing exits, keep going!
538
539
if (CBreakPoints::HasMemChecks()) {
540
// We could modify coreState, so we need to write PC and check.
541
// Otherwise, PC may end up on the jal. We add 4 to skip the delay slot.
542
FlushAll();
543
WriteExit(GetCompilerPC() + 4, js.nextExit++);
544
js.compiling = false;
545
}
546
547
// Add a trigger so that if the inlined code changes, we invalidate this block.
548
blocks.ProxyBlock(js.blockStart, dest, funcSize / sizeof(u32), GetCodePtr());
549
#endif
550
return true;
551
}
552
553
void ArmJit::Comp_ReplacementFunc(MIPSOpcode op)
554
{
555
// We get here if we execute the first instruction of a replaced function. This means
556
// that we do need to return to RA.
557
558
// Inlined function calls (caught in jal) are handled differently.
559
560
int index = op.encoding & MIPS_EMUHACK_VALUE_MASK;
561
562
const ReplacementTableEntry *entry = GetReplacementFunc(index);
563
if (!entry) {
564
ERROR_LOG_REPORT_ONCE(replFunc, Log::HLE, "Invalid replacement op %08x at %08x", op.encoding, js.compilerPC);
565
return;
566
}
567
568
u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());
569
bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;
570
if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {
571
// We don't need to disable hooks, the code will still run.
572
if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {
573
// Any breakpoint at the func entry was already tripped, so we can still run the replacement.
574
// That's a common case - just to see how often the replacement hits.
575
disabled = CBreakPoints::RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));
576
}
577
}
578
579
if (disabled) {
580
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
581
} else if (entry->jitReplaceFunc) {
582
MIPSReplaceFunc repl = entry->jitReplaceFunc;
583
int cycles = (this->*repl)();
584
585
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
586
// Compile the original instruction at this address. We ignore cycles for hooks.
587
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
588
} else {
589
FlushAll();
590
// Flushed, so R1 is safe.
591
LDR(R1, CTXREG, MIPS_REG_RA * 4);
592
js.downcountAmount += cycles;
593
WriteExitDestInR(R1);
594
js.compiling = false;
595
}
596
} else if (entry->replaceFunc) {
597
FlushAll();
598
SaveDowncount();
599
RestoreRoundingMode();
600
gpr.SetRegImm(SCRATCHREG1, GetCompilerPC());
601
MovToPC(SCRATCHREG1);
602
603
// Standard function call, nothing fancy.
604
// The function returns the number of cycles it took in EAX.
605
if (BLInRange((const void *)(entry->replaceFunc))) {
606
BL((const void *)(entry->replaceFunc));
607
} else {
608
MOVI2R(R0, (uintptr_t)entry->replaceFunc);
609
BL(R0);
610
}
611
612
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
613
// Compile the original instruction at this address. We ignore cycles for hooks.
614
ApplyRoundingMode();
615
RestoreDowncount();
616
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);
617
} else {
618
ApplyRoundingMode();
619
RestoreDowncount();
620
621
CMPI2R(R0, 0, SCRATCHREG2);
622
FixupBranch positive = B_CC(CC_GE);
623
624
RSB(R0, R0, Operand2(0));
625
MovFromPC(R1);
626
FixupBranch done = B();
627
628
SetJumpTarget(positive);
629
LDR(R1, CTXREG, MIPS_REG_RA * 4);
630
631
SetJumpTarget(done);
632
WriteDownCountR(R0);
633
WriteExitDestInR(R1);
634
js.compiling = false;
635
}
636
} else {
637
ERROR_LOG(Log::HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
638
}
639
}
640
641
void ArmJit::Comp_Generic(MIPSOpcode op)
642
{
643
FlushAll();
644
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
645
if (func)
646
{
647
SaveDowncount();
648
// TODO: Perhaps keep the rounding mode for interp?
649
RestoreRoundingMode();
650
gpr.SetRegImm(SCRATCHREG1, GetCompilerPC());
651
MovToPC(SCRATCHREG1);
652
gpr.SetRegImm(R0, op.encoding);
653
QuickCallFunction(R1, (void *)func);
654
ApplyRoundingMode();
655
RestoreDowncount();
656
}
657
658
const MIPSInfo info = MIPSGetInfo(op);
659
if ((info & IS_VFPU) != 0 && (info & VFPU_NO_PREFIX) == 0)
660
{
661
// If it does eat them, it'll happen in MIPSCompileOp().
662
if ((info & OUT_EAT_PREFIX) == 0)
663
js.PrefixUnknown();
664
665
// Even if DISABLE'd, we want to set this flag so we overwrite.
666
if ((info & OUT_VFPU_PREFIX) != 0)
667
js.blockWrotePrefixes = true;
668
}
669
}
670
671
void ArmJit::MovFromPC(ARMReg r) {
672
LDR(r, CTXREG, offsetof(MIPSState, pc));
673
}
674
675
void ArmJit::MovToPC(ARMReg r) {
676
STR(r, CTXREG, offsetof(MIPSState, pc));
677
}
678
679
void ArmJit::SaveDowncount() {
680
if (jo.downcountInRegister)
681
STR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
682
}
683
684
void ArmJit::RestoreDowncount() {
685
if (jo.downcountInRegister)
686
LDR(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
687
}
688
689
void ArmJit::WriteDownCount(int offset) {
690
if (jo.downcountInRegister) {
691
int theDowncount = js.downcountAmount + offset;
692
Operand2 op2;
693
if (TryMakeOperand2(theDowncount, op2)) {
694
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, op2);
695
} else {
696
// Should be fine to use R2 here, flushed the regcache anyway.
697
// If js.downcountAmount can be expressed as an Imm8, we don't need this anyway.
698
gpr.SetRegImm(R2, theDowncount);
699
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, R2);
700
}
701
} else {
702
int theDowncount = js.downcountAmount + offset;
703
LDR(SCRATCHREG2, CTXREG, offsetof(MIPSState, downcount));
704
Operand2 op2;
705
if (TryMakeOperand2(theDowncount, op2)) {
706
SUBS(SCRATCHREG2, SCRATCHREG2, op2);
707
} else {
708
// Should be fine to use R2 here, flushed the regcache anyway.
709
// If js.downcountAmount can be expressed as an Imm8, we don't need this anyway.
710
gpr.SetRegImm(R2, theDowncount);
711
SUBS(SCRATCHREG2, SCRATCHREG2, R2);
712
}
713
STR(SCRATCHREG2, CTXREG, offsetof(MIPSState, downcount));
714
}
715
}
716
717
// Abuses R2
718
void ArmJit::WriteDownCountR(ARMReg reg) {
719
if (jo.downcountInRegister) {
720
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, reg);
721
} else {
722
LDR(R2, CTXREG, offsetof(MIPSState, downcount));
723
SUBS(R2, R2, reg);
724
STR(R2, CTXREG, offsetof(MIPSState, downcount));
725
}
726
}
727
728
// Destroys SCRATCHREG2. Does not destroy SCRATCHREG1.
729
void ArmJit::RestoreRoundingMode(bool force) {
730
// If the game has never set an interesting rounding mode, we can safely skip this.
731
if (force || js.hasSetRounding) {
732
QuickCallFunction(R1, restoreRoundingMode);
733
}
734
}
735
736
// Does not destroy R0 (SCRATCHREG1). Destroys R14 (SCRATCHREG2).
737
void ArmJit::ApplyRoundingMode(bool force) {
738
// If the game has never set an interesting rounding mode, we can safely skip this.
739
if (force || js.hasSetRounding) {
740
QuickCallFunction(R1, applyRoundingMode);
741
}
742
}
743
744
// Does (must!) not destroy R0 (SCRATCHREG1). Destroys R14 (SCRATCHREG2).
745
void ArmJit::UpdateRoundingMode(u32 fcr31) {
746
// We must set js.hasSetRounding at compile time, or this block will use the wrong rounding mode.
747
// The fcr31 parameter is -1 when not known at compile time, so we just assume it was changed.
748
if (fcr31 & 0x01000003) {
749
js.hasSetRounding = true;
750
}
751
}
752
753
// IDEA - could have a WriteDualExit that takes two destinations and two condition flags,
754
// and just have conditional that set PC "twice". This only works when we fall back to dispatcher
755
// though, as we need to have the SUBS flag set in the end. So with block linking in the mix,
756
// I don't think this gives us that much benefit.
757
void ArmJit::WriteExit(u32 destination, int exit_num)
758
{
759
// NOTE: Can't blindly check for bad destination addresses here, sometimes exits with bad destinations are written intentionally (like breaks).
760
_assert_msg_(exit_num < MAX_JIT_BLOCK_EXITS, "Expected a valid exit_num. dest=%08x", destination);
761
762
WriteDownCount();
763
//If nobody has taken care of this yet (this can be removed when all branches are done)
764
JitBlock *b = js.curBlock;
765
b->exitAddress[exit_num] = destination;
766
b->exitPtrs[exit_num] = GetWritableCodePtr();
767
768
// Link opportunity!
769
int block = blocks.GetBlockNumberFromStartAddress(destination);
770
if (block >= 0 && jo.enableBlocklink) {
771
// It exists! Joy of joy!
772
B(blocks.GetBlock(block)->checkedEntry);
773
b->linkStatus[exit_num] = true;
774
} else {
775
gpr.SetRegImm(R0, destination);
776
B((const void *)dispatcherPCInR0);
777
}
778
}
779
780
void ArmJit::WriteExitDestInR(ARMReg Reg)
781
{
782
// TODO: If not fast memory, check for invalid address in reg and trigger exception.
783
MovToPC(Reg);
784
WriteDownCount();
785
// TODO: shouldn't need an indirect branch here...
786
B((const void *)dispatcher);
787
}
788
789
void ArmJit::WriteSyscallExit()
790
{
791
WriteDownCount();
792
B((const void *)dispatcherCheckCoreState);
793
}
794
795
bool ArmJit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
796
if (CBreakPoints::IsAddressBreakPoint(addr)) {
797
MRS(R8);
798
FlushAll();
799
MOVI2R(SCRATCHREG1, GetCompilerPC());
800
MovToPC(SCRATCHREG1);
801
SaveDowncount();
802
RestoreRoundingMode();
803
MOVI2R(R0, addr);
804
QuickCallFunction(SCRATCHREG2, &JitBreakpoint);
805
806
// If 0, the conditional breakpoint wasn't taken.
807
CMPI2R(R0, 0, SCRATCHREG2);
808
FixupBranch skip = B_CC(CC_EQ);
809
WriteDownCount(downcountOffset);
810
ApplyRoundingMode();
811
RestoreDowncount();
812
B((const void *)dispatcherCheckCoreState);
813
SetJumpTarget(skip);
814
815
ApplyRoundingMode();
816
_MSR(true, false, R8);
817
return true;
818
}
819
820
return false;
821
}
822
823
bool ArmJit::CheckMemoryBreakpoint(int instructionOffset) {
824
if (CBreakPoints::HasMemChecks()) {
825
int off = instructionOffset + (js.inDelaySlot ? 1 : 0);
826
827
MRS(R8);
828
FlushAll();
829
SaveDowncount();
830
RestoreRoundingMode();
831
MOVI2R(R0, GetCompilerPC());
832
MovToPC(R0);
833
if (off != 0)
834
ADDI2R(R0, R0, off * 4, SCRATCHREG2);
835
QuickCallFunction(SCRATCHREG2, &JitMemCheck);
836
837
// If 0, the breakpoint wasn't tripped.
838
CMPI2R(R0, 0, SCRATCHREG2);
839
FixupBranch skip = B_CC(CC_EQ);
840
WriteDownCount(-1 - off);
841
ApplyRoundingMode();
842
RestoreDowncount();
843
B((const void *)dispatcherCheckCoreState);
844
SetJumpTarget(skip);
845
846
ApplyRoundingMode();
847
_MSR(true, false, R8);
848
return true;
849
}
850
851
return false;
852
}
853
854
void ArmJit::Comp_DoNothing(MIPSOpcode op) { }
855
856
MIPSOpcode ArmJit::GetOriginalOp(MIPSOpcode op) {
857
JitBlockCache *bc = GetBlockCache();
858
int block_num = bc->GetBlockNumberFromEmuHackOp(op, true);
859
if (block_num >= 0) {
860
return bc->GetOriginalFirstOp(block_num);
861
} else {
862
return op;
863
}
864
}
865
866
} // namespace
867
868
#endif // PPSSPP_ARCH(ARM)
869
870