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/ARM64/Arm64IRCompALU.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
// In other words, PPSSPP_ARCH(ARM64) || DISASM_ALL.
20
#if PPSSPP_ARCH(ARM64) || (PPSSPP_PLATFORM(WINDOWS) && !defined(__LIBRETRO__))
21
22
#include "Common/CPUDetect.h"
23
#include "Core/MemMap.h"
24
#include "Core/MIPS/ARM64/Arm64IRJit.h"
25
#include "Core/MIPS/ARM64/Arm64IRRegCache.h"
26
27
// This file contains compilation for integer / arithmetic / logic related instructions.
28
//
29
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
30
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
31
32
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
33
#define CONDITIONAL_DISABLE {}
34
#define DISABLE { CompIR_Generic(inst); return; }
35
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
36
37
namespace MIPSComp {
38
39
using namespace Arm64Gen;
40
using namespace Arm64IRJitConstants;
41
42
void Arm64JitBackend::CompIR_Arith(IRInst inst) {
43
CONDITIONAL_DISABLE;
44
45
bool allowPtrMath = inst.constant <= 0x7FFFFFFF;
46
#ifdef MASKED_PSP_MEMORY
47
// Since we modify it, we can't safely.
48
allowPtrMath = false;
49
#endif
50
51
switch (inst.op) {
52
case IROp::Add:
53
regs_.Map(inst);
54
ADD(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
55
break;
56
57
case IROp::Sub:
58
regs_.Map(inst);
59
SUB(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
60
break;
61
62
case IROp::AddConst:
63
if (regs_.IsGPRMappedAsPointer(inst.dest) && inst.dest == inst.src1 && allowPtrMath) {
64
regs_.MarkGPRAsPointerDirty(inst.dest);
65
ADDI2R(regs_.RPtr(inst.dest), regs_.RPtr(inst.src1), (int)inst.constant, SCRATCH1_64);
66
} else {
67
regs_.Map(inst);
68
ADDI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
69
}
70
break;
71
72
case IROp::SubConst:
73
if (regs_.IsGPRMappedAsPointer(inst.dest) && inst.dest == inst.src1 && allowPtrMath) {
74
regs_.MarkGPRAsPointerDirty(inst.dest);
75
SUBI2R(regs_.RPtr(inst.dest), regs_.RPtr(inst.src1), (int)inst.constant, SCRATCH1_64);
76
} else {
77
regs_.Map(inst);
78
SUBI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
79
}
80
break;
81
82
case IROp::Neg:
83
regs_.Map(inst);
84
NEG(regs_.R(inst.dest), regs_.R(inst.src1));
85
break;
86
87
default:
88
INVALIDOP;
89
break;
90
}
91
}
92
93
void Arm64JitBackend::CompIR_Assign(IRInst inst) {
94
CONDITIONAL_DISABLE;
95
96
switch (inst.op) {
97
case IROp::Mov:
98
if (inst.dest != inst.src1) {
99
regs_.Map(inst);
100
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
101
}
102
break;
103
104
case IROp::Ext8to32:
105
regs_.Map(inst);
106
SXTB(regs_.R(inst.dest), regs_.R(inst.src1));
107
break;
108
109
case IROp::Ext16to32:
110
regs_.Map(inst);
111
SXTH(regs_.R(inst.dest), regs_.R(inst.src1));
112
break;
113
114
default:
115
INVALIDOP;
116
break;
117
}
118
}
119
120
void Arm64JitBackend::CompIR_Bits(IRInst inst) {
121
CONDITIONAL_DISABLE;
122
123
switch (inst.op) {
124
case IROp::BSwap16:
125
regs_.Map(inst);
126
REV16(regs_.R(inst.dest), regs_.R(inst.src1));
127
break;
128
129
case IROp::BSwap32:
130
regs_.Map(inst);
131
REV32(regs_.R(inst.dest), regs_.R(inst.src1));
132
break;
133
134
case IROp::Clz:
135
regs_.Map(inst);
136
CLZ(regs_.R(inst.dest), regs_.R(inst.src1));
137
break;
138
139
case IROp::ReverseBits:
140
regs_.Map(inst);
141
RBIT(regs_.R(inst.dest), regs_.R(inst.src1));
142
break;
143
144
default:
145
INVALIDOP;
146
break;
147
}
148
}
149
150
void Arm64JitBackend::CompIR_Compare(IRInst inst) {
151
CONDITIONAL_DISABLE;
152
153
switch (inst.op) {
154
case IROp::Slt:
155
regs_.Map(inst);
156
CMP(regs_.R(inst.src1), regs_.R(inst.src2));
157
CSET(regs_.R(inst.dest), CC_LT);
158
break;
159
160
case IROp::SltConst:
161
if (inst.constant == 0) {
162
// Basically, getting the sign bit.
163
regs_.Map(inst);
164
UBFX(regs_.R(inst.dest), regs_.R(inst.src1), 31, 1);
165
} else {
166
regs_.Map(inst);
167
CMPI2R(regs_.R(inst.src1), (int32_t)inst.constant, SCRATCH1);
168
CSET(regs_.R(inst.dest), CC_LT);
169
}
170
break;
171
172
case IROp::SltU:
173
if (regs_.IsGPRImm(inst.src1) && regs_.GetGPRImm(inst.src1) == 0) {
174
// This is kinda common, same as != 0. Avoid flushing src1.
175
regs_.SpillLockGPR(inst.src2, inst.dest);
176
regs_.MapGPR(inst.src2);
177
regs_.MapGPR(inst.dest, MIPSMap::NOINIT);
178
CMP(regs_.R(inst.src2), 0);
179
CSET(regs_.R(inst.dest), CC_NEQ);
180
} else {
181
regs_.Map(inst);
182
CMP(regs_.R(inst.src1), regs_.R(inst.src2));
183
CSET(regs_.R(inst.dest), CC_LO);
184
}
185
break;
186
187
case IROp::SltUConst:
188
if (inst.constant == 0) {
189
regs_.SetGPRImm(inst.dest, 0);
190
} else {
191
regs_.Map(inst);
192
CMPI2R(regs_.R(inst.src1), (int32_t)inst.constant, SCRATCH1);
193
CSET(regs_.R(inst.dest), CC_LO);
194
}
195
break;
196
197
default:
198
INVALIDOP;
199
break;
200
}
201
}
202
203
void Arm64JitBackend::CompIR_CondAssign(IRInst inst) {
204
CONDITIONAL_DISABLE;
205
206
switch (inst.op) {
207
case IROp::MovZ:
208
if (inst.dest != inst.src2) {
209
regs_.Map(inst);
210
CMP(regs_.R(inst.src1), 0);
211
CSEL(regs_.R(inst.dest), regs_.R(inst.src2), regs_.R(inst.dest), CC_EQ);
212
}
213
break;
214
215
case IROp::MovNZ:
216
if (inst.dest != inst.src2) {
217
regs_.Map(inst);
218
CMP(regs_.R(inst.src1), 0);
219
CSEL(regs_.R(inst.dest), regs_.R(inst.src2), regs_.R(inst.dest), CC_NEQ);
220
}
221
break;
222
223
case IROp::Max:
224
if (inst.src1 != inst.src2) {
225
regs_.Map(inst);
226
CMP(regs_.R(inst.src1), regs_.R(inst.src2));
227
CSEL(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2), CC_GE);
228
} else if (inst.dest != inst.src1) {
229
regs_.Map(inst);
230
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
231
}
232
break;
233
234
case IROp::Min:
235
if (inst.src1 != inst.src2) {
236
regs_.Map(inst);
237
CMP(regs_.R(inst.src1), regs_.R(inst.src2));
238
CSEL(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2), CC_LE);
239
} else if (inst.dest != inst.src1) {
240
regs_.Map(inst);
241
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
242
}
243
break;
244
245
default:
246
INVALIDOP;
247
break;
248
}
249
}
250
251
void Arm64JitBackend::CompIR_Div(IRInst inst) {
252
CONDITIONAL_DISABLE;
253
254
switch (inst.op) {
255
case IROp::Div:
256
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::NOINIT } });
257
// INT_MIN divided by -1 = INT_MIN, anything divided by 0 = 0.
258
SDIV(regs_.R(IRREG_LO), regs_.R(inst.src1), regs_.R(inst.src2));
259
MSUB(SCRATCH1, regs_.R(inst.src2), regs_.R(IRREG_LO), regs_.R(inst.src1));
260
261
// Now some tweaks for divide by zero and overflow.
262
{
263
// Start with divide by zero, remainder is fine.
264
FixupBranch skipNonZero = CBNZ(regs_.R(inst.src2));
265
MOVI2R(regs_.R(IRREG_LO), 1);
266
CMP(regs_.R(inst.src1), 0);
267
// mips->lo = numerator < 0 ? 1 : -1
268
CSNEG(regs_.R(IRREG_LO), regs_.R(IRREG_LO), regs_.R(IRREG_LO), CC_LT);
269
SetJumpTarget(skipNonZero);
270
271
// For overflow, we end up with remainder as zero, need to fix.
272
MOVI2R(SCRATCH2, 0x80000000);
273
CMP(regs_.R(inst.src1), SCRATCH2);
274
FixupBranch notMostNegative = B(CC_NEQ);
275
CMPI2R(regs_.R(inst.src2), -1);
276
// If it's not equal, keep SCRATCH1. Otherwise (equal), invert 0 = -1.
277
CSINV(SCRATCH1, SCRATCH1, WZR, CC_NEQ);
278
SetJumpTarget(notMostNegative);
279
}
280
281
BFI(regs_.R64(IRREG_LO), SCRATCH1_64, 32, 32);
282
break;
283
284
case IROp::DivU:
285
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::NOINIT } });
286
// Anything divided by 0 = 0.
287
UDIV(regs_.R(IRREG_LO), regs_.R(inst.src1), regs_.R(inst.src2));
288
MSUB(SCRATCH1, regs_.R(inst.src2), regs_.R(IRREG_LO), regs_.R(inst.src1));
289
290
// On divide by zero, we have to update LO - remainder is correct.
291
{
292
FixupBranch skipNonZero = CBNZ(regs_.R(inst.src2));
293
MOVI2R(regs_.R(IRREG_LO), 0xFFFF);
294
CMP(regs_.R(inst.src1), regs_.R(IRREG_LO));
295
// If it's <= 0xFFFF, keep 0xFFFF. Otherwise, invert 0 = -1.
296
CSINV(regs_.R(IRREG_LO), regs_.R(IRREG_LO), WZR, CC_LE);
297
SetJumpTarget(skipNonZero);
298
}
299
300
BFI(regs_.R64(IRREG_LO), SCRATCH1_64, 32, 32);
301
break;
302
303
default:
304
INVALIDOP;
305
break;
306
}
307
}
308
309
void Arm64JitBackend::CompIR_HiLo(IRInst inst) {
310
CONDITIONAL_DISABLE;
311
312
switch (inst.op) {
313
case IROp::MtLo:
314
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
315
BFI(regs_.R64(IRREG_LO), regs_.R64(inst.src1), 0, 32);
316
break;
317
318
case IROp::MtHi:
319
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
320
BFI(regs_.R64(IRREG_LO), regs_.R64(inst.src1), 32, 32);
321
break;
322
323
case IROp::MfLo:
324
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::INIT } });
325
MOV(regs_.R(inst.dest), regs_.R(IRREG_LO));
326
break;
327
328
case IROp::MfHi:
329
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::INIT } });
330
UBFX(regs_.R64(inst.dest), regs_.R64(IRREG_LO), 32, 32);
331
break;
332
333
default:
334
INVALIDOP;
335
break;
336
}
337
}
338
339
void Arm64JitBackend::CompIR_Logic(IRInst inst) {
340
CONDITIONAL_DISABLE;
341
342
switch (inst.op) {
343
case IROp::And:
344
if (inst.src1 != inst.src2) {
345
regs_.Map(inst);
346
AND(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
347
} else if (inst.src1 != inst.dest) {
348
regs_.Map(inst);
349
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
350
}
351
break;
352
353
case IROp::Or:
354
if (inst.src1 != inst.src2) {
355
regs_.Map(inst);
356
ORR(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
357
} else if (inst.src1 != inst.dest) {
358
regs_.Map(inst);
359
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
360
}
361
break;
362
363
case IROp::Xor:
364
if (inst.src1 == inst.src2) {
365
regs_.SetGPRImm(inst.dest, 0);
366
} else {
367
regs_.Map(inst);
368
EOR(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
369
}
370
break;
371
372
case IROp::AndConst:
373
regs_.Map(inst);
374
ANDI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
375
break;
376
377
case IROp::OrConst:
378
regs_.Map(inst);
379
ORRI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
380
break;
381
382
case IROp::XorConst:
383
regs_.Map(inst);
384
EORI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
385
break;
386
387
case IROp::Not:
388
regs_.Map(inst);
389
MVN(regs_.R(inst.dest), regs_.R(inst.src1));
390
break;
391
392
default:
393
INVALIDOP;
394
break;
395
}
396
}
397
398
void Arm64JitBackend::CompIR_Mult(IRInst inst) {
399
CONDITIONAL_DISABLE;
400
401
switch (inst.op) {
402
case IROp::Mult:
403
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::NOINIT } });
404
SMULL(regs_.R64(IRREG_LO), regs_.R(inst.src1), regs_.R(inst.src2));
405
break;
406
407
case IROp::MultU:
408
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::NOINIT } });
409
UMULL(regs_.R64(IRREG_LO), regs_.R(inst.src1), regs_.R(inst.src2));
410
break;
411
412
case IROp::Madd:
413
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
414
// Accumulator is at the end, "standard" syntax.
415
SMADDL(regs_.R64(IRREG_LO), regs_.R(inst.src1), regs_.R(inst.src2), regs_.R64(IRREG_LO));
416
break;
417
418
case IROp::MaddU:
419
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
420
// Accumulator is at the end, "standard" syntax.
421
UMADDL(regs_.R64(IRREG_LO), regs_.R(inst.src1), regs_.R(inst.src2), regs_.R64(IRREG_LO));
422
break;
423
424
case IROp::Msub:
425
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
426
// Accumulator is at the end, "standard" syntax.
427
SMSUBL(regs_.R64(IRREG_LO), regs_.R(inst.src1), regs_.R(inst.src2), regs_.R64(IRREG_LO));
428
break;
429
430
case IROp::MsubU:
431
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
432
// Accumulator is at the end, "standard" syntax.
433
UMSUBL(regs_.R64(IRREG_LO), regs_.R(inst.src1), regs_.R(inst.src2), regs_.R64(IRREG_LO));
434
break;
435
436
default:
437
INVALIDOP;
438
break;
439
}
440
}
441
442
void Arm64JitBackend::CompIR_Shift(IRInst inst) {
443
CONDITIONAL_DISABLE;
444
445
switch (inst.op) {
446
case IROp::Shl:
447
regs_.Map(inst);
448
LSLV(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
449
break;
450
451
case IROp::Shr:
452
regs_.Map(inst);
453
LSRV(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
454
break;
455
456
case IROp::Sar:
457
regs_.Map(inst);
458
ASRV(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
459
break;
460
461
case IROp::Ror:
462
regs_.Map(inst);
463
RORV(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
464
break;
465
466
case IROp::ShlImm:
467
// Shouldn't happen, but let's be safe of any passes that modify the ops.
468
if (inst.src2 >= 32) {
469
regs_.SetGPRImm(inst.dest, 0);
470
} else if (inst.src2 == 0) {
471
if (inst.dest != inst.src1) {
472
regs_.Map(inst);
473
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
474
}
475
} else {
476
regs_.Map(inst);
477
MOV(regs_.R(inst.dest), regs_.R(inst.src1), ArithOption(regs_.R(inst.src1), ST_LSL, inst.src2));
478
}
479
break;
480
481
case IROp::ShrImm:
482
// Shouldn't happen, but let's be safe of any passes that modify the ops.
483
if (inst.src2 >= 32) {
484
regs_.SetGPRImm(inst.dest, 0);
485
} else if (inst.src2 == 0) {
486
if (inst.dest != inst.src1) {
487
regs_.Map(inst);
488
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
489
}
490
} else {
491
regs_.Map(inst);
492
MOV(regs_.R(inst.dest), regs_.R(inst.src1), ArithOption(regs_.R(inst.src1), ST_LSR, inst.src2));
493
}
494
break;
495
496
case IROp::SarImm:
497
// Shouldn't happen, but let's be safe of any passes that modify the ops.
498
if (inst.src2 >= 32) {
499
regs_.Map(inst);
500
MOV(regs_.R(inst.dest), regs_.R(inst.src1), ArithOption(regs_.R(inst.src1), ST_ASR, 31));
501
} else if (inst.src2 == 0) {
502
if (inst.dest != inst.src1) {
503
regs_.Map(inst);
504
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
505
}
506
} else {
507
regs_.Map(inst);
508
MOV(regs_.R(inst.dest), regs_.R(inst.src1), ArithOption(regs_.R(inst.src1), ST_ASR, inst.src2));
509
}
510
break;
511
512
case IROp::RorImm:
513
if (inst.src2 == 0) {
514
if (inst.dest != inst.src1) {
515
regs_.Map(inst);
516
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
517
}
518
} else {
519
regs_.Map(inst);
520
MOV(regs_.R(inst.dest), regs_.R(inst.src1), ArithOption(regs_.R(inst.src1), ST_ROR, inst.src2 & 31));
521
}
522
break;
523
524
default:
525
INVALIDOP;
526
break;
527
}
528
}
529
530
} // namespace MIPSComp
531
532
#endif
533
534