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/RegCache.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(X86) || PPSSPP_ARCH(AMD64)
20
21
#include <cstring>
22
23
#include "Common/x64Emitter.h"
24
#include "Core/Reporting.h"
25
#include "Core/MIPS/MIPS.h"
26
#include "Core/MIPS/MIPSTables.h"
27
#include "Core/MIPS/MIPSAnalyst.h"
28
#include "Core/MIPS/x86/Jit.h"
29
#include "Core/MIPS/x86/RegCache.h"
30
31
using namespace Gen;
32
using namespace X64JitConstants;
33
34
static const X64Reg allocationOrder[] = {
35
// R12, when used as base register, for example in a LEA, can generate bad code! Need to look into this.
36
// On x64, RCX and RDX are the first args. CallProtectedFunction() assumes they're not regcached.
37
#if PPSSPP_ARCH(AMD64)
38
#ifdef _WIN32
39
RSI, RDI, R8, R9, R10, R11, R12, R13,
40
#else
41
RBP, R8, R9, R10, R11, R12, R13,
42
#endif
43
#elif PPSSPP_ARCH(X86)
44
ESI, EDI, EDX, ECX, EBX,
45
#endif
46
};
47
48
#if PPSSPP_ARCH(AMD64)
49
static X64Reg allocationOrderR15[ARRAY_SIZE(allocationOrder) + 1] = {INVALID_REG};
50
#endif
51
52
void GPRRegCache::FlushBeforeCall() {
53
// TODO: Only flush the non-preserved-by-callee registers.
54
Flush();
55
}
56
57
GPRRegCache::GPRRegCache() {
58
}
59
60
void GPRRegCache::Start(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo, MIPSAnalyst::AnalysisResults &stats) {
61
#if PPSSPP_ARCH(AMD64)
62
if (allocationOrderR15[0] == INVALID_REG) {
63
memcpy(allocationOrderR15, allocationOrder, sizeof(allocationOrder));
64
allocationOrderR15[ARRAY_SIZE(allocationOrderR15) - 1] = R15;
65
}
66
#endif
67
68
mips_ = mipsState;
69
for (int i = 0; i < NUM_X_REGS; i++) {
70
xregs[i].free = true;
71
xregs[i].dirty = false;
72
xregs[i].allocLocked = false;
73
}
74
memset(regs, 0, sizeof(regs));
75
OpArg base = GetDefaultLocation(MIPS_REG_ZERO);
76
for (int i = 0; i < 32; i++) {
77
regs[i].location = base;
78
base.IncreaseOffset(sizeof(u32));
79
}
80
for (int i = 32; i < NUM_MIPS_GPRS; i++) {
81
regs[i].location = GetDefaultLocation(MIPSGPReg(i));
82
}
83
SetImm(MIPS_REG_ZERO, 0);
84
85
// todo: sort to find the most popular regs
86
/*
87
int maxPreload = 2;
88
for (int i = 0; i < NUM_MIPS_GPRS; i++)
89
{
90
if (stats.numReads[i] > 2 || stats.numWrites[i] >= 2)
91
{
92
LoadToX64(i, true, false); //stats.firstRead[i] <= stats.firstWrite[i], false);
93
maxPreload--;
94
if (!maxPreload)
95
break;
96
}
97
}*/
98
//Find top regs - preload them (load bursts ain't bad)
99
//But only preload IF written OR reads >= 3
100
101
js_ = js;
102
jo_ = jo;
103
}
104
105
106
// these are MIPS reg indices
107
void GPRRegCache::Lock(MIPSGPReg p1, MIPSGPReg p2, MIPSGPReg p3, MIPSGPReg p4) {
108
regs[p1].locked = true;
109
if (p2 != MIPS_REG_INVALID) regs[p2].locked = true;
110
if (p3 != MIPS_REG_INVALID) regs[p3].locked = true;
111
if (p4 != MIPS_REG_INVALID) regs[p4].locked = true;
112
}
113
114
// these are x64 reg indices
115
void GPRRegCache::LockX(int x1, int x2, int x3, int x4) {
116
_assert_msg_(!xregs[x1].allocLocked, "RegCache: x %d already locked!", x1);
117
xregs[x1].allocLocked = true;
118
if (x2 != 0xFF) xregs[x2].allocLocked = true;
119
if (x3 != 0xFF) xregs[x3].allocLocked = true;
120
if (x4 != 0xFF) xregs[x4].allocLocked = true;
121
}
122
123
void GPRRegCache::UnlockAll() {
124
for (int i = 0; i < NUM_MIPS_GPRS; i++)
125
regs[i].locked = false;
126
// In case it was stored, discard it now.
127
SetImm(MIPS_REG_ZERO, 0);
128
}
129
130
void GPRRegCache::UnlockAllX() {
131
for (int i = 0; i < NUM_X_REGS; i++)
132
xregs[i].allocLocked = false;
133
}
134
135
X64Reg GPRRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) {
136
int allocCount;
137
const X64Reg *allocOrder = GetAllocationOrder(allocCount);
138
139
static const int UNUSED_LOOKAHEAD_OPS = 30;
140
141
*clobbered = false;
142
for (int i = 0; i < allocCount; i++) {
143
X64Reg reg = allocOrder[i];
144
if (xregs[reg].allocLocked)
145
continue;
146
if (xregs[reg].mipsReg != MIPS_REG_INVALID && regs[xregs[reg].mipsReg].locked)
147
continue;
148
149
// Awesome, a clobbered reg. Let's use it.
150
if (MIPSAnalyst::IsRegisterClobbered(xregs[reg].mipsReg, js_->compilerPC, UNUSED_LOOKAHEAD_OPS)) {
151
*clobbered = true;
152
return reg;
153
}
154
155
// Not awesome. A used reg. Let's try to avoid spilling.
156
if (unusedOnly && MIPSAnalyst::IsRegisterUsed(xregs[reg].mipsReg, js_->compilerPC, UNUSED_LOOKAHEAD_OPS)) {
157
continue;
158
}
159
160
return reg;
161
}
162
163
return INVALID_REG;
164
}
165
166
X64Reg GPRRegCache::GetFreeXReg()
167
{
168
int aCount;
169
const X64Reg *aOrder = GetAllocationOrder(aCount);
170
for (int i = 0; i < aCount; i++)
171
{
172
X64Reg xr = aOrder[i];
173
if (!xregs[xr].allocLocked && xregs[xr].free)
174
{
175
return xr;
176
}
177
}
178
179
//Okay, not found :( Force grab one
180
bool clobbered;
181
X64Reg bestToSpill = FindBestToSpill(true, &clobbered);
182
if (bestToSpill == INVALID_REG) {
183
bestToSpill = FindBestToSpill(false, &clobbered);
184
}
185
186
if (bestToSpill != INVALID_REG) {
187
// TODO: Broken somehow in Dante's Inferno, but most games work. Bad flags in MIPSTables somewhere?
188
if (clobbered) {
189
DiscardRegContentsIfCached(xregs[bestToSpill].mipsReg);
190
} else {
191
StoreFromRegister(xregs[bestToSpill].mipsReg);
192
}
193
return bestToSpill;
194
}
195
196
// Still no dice? Give up.
197
_assert_msg_(false, "Regcache ran out of regs");
198
return (X64Reg)-1;
199
}
200
201
void GPRRegCache::FlushR(X64Reg reg)
202
{
203
if (reg >= NUM_X_REGS) {
204
_assert_msg_(false, "Flushing non existent reg");
205
} else if (!xregs[reg].free) {
206
StoreFromRegister(xregs[reg].mipsReg);
207
}
208
}
209
210
void GPRRegCache::FlushRemap(MIPSGPReg oldreg, MIPSGPReg newreg) {
211
OpArg oldLocation = regs[oldreg].location;
212
_assert_msg_(oldLocation.IsSimpleReg(), "FlushRemap: Must already be in an x86 register");
213
214
X64Reg xr = oldLocation.GetSimpleReg();
215
216
if (oldreg == newreg) {
217
xregs[xr].dirty = true;
218
return;
219
}
220
221
StoreFromRegister(oldreg);
222
223
// Now, if newreg already was mapped somewhere, get rid of that.
224
DiscardRegContentsIfCached(newreg);
225
226
// Now, take over the old register.
227
regs[newreg].location = oldLocation;
228
regs[newreg].away = true;
229
regs[newreg].locked = true;
230
xregs[xr].mipsReg = newreg;
231
xregs[xr].dirty = true;
232
xregs[xr].free = false;
233
}
234
235
int GPRRegCache::SanityCheck() const {
236
for (int i = 0; i < NUM_MIPS_GPRS; i++) {
237
const MIPSGPReg r = MIPSGPReg(i);
238
if (regs[i].away) {
239
if (regs[i].location.IsSimpleReg()) {
240
Gen::X64Reg simple = regs[i].location.GetSimpleReg();
241
if (xregs[simple].allocLocked)
242
return 1;
243
if (xregs[simple].mipsReg != r)
244
return 2;
245
}
246
else if (regs[i].location.IsImm())
247
return 3;
248
}
249
}
250
return 0;
251
}
252
253
void GPRRegCache::DiscardRegContentsIfCached(MIPSGPReg preg) {
254
if (regs[preg].away && regs[preg].location.IsSimpleReg()) {
255
X64Reg xr = regs[preg].location.GetSimpleReg();
256
xregs[xr].free = true;
257
xregs[xr].dirty = false;
258
xregs[xr].mipsReg = MIPS_REG_INVALID;
259
regs[preg].away = false;
260
if (preg == MIPS_REG_ZERO) {
261
regs[preg].location = Imm32(0);
262
} else {
263
regs[preg].location = GetDefaultLocation(preg);
264
}
265
}
266
}
267
268
void GPRRegCache::DiscardR(MIPSGPReg preg) {
269
if (regs[preg].away) {
270
if (regs[preg].location.IsSimpleReg()) {
271
DiscardRegContentsIfCached(preg);
272
} else {
273
regs[preg].away = false;
274
if (preg == MIPS_REG_ZERO) {
275
regs[preg].location = Imm32(0);
276
} else {
277
regs[preg].location = GetDefaultLocation(preg);
278
}
279
}
280
}
281
}
282
283
284
void GPRRegCache::SetImm(MIPSGPReg preg, u32 immValue) {
285
// ZERO is always zero. Let's just make sure.
286
if (preg == MIPS_REG_ZERO)
287
immValue = 0;
288
289
DiscardRegContentsIfCached(preg);
290
regs[preg].away = true;
291
regs[preg].location = Imm32(immValue);
292
}
293
294
bool GPRRegCache::IsImm(MIPSGPReg preg) const {
295
// Note that ZERO is generally always imm.
296
return regs[preg].location.IsImm();
297
}
298
299
u32 GPRRegCache::GetImm(MIPSGPReg preg) const {
300
_dbg_assert_msg_(IsImm(preg), "Reg %d must be an immediate.", preg);
301
// Always 0 for ZERO.
302
if (preg == MIPS_REG_ZERO)
303
return 0;
304
return regs[preg].location.GetImmValue();
305
}
306
307
const X64Reg *GPRRegCache::GetAllocationOrder(int &count) {
308
#if PPSSPP_ARCH(AMD64)
309
if (!jo_->reserveR15ForAsm) {
310
count = ARRAY_SIZE(allocationOrderR15);
311
return allocationOrderR15;
312
}
313
#endif
314
count = ARRAY_SIZE(allocationOrder);
315
return allocationOrder;
316
}
317
318
319
OpArg GPRRegCache::GetDefaultLocation(MIPSGPReg reg) const {
320
if (reg < 32) {
321
return MDisp(CTXREG, -128 + reg * 4);
322
}
323
switch (reg) {
324
case MIPS_REG_HI:
325
return MIPSSTATE_VAR(hi);
326
case MIPS_REG_LO:
327
return MIPSSTATE_VAR(lo);
328
case MIPS_REG_FPCOND:
329
return MIPSSTATE_VAR(fpcond);
330
case MIPS_REG_VFPUCC:
331
return MIPSSTATE_VAR(vfpuCtrl[VFPU_CTRL_CC]);
332
default:
333
ERROR_LOG_REPORT(Log::JIT, "Bad mips register %d", reg);
334
return MIPSSTATE_VAR(r[0]);
335
}
336
}
337
338
339
void GPRRegCache::KillImmediate(MIPSGPReg preg, bool doLoad, bool makeDirty) {
340
if (regs[preg].away) {
341
if (regs[preg].location.IsImm())
342
MapReg(preg, doLoad, makeDirty);
343
else if (regs[preg].location.IsSimpleReg())
344
xregs[RX(preg)].dirty |= makeDirty;
345
}
346
}
347
348
void GPRRegCache::MapReg(MIPSGPReg i, bool doLoad, bool makeDirty) {
349
if (!regs[i].away && regs[i].location.IsImm()) {
350
_assert_msg_(false, "Bad immediate");
351
}
352
if (!regs[i].away || (regs[i].away && regs[i].location.IsImm())) {
353
X64Reg xr = GetFreeXReg();
354
_assert_msg_(!xregs[xr].dirty, "Xreg already dirty");
355
_assert_msg_(!xregs[xr].allocLocked, "GetFreeXReg returned locked register");
356
xregs[xr].free = false;
357
xregs[xr].mipsReg = i;
358
xregs[xr].dirty = makeDirty || regs[i].location.IsImm();
359
OpArg newloc = ::Gen::R(xr);
360
if (doLoad) {
361
// Force ZERO to be 0.
362
if (i == MIPS_REG_ZERO)
363
emit->MOV(32, newloc, Imm32(0));
364
else
365
emit->MOV(32, newloc, regs[i].location);
366
}
367
for (int j = 0; j < 32; j++) {
368
if (i != MIPSGPReg(j) && regs[j].location.IsSimpleReg(xr)) {
369
_assert_msg_(false, "BindToRegister: Strange condition");
370
}
371
}
372
regs[i].away = true;
373
regs[i].location = newloc;
374
} else {
375
// reg location must be simplereg; memory locations
376
// and immediates are taken care of above.
377
xregs[RX(i)].dirty |= makeDirty;
378
}
379
380
_assert_msg_(!xregs[RX(i)].allocLocked, "This reg should have been flushed (r%d)", i);
381
}
382
383
void GPRRegCache::StoreFromRegister(MIPSGPReg i) {
384
if (regs[i].away) {
385
bool doStore;
386
if (regs[i].location.IsSimpleReg()) {
387
X64Reg xr = RX(i);
388
xregs[xr].free = true;
389
xregs[xr].mipsReg = MIPS_REG_INVALID;
390
doStore = xregs[xr].dirty;
391
xregs[xr].dirty = false;
392
} else {
393
//must be immediate - do nothing
394
doStore = true;
395
}
396
OpArg newLoc = GetDefaultLocation(i);
397
// But never store to ZERO.
398
if (doStore && i != MIPS_REG_ZERO)
399
emit->MOV(32, newLoc, regs[i].location);
400
regs[i].location = newLoc;
401
regs[i].away = false;
402
}
403
}
404
405
void GPRRegCache::Flush() {
406
for (int i = 0; i < NUM_X_REGS; i++) {
407
_assert_msg_(!xregs[i].allocLocked, "Someone forgot to unlock X64 reg %d.", i);
408
}
409
SetImm(MIPS_REG_ZERO, 0);
410
for (int i = 1; i < NUM_MIPS_GPRS; i++) {
411
const MIPSGPReg r = MIPSGPReg(i);
412
_assert_msg_(!regs[i].locked, "Somebody forgot to unlock MIPS reg %d.", i);
413
if (regs[i].away) {
414
if (regs[i].location.IsSimpleReg()) {
415
X64Reg xr = RX(r);
416
StoreFromRegister(r);
417
xregs[xr].dirty = false;
418
}
419
else if (regs[i].location.IsImm()) {
420
StoreFromRegister(r);
421
} else {
422
_assert_msg_(false, "Jit64 - Flush unhandled case, reg %d PC: %08x", i, mips_->pc);
423
}
424
}
425
}
426
}
427
428
void GPRRegCache::GetState(GPRRegCacheState &state) const {
429
memcpy(state.regs, regs, sizeof(regs));
430
memcpy(state.xregs, xregs, sizeof(xregs));
431
}
432
433
void GPRRegCache::RestoreState(const GPRRegCacheState& state) {
434
memcpy(regs, state.regs, sizeof(regs));
435
memcpy(xregs, state.xregs, sizeof(xregs));
436
}
437
438
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
439
440