/
PPUThread.h
291 lines (244 loc) · 7.23 KB
/
PPUThread.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
#pragma once
#include "../CPU/CPUThread.h"
#include "../Memory/vm.h"
#include "Utilities/lockless.h"
enum class ppu_cmd : u32
{
null,
opcode, // Execute PPU instruction from arg
set_gpr, // Set gpr[arg] (+1 cmd)
set_args, // Set general-purpose args (+arg cmd)
lle_call, // Load addr and rtoc at *arg or *gpr[arg] and execute
hle_call, // Execute function by index (arg)
ptr_call, // Execute function by pointer
initialize, // ppu_initialize()
sleep,
reset_stack, // resets stack address
};
// Formatting helper
enum class ppu_syscall_code : u64
{
};
// ppu_thread constructor argument
struct ppu_thread_params
{
vm::addr_t stack_addr;
u32 stack_size;
u32 tls_addr;
u32 entry;
u64 arg0;
u64 arg1;
};
class ppu_thread : public cpu_thread
{
public:
static const u32 id_base = 0x01000000; // TODO (used to determine thread type)
static const u32 id_step = 1;
static const u32 id_count = 2048;
static void on_cleanup(named_thread<ppu_thread>*);
virtual std::string get_name() const override;
virtual std::string dump() const override;
virtual void cpu_task() override final;
virtual void cpu_sleep() override;
virtual void cpu_mem() override;
virtual void cpu_unmem() override;
virtual ~ppu_thread() override;
ppu_thread(const ppu_thread_params&, std::string_view name, u32 prio, int detached = 0);
u64 gpr[32] = {}; // General-Purpose Registers
f64 fpr[32] = {}; // Floating Point Registers
v128 vr[32] = {}; // Vector Registers
struct cr_bits
{
alignas(16) u8 bits[32];
u8& operator [](std::size_t i)
{
return bits[i];
}
// Pack CR bits
u32 pack() const
{
u32 result{};
for (u32 bit : bits)
{
result <<= 1;
result |= bit;
}
return result;
}
// Unpack CR bits
void unpack(u32 value)
{
for (u8& b : bits)
{
b = value & 0x1;
value >>= 1;
}
}
};
cr_bits cr{}; // Condition Registers (unpacked)
// Floating-Point Status and Control Register (unpacked)
union
{
struct
{
// TODO
bool _start[16];
bool fl; // FPCC.FL
bool fg; // FPCC.FG
bool fe; // FPCC.FE
bool fu; // FPCC.FU
bool _end[12];
};
cr_bits bits;
}
fpscr{};
u64 lr{}; // Link Register
u64 ctr{}; // Counter Register
u32 vrsave{0xffffffff}; // VR Save Register
u32 cia{}; // Current Instruction Address
// Fixed-Point Exception Register (abstract representation)
struct
{
bool so{}; // Summary Overflow
bool ov{}; // Overflow
bool ca{}; // Carry
u8 cnt{}; // 0..6
}
xer;
/*
Saturation. A sticky status bit indicating that some field in a saturating instruction saturated since the last
time SAT was cleared. In other words when SAT = '1' it remains set to '1' until it is cleared to '0' by an
mtvscr instruction.
1 The vector saturate instruction implicitly sets when saturation has occurred on the results one of
the vector instructions having saturate in its name:
Move To VSCR (mtvscr)
Vector Add Integer with Saturation (vaddubs, vadduhs, vadduws, vaddsbs, vaddshs,
vaddsws)
Vector Subtract Integer with Saturation (vsububs, vsubuhs, vsubuws, vsubsbs, vsubshs,
vsubsws)
Vector Multiply-Add Integer with Saturation (vmhaddshs, vmhraddshs)
Vector Multiply-Sum with Saturation (vmsumuhs, vmsumshs, vsumsws)
Vector Sum-Across with Saturation (vsumsws, vsum2sws, vsum4sbs, vsum4shs,
vsum4ubs)
Vector Pack with Saturation (vpkuhus, vpkuwus, vpkshus, vpkswus, vpkshss, vpkswss)
Vector Convert to Fixed-Point with Saturation (vctuxs, vctsxs)
0 Indicates no saturation occurred; mtvscr can explicitly clear this bit.
*/
bool sat{};
/*
Non-Java. A mode control bit that determines whether vector floating-point operations will be performed
in a Java-IEEE-C9X-compliant mode or a possibly faster non-Java/non-IEEE mode.
0 The Java-IEEE-C9X-compliant mode is selected. Denormalized values are handled as specified
by Java, IEEE, and C9X standard.
1 The non-Java/non-IEEE-compliant mode is selected. If an element in a source vector register
contains a denormalized value, the value '0' is used instead. If an instruction causes an underflow
exception, the corresponding element in the target vr is cleared to '0'. In both cases, the '0'
has the same sign as the denormalized or underflowing value.
*/
bool nj = false;
u32 raddr{0}; // Reservation addr
u64 rtime{0};
u64 rdata{0}; // Reservation data
atomic_t<u32> prio{0}; // Thread priority (0..3071)
const u32 stack_size; // Stack size
const u32 stack_addr; // Stack address
atomic_t<u32> joiner{~0u}; // Joining thread (-1 if detached)
lf_fifo<atomic_t<cmd64>, 127> cmd_queue; // Command queue for asynchronous operations.
void cmd_push(cmd64);
void cmd_list(std::initializer_list<cmd64>);
void cmd_pop(u32 = 0);
cmd64 cmd_wait(); // Empty command means caller must return, like true from cpu_thread::check_status().
cmd64 cmd_get(u32 index) { return cmd_queue[cmd_queue.peek() + index].load(); }
u64 start_time{0}; // Sleep start timepoint
const char* last_function{}; // Last function name for diagnosis, optimized for speed.
lf_value<std::string> ppu_name; // Thread name
be_t<u64>* get_stack_arg(s32 i, u64 align = alignof(u64));
void exec_task();
void fast_call(u32 addr, u32 rtoc);
static u32 stack_push(u32 size, u32 align_v);
static void stack_pop_verbose(u32 addr, u32 size) noexcept;
};
template<typename T, typename = void>
struct ppu_gpr_cast_impl
{
static_assert(!sizeof(T), "Invalid type for ppu_gpr_cast<>");
};
template<typename T>
struct ppu_gpr_cast_impl<T, std::enable_if_t<std::is_integral<T>::value || std::is_enum<T>::value>>
{
static_assert(sizeof(T) <= 8, "Too big integral type for ppu_gpr_cast<>()");
static_assert(std::is_same<std::decay_t<T>, bool>::value == false, "bool type is deprecated in ppu_gpr_cast<>(), use b8 instead");
static inline u64 to(const T& value)
{
return static_cast<u64>(value);
}
static inline T from(const u64 reg)
{
return static_cast<T>(reg);
}
};
template<>
struct ppu_gpr_cast_impl<b8, void>
{
static inline u64 to(const b8& value)
{
return value;
}
static inline b8 from(const u64 reg)
{
return static_cast<u32>(reg) != 0;
}
};
template<>
struct ppu_gpr_cast_impl<error_code, void>
{
static inline u64 to(const error_code& code)
{
return code;
}
static inline error_code from(const u64 reg)
{
return not_an_error(reg);
}
};
template<typename T, typename AT>
struct ppu_gpr_cast_impl<vm::_ptr_base<T, AT>, void>
{
static inline u64 to(const vm::_ptr_base<T, AT>& value)
{
return ppu_gpr_cast_impl<AT>::to(value.addr());
}
static inline vm::_ptr_base<T, AT> from(const u64 reg)
{
return vm::cast(ppu_gpr_cast_impl<AT>::from(reg));
}
};
template<typename T, typename AT>
struct ppu_gpr_cast_impl<vm::_ref_base<T, AT>, void>
{
static inline u64 to(const vm::_ref_base<T, AT>& value)
{
return ppu_gpr_cast_impl<AT>::to(value.addr());
}
static inline vm::_ref_base<T, AT> from(const u64 reg)
{
return vm::cast(ppu_gpr_cast_impl<AT>::from(reg));
}
};
template <>
struct ppu_gpr_cast_impl<vm::null_t, void>
{
static inline u64 to(const vm::null_t& value)
{
return 0;
}
static inline vm::null_t from(const u64 reg)
{
return vm::null;
}
};
template<typename To = u64, typename From>
inline To ppu_gpr_cast(const From& value)
{
return ppu_gpr_cast_impl<To>::from(ppu_gpr_cast_impl<From>::to(value));
}