Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/MIPS/LoongArch64/LoongArch64Jit.cpp
3188 views
1
// Copyright (c) 2025- 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 <cstddef>
19
#include "Core/MemMap.h"
20
#include "Core/MIPS/MIPSTables.h"
21
#include "Core/MIPS/LoongArch64/LoongArch64Jit.h"
22
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.h"
23
24
#include <algorithm>
25
// for std::min
26
27
namespace MIPSComp {
28
29
using namespace LoongArch64Gen;
30
using namespace LoongArch64JitConstants;
31
32
// Needs space for a LI and J which might both be 32-bit offsets.
33
static constexpr int MIN_BLOCK_NORMAL_LEN = 16;
34
static constexpr int MIN_BLOCK_EXIT_LEN = 8;
35
36
LoongArch64JitBackend::LoongArch64JitBackend(JitOptions &jitopt, IRBlockCache &blocks)
37
: IRNativeBackend(blocks), jo(jitopt), regs_(&jo) {
38
// Automatically disable incompatible options.
39
if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {
40
jo.enablePointerify = false;
41
}
42
jo.optimizeForInterpreter = false;
43
44
// Since we store the offset, this is as big as it can be.
45
// We could shift off one bit to double it, would need to change LoongArch64Asm.
46
AllocCodeSpace(1024 * 1024 * 16);
47
48
regs_.Init(this);
49
}
50
51
LoongArch64JitBackend::~LoongArch64JitBackend(){
52
}
53
54
static void NoBlockExits() {
55
_assert_msg_(false, "Never exited block, invalid IR?");
56
}
57
58
bool LoongArch64JitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num) {
59
if (GetSpaceLeft() < 0x800)
60
return false;
61
62
IRBlock *block = irBlockCache->GetBlock(block_num);
63
BeginWrite(std::min(GetSpaceLeft(), (size_t)block->GetNumIRInstructions() * 32));
64
65
u32 startPC = block->GetOriginalStart();
66
bool wroteCheckedOffset = false;
67
if (jo.enableBlocklink && !jo.useBackJump) {
68
SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));
69
wroteCheckedOffset = true;
70
71
WriteDebugPC(startPC);
72
73
FixupBranch normalEntry = BGE(DOWNCOUNTREG, R_ZERO);
74
LI(SCRATCH1, startPC);
75
QuickJ(R_RA, outerLoopPCInSCRATCH1_);
76
SetJumpTarget(normalEntry);
77
}
78
79
// Don't worry, the codespace isn't large enough to overflow offsets.
80
const u8 *blockStart = GetCodePointer();
81
block->SetNativeOffset((int)GetOffset(blockStart));
82
compilingBlockNum_ = block_num;
83
84
regs_.Start(irBlockCache, block_num);
85
86
std::vector<const u8 *> addresses;
87
const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);
88
for (int i = 0; i < block->GetNumIRInstructions(); ++i) {
89
const IRInst &inst = instructions[i];
90
regs_.SetIRIndex(i);
91
addresses.push_back(GetCodePtr());
92
93
CompileIRInst(inst);
94
95
if (jo.Disabled(JitDisable::REGALLOC_GPR) || jo.Disabled(JitDisable::REGALLOC_FPR))
96
regs_.FlushAll(jo.Disabled(JitDisable::REGALLOC_GPR), jo.Disabled(JitDisable::REGALLOC_FPR));
97
98
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
99
if (GetSpaceLeft() < 0x800) {
100
compilingBlockNum_ = -1;
101
return false;
102
}
103
}
104
105
// We should've written an exit above. If we didn't, bad things will happen.
106
// Only check if debug stats are enabled - needlessly wastes jit space.
107
if (DebugStatsEnabled()) {
108
QuickCallFunction(&NoBlockExits, SCRATCH2);
109
QuickJ(R_RA, hooks_.crashHandler);
110
}
111
112
int len = (int)GetOffset(GetCodePointer()) - block->GetNativeOffset();
113
if (len < MIN_BLOCK_NORMAL_LEN) {
114
// We need at least 16 bytes to invalidate blocks with, but larger doesn't need to align.
115
ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - len);
116
}
117
118
if (!wroteCheckedOffset) {
119
// Always record this, even if block link disabled - it's used for size calc.
120
SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));
121
}
122
123
if (jo.enableBlocklink && jo.useBackJump) {
124
WriteDebugPC(startPC);
125
126
// Most blocks shouldn't be >= 256KB, so usually we can just BGE.
127
if (BranchInRange(blockStart)) {
128
BGE(DOWNCOUNTREG, R_ZERO, blockStart);
129
} else {
130
FixupBranch skip = BLT(DOWNCOUNTREG, R_ZERO);
131
B(blockStart);
132
SetJumpTarget(skip);
133
}
134
LI(SCRATCH1, startPC);
135
QuickJ(R_RA, outerLoopPCInSCRATCH1_);
136
}
137
138
if (logBlocks_ > 0) {
139
--logBlocks_;
140
141
std::map<const u8 *, int> addressesLookup;
142
for (int i = 0; i < (int)addresses.size(); ++i)
143
addressesLookup[addresses[i]] = i;
144
145
INFO_LOG(Log::JIT, "=============== LoongArch64 (%08x, %d bytes) ===============", startPC, len);
146
const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);
147
for (const u8 *p = blockStart; p < GetCodePointer(); ) {
148
auto it = addressesLookup.find(p);
149
if (it != addressesLookup.end()) {
150
const IRInst &inst = instructions[it->second];
151
152
char temp[512];
153
DisassembleIR(temp, sizeof(temp), inst);
154
INFO_LOG(Log::JIT, "IR: #%d %s", it->second, temp);
155
}
156
157
auto next = std::next(it);
158
const u8 *nextp = next == addressesLookup.end() ? GetCodePointer() : next->first;
159
160
#if PPSSPP_ARCH(LOONGARCH64)
161
auto lines = DisassembleLA64(p, (int)(nextp - p));
162
for (const auto &line : lines)
163
INFO_LOG(Log::JIT, "LA: %s", line.c_str());
164
#endif
165
p = nextp;
166
}
167
}
168
169
EndWrite();
170
FlushIcache();
171
compilingBlockNum_ = -1;
172
173
return true;
174
}
175
176
void LoongArch64JitBackend::WriteConstExit(uint32_t pc) {
177
int block_num = blocks_.GetBlockNumberFromStartAddress(pc);
178
const IRNativeBlock *nativeBlock = GetNativeBlock(block_num);
179
180
int exitStart = (int)GetOffset(GetCodePointer());
181
if (block_num >= 0 && jo.enableBlocklink && nativeBlock && nativeBlock->checkedOffset != 0) {
182
QuickJ(SCRATCH1, GetBasePtr() + nativeBlock->checkedOffset);
183
} else {
184
LI(SCRATCH1, pc);
185
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
186
}
187
188
if (jo.enableBlocklink) {
189
// In case of compression or early link, make sure it's large enough.
190
int len = (int)GetOffset(GetCodePointer()) - exitStart;
191
if (len < MIN_BLOCK_EXIT_LEN) {
192
ReserveCodeSpace(MIN_BLOCK_EXIT_LEN - len);
193
len = MIN_BLOCK_EXIT_LEN;
194
}
195
196
AddLinkableExit(compilingBlockNum_, pc, exitStart, len);
197
}
198
}
199
200
void LoongArch64JitBackend::OverwriteExit(int srcOffset, int len, int block_num) {
201
_dbg_assert_(len >= MIN_BLOCK_EXIT_LEN);
202
203
const IRNativeBlock *nativeBlock = GetNativeBlock(block_num);
204
if (nativeBlock) {
205
u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + srcOffset;
206
if (PlatformIsWXExclusive()) {
207
ProtectMemoryPages(writable, len, MEM_PROT_READ | MEM_PROT_WRITE);
208
}
209
210
LoongArch64Emitter emitter(GetBasePtr() + srcOffset, writable);
211
emitter.QuickJ(SCRATCH1, GetBasePtr() + nativeBlock->checkedOffset);
212
int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);
213
if (bytesWritten < len)
214
emitter.ReserveCodeSpace(len - bytesWritten);
215
emitter.FlushIcache();
216
217
if (PlatformIsWXExclusive()) {
218
ProtectMemoryPages(writable, 16, MEM_PROT_READ | MEM_PROT_EXEC);
219
}
220
}
221
}
222
223
void LoongArch64JitBackend::CompIR_Generic(IRInst inst) {
224
// If we got here, we're going the slow way.
225
uint64_t value;
226
memcpy(&value, &inst, sizeof(inst));
227
228
FlushAll();
229
LI(R4, value);
230
SaveStaticRegisters();
231
WriteDebugProfilerStatus(IRProfilerStatus::IR_INTERPRET);
232
QuickCallFunction(&DoIRInst, SCRATCH2);
233
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
234
LoadStaticRegisters();
235
236
// We only need to check the return value if it's a potential exit.
237
if ((GetIRMeta(inst.op)->flags & IRFLAG_EXIT) != 0) {
238
MOVE(SCRATCH1, R4);
239
240
if (BranchZeroInRange(dispatcherPCInSCRATCH1_)) {
241
BNEZ(R4, dispatcherPCInSCRATCH1_);
242
} else {
243
FixupBranch skip = BEQZ(R4);
244
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
245
SetJumpTarget(skip);
246
}
247
}
248
}
249
250
void LoongArch64JitBackend::CompIR_Interpret(IRInst inst) {
251
MIPSOpcode op(inst.constant);
252
253
// IR protects us against this being a branching instruction (well, hopefully.)
254
FlushAll();
255
SaveStaticRegisters();
256
WriteDebugProfilerStatus(IRProfilerStatus::INTERPRET);
257
if (DebugStatsEnabled()) {
258
LI(R4, MIPSGetName(op));
259
QuickCallFunction(&NotifyMIPSInterpret, SCRATCH2);
260
}
261
LI(R4, (int32_t)inst.constant);
262
QuickCallFunction((const u8 *)MIPSGetInterpretFunc(op), SCRATCH2);
263
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
264
LoadStaticRegisters();
265
}
266
267
void LoongArch64JitBackend::FlushAll() {
268
regs_.FlushAll();
269
}
270
271
bool LoongArch64JitBackend::DescribeCodePtr(const u8 *ptr, std::string &name) const {
272
// Used in disassembly viewer.
273
// Don't use spaces; profilers get confused or truncate them.
274
if (ptr == dispatcherPCInSCRATCH1_) {
275
name = "dispatcherPCInSCRATCH1";
276
} else if (ptr == outerLoopPCInSCRATCH1_) {
277
name = "outerLoopPCInSCRATCH1";
278
} else if (ptr == dispatcherNoCheck_) {
279
name = "dispatcherNoCheck";
280
} else if (ptr == saveStaticRegisters_) {
281
name = "saveStaticRegisters";
282
} else if (ptr == loadStaticRegisters_) {
283
name = "loadStaticRegisters";
284
} else if (ptr == applyRoundingMode_) {
285
name = "applyRoundingMode";
286
} else if (ptr >= GetBasePtr() && ptr < GetBasePtr() + jitStartOffset_) {
287
name = "fixedCode";
288
} else {
289
return IRNativeBackend::DescribeCodePtr(ptr, name);
290
}
291
return true;
292
}
293
294
void LoongArch64JitBackend::ClearAllBlocks() {
295
ClearCodeSpace(jitStartOffset_);
296
FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_);
297
EraseAllLinks(-1);
298
}
299
300
void LoongArch64JitBackend::InvalidateBlock(IRBlockCache *irBlockCache, int block_num) {
301
IRBlock *block = irBlockCache->GetBlock(block_num);
302
int offset = block->GetNativeOffset();
303
u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + offset;
304
305
// Overwrite the block with a jump to compile it again.
306
u32 pc = block->GetOriginalStart();
307
if (pc != 0) {
308
// Hopefully we always have at least 16 bytes, which should be all we need.
309
if (PlatformIsWXExclusive()) {
310
ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_WRITE);
311
}
312
313
LoongArch64Emitter emitter(GetBasePtr() + offset, writable);
314
// We sign extend to ensure it will fit in 32-bit and 8 bytes LI.
315
// TODO: May need to change if dispatcher doesn't reload PC.
316
emitter.LI(SCRATCH1, (int32_t)pc);
317
emitter.QuickJ(R_RA, dispatcherPCInSCRATCH1_);
318
int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);
319
if (bytesWritten < MIN_BLOCK_NORMAL_LEN)
320
emitter.ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - bytesWritten);
321
emitter.FlushIcache();
322
323
if (PlatformIsWXExclusive()) {
324
ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_EXEC);
325
}
326
}
327
328
EraseAllLinks(block_num);
329
}
330
331
void LoongArch64JitBackend::RestoreRoundingMode(bool force) {
332
MOVGR2FCSR(FCSR3, R_ZERO); // 0 = RNE - Round Nearest Even
333
}
334
335
void LoongArch64JitBackend::ApplyRoundingMode(bool force) {
336
QuickCallFunction(applyRoundingMode_);
337
}
338
339
void LoongArch64JitBackend::MovFromPC(LoongArch64Reg r) {
340
LD_WU(r, CTXREG, offsetof(MIPSState, pc));
341
}
342
343
void LoongArch64JitBackend::MovToPC(LoongArch64Reg r) {
344
ST_W(r, CTXREG, offsetof(MIPSState, pc));
345
}
346
347
void LoongArch64JitBackend::WriteDebugPC(uint32_t pc) {
348
if (hooks_.profilerPC) {
349
int offset = (const u8 *)hooks_.profilerPC - GetBasePtr();
350
LI(SCRATCH2, hooks_.profilerPC);
351
LI(R_RA, (int32_t)pc);
352
ST_W(R_RA, SCRATCH2, 0);
353
}
354
}
355
356
void LoongArch64JitBackend::WriteDebugPC(LoongArch64Reg r) {
357
if (hooks_.profilerPC) {
358
int offset = (const u8 *)hooks_.profilerPC - GetBasePtr();
359
LI(SCRATCH2, hooks_.profilerPC);
360
ST_W(r, SCRATCH2, 0);
361
}
362
}
363
364
void LoongArch64JitBackend::WriteDebugProfilerStatus(IRProfilerStatus status) {
365
if (hooks_.profilerPC) {
366
int offset = (const u8 *)hooks_.profilerStatus - GetBasePtr();
367
LI(SCRATCH2, hooks_.profilerStatus);
368
LI(R_RA, (int)status);
369
ST_W(R_RA, SCRATCH2, 0);
370
}
371
}
372
373
void LoongArch64JitBackend::SaveStaticRegisters() {
374
if (jo.useStaticAlloc) {
375
QuickCallFunction(saveStaticRegisters_);
376
} else {
377
// Inline the single operation
378
ST_W(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
379
}
380
}
381
382
void LoongArch64JitBackend::LoadStaticRegisters() {
383
if (jo.useStaticAlloc) {
384
QuickCallFunction(loadStaticRegisters_);
385
} else {
386
LD_W(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
387
}
388
}
389
390
void LoongArch64JitBackend::NormalizeSrc1(IRInst inst, LoongArch64Reg *reg, LoongArch64Reg tempReg, bool allowOverlap) {
391
*reg = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, tempReg);
392
}
393
394
void LoongArch64JitBackend::NormalizeSrc12(IRInst inst, LoongArch64Reg *lhs, LoongArch64Reg *rhs, LoongArch64Reg lhsTempReg, LoongArch64Reg rhsTempReg, bool allowOverlap) {
395
*lhs = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, lhsTempReg);
396
*rhs = NormalizeR(inst.src2, allowOverlap ? 0 : inst.dest, rhsTempReg);
397
}
398
399
LoongArch64Reg LoongArch64JitBackend::NormalizeR(IRReg rs, IRReg rd, LoongArch64Reg tempReg) {
400
// For proper compare, we must sign extend so they both match or don't match.
401
// But don't change pointers, in case one is SP (happens in LittleBigPlanet.)
402
if (regs_.IsGPRImm(rs) && regs_.GetGPRImm(rs) == 0) {
403
return R_ZERO;
404
} else if (regs_.IsGPRMappedAsPointer(rs) || rs == rd) {
405
return regs_.Normalize32(rs, tempReg);
406
} else {
407
return regs_.Normalize32(rs);
408
}
409
}
410
411
} // namespace MIPSComp
412