/
JitArm64_RegCache.h
357 lines (282 loc) · 11.5 KB
/
JitArm64_RegCache.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// Copyright 2014 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <memory>
#include <type_traits>
#include <vector>
#include "Common/Arm64Emitter.h"
#include "Common/CommonTypes.h"
#include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/PPCAnalyst.h"
#include "Core/PowerPC/PowerPC.h"
class JitArm64;
// Dedicated host registers
// memory base register
constexpr Arm64Gen::ARM64Reg MEM_REG = Arm64Gen::ARM64Reg::X28;
// ppcState pointer
constexpr Arm64Gen::ARM64Reg PPC_REG = Arm64Gen::ARM64Reg::X29;
// PC register when calling the dispatcher
constexpr Arm64Gen::ARM64Reg DISPATCHER_PC = Arm64Gen::ARM64Reg::W26;
#define PPCSTATE_OFF(elem) (offsetof(PowerPC::PowerPCState, elem))
#define PPCSTATE_OFF_ARRAY(elem, i) \
(offsetof(PowerPC::PowerPCState, elem[0]) + sizeof(PowerPC::PowerPCState::elem[0]) * (i))
#define PPCSTATE_OFF_GPR(i) PPCSTATE_OFF_ARRAY(gpr, i)
#define PPCSTATE_OFF_CR(i) PPCSTATE_OFF_ARRAY(cr.fields, i)
#define PPCSTATE_OFF_SR(i) PPCSTATE_OFF_ARRAY(sr, i)
#define PPCSTATE_OFF_SPR(i) PPCSTATE_OFF_ARRAY(spr, i)
static_assert(std::is_same_v<decltype(PowerPC::PowerPCState::ps[0]), PowerPC::PairedSingle&>);
#define PPCSTATE_OFF_PS0(i) (PPCSTATE_OFF_ARRAY(ps, i) + offsetof(PowerPC::PairedSingle, ps0))
#define PPCSTATE_OFF_PS1(i) (PPCSTATE_OFF_ARRAY(ps, i) + offsetof(PowerPC::PairedSingle, ps1))
// Some asserts to make sure we will be able to load everything
static_assert(PPCSTATE_OFF_SPR(1023) <= 16380, "LDR(32bit) can't reach the last SPR");
static_assert((PPCSTATE_OFF_PS0(0) % 8) == 0, "LDR(64bit VFP) requires FPRs to be 8 byte aligned");
static_assert(PPCSTATE_OFF(xer_ca) < 4096, "STRB can't store xer_ca!");
static_assert(PPCSTATE_OFF(xer_so_ov) < 4096, "STRB can't store xer_so_ov!");
enum class RegType
{
NotLoaded,
Discarded, // Reg is not loaded because we know it won't be read before the next write
Register, // Reg type is register
Immediate, // Reg is really a IMM
LowerPair, // Only the lower pair of a paired register
Duplicated, // The lower reg is the same as the upper one (physical upper doesn't actually have
// the duplicated value)
Single, // Both registers are loaded as single
LowerPairSingle, // Only the lower pair of a paired register, as single
DuplicatedSingle, // The lower one contains both registers, as single
};
enum class FlushMode
{
// Flushes all registers, no exceptions
All,
// Flushes registers in a conditional branch
// Doesn't wipe the state of the registers from the cache
MaintainState,
};
class OpArg
{
public:
OpArg() = default;
RegType GetType() const { return m_type; }
Arm64Gen::ARM64Reg GetReg() const { return m_reg; }
u32 GetImm() const { return m_value; }
void Load(Arm64Gen::ARM64Reg reg, RegType type = RegType::Register)
{
m_type = type;
m_reg = reg;
}
void LoadToImm(u32 imm)
{
m_type = RegType::Immediate;
m_value = imm;
m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
}
void Discard()
{
// Invalidate any previous information
m_type = RegType::Discarded;
m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
// Arbitrarily large value that won't roll over on a lot of increments
m_last_used = 0xFFFF;
}
void Flush()
{
// Invalidate any previous information
m_type = RegType::NotLoaded;
m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
// Arbitrarily large value that won't roll over on a lot of increments
m_last_used = 0xFFFF;
}
u32 GetLastUsed() const { return m_last_used; }
void ResetLastUsed() { m_last_used = 0; }
void IncrementLastUsed() { ++m_last_used; }
void SetDirty(bool dirty) { m_dirty = dirty; }
bool IsDirty() const { return m_dirty; }
private:
// For REG_REG
RegType m_type = RegType::NotLoaded; // store type
Arm64Gen::ARM64Reg m_reg = Arm64Gen::ARM64Reg::INVALID_REG; // host register we are in
// For REG_IMM
u32 m_value = 0; // IMM value
u32 m_last_used = 0;
bool m_dirty = false;
};
class HostReg
{
public:
HostReg() = default;
HostReg(Arm64Gen::ARM64Reg reg) : m_reg(reg) {}
bool IsLocked() const { return m_locked; }
void Lock() { m_locked = true; }
void Unlock() { m_locked = false; }
Arm64Gen::ARM64Reg GetReg() const { return m_reg; }
bool operator==(Arm64Gen::ARM64Reg reg) const { return reg == m_reg; }
bool operator!=(Arm64Gen::ARM64Reg reg) const { return !operator==(reg); }
private:
Arm64Gen::ARM64Reg m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
bool m_locked = false;
};
class Arm64RegCache
{
public:
explicit Arm64RegCache(size_t guest_reg_count) : m_guest_registers(guest_reg_count) {}
virtual ~Arm64RegCache() = default;
void Init(JitArm64* jit);
virtual void Start(PPCAnalyst::BlockRegStats& stats) {}
void DiscardRegisters(BitSet32 regs);
void ResetRegisters(BitSet32 regs);
// Flushes the register cache in different ways depending on the mode.
// A temporary register must be supplied when flushing GPRs with FlushMode::MaintainState,
// but in other cases it can be set to ARM64Reg::INVALID_REG when convenient for the caller.
virtual void Flush(FlushMode mode, Arm64Gen::ARM64Reg tmp_reg) = 0;
virtual BitSet32 GetCallerSavedUsed() const = 0;
// Returns a temporary register for use
// Requires unlocking after done
Arm64Gen::ARM64Reg GetReg();
void UpdateLastUsed(BitSet32 regs_used);
// Get available host registers
u32 GetUnlockedRegisterCount() const;
// Locks a register so a cache cannot use it
// Useful for function calls
template <typename T = Arm64Gen::ARM64Reg, typename... Args>
void Lock(Args... args)
{
for (T reg : {args...})
{
FlushByHost(reg);
LockRegister(reg);
}
}
// Unlocks a locked register
// Unlocks registers locked with both GetReg and LockRegister
template <typename T = Arm64Gen::ARM64Reg, typename... Args>
void Unlock(Args... args)
{
for (T reg : {args...})
{
FlushByHost(reg);
UnlockRegister(reg);
}
}
protected:
// Get the order of the host registers
virtual void GetAllocationOrder() = 0;
// Flushes the most stale register
void FlushMostStaleRegister();
// Lock a register
void LockRegister(Arm64Gen::ARM64Reg host_reg);
// Unlock a register
void UnlockRegister(Arm64Gen::ARM64Reg host_reg);
// Flushes a guest register by host provided
virtual void FlushByHost(Arm64Gen::ARM64Reg host_reg,
Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG) = 0;
void DiscardRegister(size_t preg);
virtual void FlushRegister(size_t preg, bool maintain_state, Arm64Gen::ARM64Reg tmp_reg) = 0;
void IncrementAllUsed()
{
for (auto& reg : m_guest_registers)
reg.IncrementLastUsed();
}
JitArm64* m_jit = nullptr;
// Code emitter
Arm64Gen::ARM64XEmitter* m_emit = nullptr;
// Float emitter
std::unique_ptr<Arm64Gen::ARM64FloatEmitter> m_float_emit;
// Host side registers that hold the host registers in order of use
std::vector<HostReg> m_host_registers;
// Our guest GPRs
// PowerPC has 32 GPRs and 8 CRs
// PowerPC also has 32 paired FPRs
std::vector<OpArg> m_guest_registers;
// Register stats for the current block
PPCAnalyst::BlockRegStats* m_reg_stats = nullptr;
};
class Arm64GPRCache : public Arm64RegCache
{
public:
Arm64GPRCache();
void Start(PPCAnalyst::BlockRegStats& stats) override;
// Flushes the register cache in different ways depending on the mode.
// A temporary register must be supplied when flushing GPRs with FlushMode::MaintainState,
// but in other cases it can be set to ARM64Reg::INVALID_REG when convenient for the caller.
void Flush(FlushMode mode, Arm64Gen::ARM64Reg tmp_reg) override;
// Returns a guest GPR inside of a host register
// Will dump an immediate to the host register as well
Arm64Gen::ARM64Reg R(size_t preg) { return R(GetGuestGPR(preg)); }
// Returns a guest CR inside of a host register
Arm64Gen::ARM64Reg CR(size_t preg) { return R(GetGuestCR(preg)); }
// Set a register to an immediate, only valid for guest GPRs
void SetImmediate(size_t preg, u32 imm) { SetImmediate(GetGuestGPR(preg), imm); }
// Returns if a register is set as an immediate, only valid for guest GPRs
bool IsImm(size_t preg) const { return GetGuestGPROpArg(preg).GetType() == RegType::Immediate; }
// Gets the immediate that a register is set to, only valid for guest GPRs
u32 GetImm(size_t preg) const { return GetGuestGPROpArg(preg).GetImm(); }
// Binds a guest GPR to a host register, optionally loading its value
void BindToRegister(size_t preg, bool do_load) { BindToRegister(GetGuestGPR(preg), do_load); }
// Binds a guest CR to a host register, optionally loading its value
void BindCRToRegister(size_t preg, bool do_load) { BindToRegister(GetGuestCR(preg), do_load); }
BitSet32 GetCallerSavedUsed() const override;
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
{
FlushRegisters(regs, false, tmp_reg);
}
void StoreCRRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
{
FlushCRRegisters(regs, false, tmp_reg);
}
protected:
// Get the order of the host registers
void GetAllocationOrder() override;
// Flushes a guest register by host provided
void FlushByHost(Arm64Gen::ARM64Reg host_reg,
Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG) override;
void FlushRegister(size_t index, bool maintain_state, Arm64Gen::ARM64Reg tmp_reg) override;
private:
bool IsCalleeSaved(Arm64Gen::ARM64Reg reg) const;
struct GuestRegInfo
{
size_t bitsize;
size_t ppc_offset;
OpArg& reg;
};
const OpArg& GetGuestGPROpArg(size_t preg) const;
GuestRegInfo GetGuestGPR(size_t preg);
GuestRegInfo GetGuestCR(size_t preg);
GuestRegInfo GetGuestByIndex(size_t index);
Arm64Gen::ARM64Reg R(const GuestRegInfo& guest_reg);
void SetImmediate(const GuestRegInfo& guest_reg, u32 imm);
void BindToRegister(const GuestRegInfo& guest_reg, bool do_load);
void FlushRegisters(BitSet32 regs, bool maintain_state, Arm64Gen::ARM64Reg tmp_reg);
void FlushCRRegisters(BitSet32 regs, bool maintain_state, Arm64Gen::ARM64Reg tmp_reg);
};
class Arm64FPRCache : public Arm64RegCache
{
public:
Arm64FPRCache();
// Flushes the register cache in different ways depending on the mode.
// The temporary register can be set to ARM64Reg::INVALID_REG when convenient for the caller.
void Flush(FlushMode mode, Arm64Gen::ARM64Reg tmp_reg) override;
// Returns a guest register inside of a host register
// Will dump an immediate to the host register as well
Arm64Gen::ARM64Reg R(size_t preg, RegType type);
Arm64Gen::ARM64Reg RW(size_t preg, RegType type);
BitSet32 GetCallerSavedUsed() const override;
bool IsSingle(size_t preg, bool lower_only = false) const;
void FixSinglePrecision(size_t preg);
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
{
FlushRegisters(regs, false, tmp_reg);
}
protected:
// Get the order of the host registers
void GetAllocationOrder() override;
// Flushes a guest register by host provided
void FlushByHost(Arm64Gen::ARM64Reg host_reg,
Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG) override;
void FlushRegister(size_t preg, bool maintain_state, Arm64Gen::ARM64Reg tmp_reg) override;
private:
bool IsCalleeSaved(Arm64Gen::ARM64Reg reg) const;
bool IsTopHalfUsed(Arm64Gen::ARM64Reg reg) const;
void FlushRegisters(BitSet32 regs, bool maintain_state, Arm64Gen::ARM64Reg tmp_reg);
};