Merge branch 'master' into d3d12
This commit is contained in:
@@ -16,6 +16,7 @@ project("xenia-app")
|
||||
"imgui",
|
||||
"libavcodec",
|
||||
"libavutil",
|
||||
"mspack",
|
||||
"snappy",
|
||||
"spirv-tools",
|
||||
"volk",
|
||||
|
||||
@@ -42,6 +42,15 @@ class X64ThunkEmitter : public X64Emitter {
|
||||
HostToGuestThunk EmitHostToGuestThunk();
|
||||
GuestToHostThunk EmitGuestToHostThunk();
|
||||
ResolveFunctionThunk EmitResolveFunctionThunk();
|
||||
|
||||
private:
|
||||
// The following four functions provide save/load functionality for registers.
|
||||
// They assume at least StackLayout::THUNK_STACK_SIZE bytes have been
|
||||
// allocated on the stack.
|
||||
void EmitSaveVolatileRegs();
|
||||
void EmitLoadVolatileRegs();
|
||||
void EmitSaveNonvolatileRegs();
|
||||
void EmitLoadNonvolatileRegs();
|
||||
};
|
||||
|
||||
X64Backend::X64Backend() : Backend(), code_cache_(nullptr) {
|
||||
@@ -73,8 +82,6 @@ bool X64Backend::Initialize(Processor* processor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterSequences();
|
||||
|
||||
// Need movbe to do advanced LOAD/STORE tricks.
|
||||
if (FLAGS_enable_haswell_instructions) {
|
||||
machine_info_.supports_extended_load_store =
|
||||
@@ -406,6 +413,117 @@ HostToGuestThunk X64ThunkEmitter::EmitHostToGuestThunk() {
|
||||
mov(qword[rsp + 8 * 1], rcx);
|
||||
sub(rsp, stack_size);
|
||||
|
||||
// Save nonvolatile registers.
|
||||
EmitSaveNonvolatileRegs();
|
||||
|
||||
mov(rax, rcx);
|
||||
mov(rsi, rdx); // context
|
||||
mov(rcx, r8); // return address
|
||||
call(rax);
|
||||
|
||||
EmitLoadNonvolatileRegs();
|
||||
|
||||
add(rsp, stack_size);
|
||||
mov(rcx, qword[rsp + 8 * 1]);
|
||||
mov(rdx, qword[rsp + 8 * 2]);
|
||||
mov(r8, qword[rsp + 8 * 3]);
|
||||
ret();
|
||||
|
||||
void* fn = Emplace(stack_size);
|
||||
return (HostToGuestThunk)fn;
|
||||
}
|
||||
|
||||
GuestToHostThunk X64ThunkEmitter::EmitGuestToHostThunk() {
|
||||
// rcx = target function
|
||||
// rdx = arg0
|
||||
// r8 = arg1
|
||||
// r9 = arg2
|
||||
|
||||
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
||||
// rsp + 0 = return address
|
||||
sub(rsp, stack_size);
|
||||
|
||||
// Save off volatile registers.
|
||||
EmitSaveVolatileRegs();
|
||||
|
||||
mov(rax, rcx); // function
|
||||
mov(rcx, GetContextReg()); // context
|
||||
call(rax);
|
||||
|
||||
EmitLoadVolatileRegs();
|
||||
|
||||
add(rsp, stack_size);
|
||||
ret();
|
||||
|
||||
void* fn = Emplace(stack_size);
|
||||
return (GuestToHostThunk)fn;
|
||||
}
|
||||
|
||||
// X64Emitter handles actually resolving functions.
|
||||
extern "C" uint64_t ResolveFunction(void* raw_context, uint32_t target_address);
|
||||
|
||||
ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() {
|
||||
// ebx = target PPC address
|
||||
// rcx = context
|
||||
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
||||
|
||||
// rsp + 0 = return address
|
||||
sub(rsp, stack_size);
|
||||
|
||||
// Save volatile registers
|
||||
EmitSaveVolatileRegs();
|
||||
|
||||
mov(rcx, rsi); // context
|
||||
mov(rdx, rbx);
|
||||
mov(rax, uint64_t(&ResolveFunction));
|
||||
call(rax);
|
||||
|
||||
EmitLoadVolatileRegs();
|
||||
|
||||
add(rsp, stack_size);
|
||||
jmp(rax);
|
||||
|
||||
void* fn = Emplace(stack_size);
|
||||
return (ResolveFunctionThunk)fn;
|
||||
}
|
||||
|
||||
void X64ThunkEmitter::EmitSaveVolatileRegs() {
|
||||
// Save off volatile registers.
|
||||
// mov(qword[rsp + offsetof(StackLayout::Thunk, r[0])], rax);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[1])], rcx);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[2])], rdx);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[3])], r8);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[4])], r9);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[5])], r10);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[6])], r11);
|
||||
|
||||
// movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[0])], xmm0);
|
||||
movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[1])], xmm1);
|
||||
movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[2])], xmm2);
|
||||
movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[3])], xmm3);
|
||||
movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[4])], xmm4);
|
||||
movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[5])], xmm5);
|
||||
}
|
||||
|
||||
void X64ThunkEmitter::EmitLoadVolatileRegs() {
|
||||
// Load volatile registers from our stack frame.
|
||||
// movaps(xmm0, qword[rsp + offsetof(StackLayout::Thunk, xmm[0])]);
|
||||
movaps(xmm1, qword[rsp + offsetof(StackLayout::Thunk, xmm[1])]);
|
||||
movaps(xmm2, qword[rsp + offsetof(StackLayout::Thunk, xmm[2])]);
|
||||
movaps(xmm3, qword[rsp + offsetof(StackLayout::Thunk, xmm[3])]);
|
||||
movaps(xmm4, qword[rsp + offsetof(StackLayout::Thunk, xmm[4])]);
|
||||
movaps(xmm5, qword[rsp + offsetof(StackLayout::Thunk, xmm[5])]);
|
||||
|
||||
// mov(rax, qword[rsp + offsetof(StackLayout::Thunk, r[0])]);
|
||||
mov(rcx, qword[rsp + offsetof(StackLayout::Thunk, r[1])]);
|
||||
mov(rdx, qword[rsp + offsetof(StackLayout::Thunk, r[2])]);
|
||||
mov(r8, qword[rsp + offsetof(StackLayout::Thunk, r[3])]);
|
||||
mov(r9, qword[rsp + offsetof(StackLayout::Thunk, r[4])]);
|
||||
mov(r10, qword[rsp + offsetof(StackLayout::Thunk, r[5])]);
|
||||
mov(r11, qword[rsp + offsetof(StackLayout::Thunk, r[6])]);
|
||||
}
|
||||
|
||||
void X64ThunkEmitter::EmitSaveNonvolatileRegs() {
|
||||
// Preserve nonvolatile registers.
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[0])], rbx);
|
||||
mov(qword[rsp + offsetof(StackLayout::Thunk, r[1])], rcx);
|
||||
@@ -427,12 +545,9 @@ HostToGuestThunk X64ThunkEmitter::EmitHostToGuestThunk() {
|
||||
movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[7])], xmm13);
|
||||
movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[8])], xmm14);
|
||||
movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[9])], xmm15);
|
||||
}
|
||||
|
||||
mov(rax, rcx);
|
||||
mov(rsi, rdx); // context
|
||||
mov(rcx, r8); // return address
|
||||
call(rax);
|
||||
|
||||
void X64ThunkEmitter::EmitLoadNonvolatileRegs() {
|
||||
movaps(xmm6, qword[rsp + offsetof(StackLayout::Thunk, xmm[0])]);
|
||||
movaps(xmm7, qword[rsp + offsetof(StackLayout::Thunk, xmm[1])]);
|
||||
movaps(xmm8, qword[rsp + offsetof(StackLayout::Thunk, xmm[2])]);
|
||||
@@ -453,100 +568,6 @@ HostToGuestThunk X64ThunkEmitter::EmitHostToGuestThunk() {
|
||||
mov(r13, qword[rsp + offsetof(StackLayout::Thunk, r[6])]);
|
||||
mov(r14, qword[rsp + offsetof(StackLayout::Thunk, r[7])]);
|
||||
mov(r15, qword[rsp + offsetof(StackLayout::Thunk, r[8])]);
|
||||
|
||||
add(rsp, stack_size);
|
||||
mov(rcx, qword[rsp + 8 * 1]);
|
||||
mov(rdx, qword[rsp + 8 * 2]);
|
||||
mov(r8, qword[rsp + 8 * 3]);
|
||||
ret();
|
||||
|
||||
void* fn = Emplace(stack_size);
|
||||
return (HostToGuestThunk)fn;
|
||||
}
|
||||
|
||||
GuestToHostThunk X64ThunkEmitter::EmitGuestToHostThunk() {
|
||||
// rcx = context
|
||||
// rdx = target function
|
||||
// r8 = arg0
|
||||
// r9 = arg1
|
||||
// r10 = arg2
|
||||
|
||||
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
||||
// rsp + 0 = return address
|
||||
mov(qword[rsp + 8 * 2], rdx);
|
||||
mov(qword[rsp + 8 * 1], rcx);
|
||||
sub(rsp, stack_size);
|
||||
|
||||
// Save off volatile registers.
|
||||
// TODO(DrChat): Enable this when we actually need this.
|
||||
// mov(qword[rsp + offsetof(StackLayout::Thunk, r[0])], rcx);
|
||||
// mov(qword[rsp + offsetof(StackLayout::Thunk, r[1])], rdx);
|
||||
// mov(qword[rsp + offsetof(StackLayout::Thunk, r[2])], r8);
|
||||
// mov(qword[rsp + offsetof(StackLayout::Thunk, r[3])], r9);
|
||||
// mov(qword[rsp + offsetof(StackLayout::Thunk, r[4])], r10);
|
||||
// mov(qword[rsp + offsetof(StackLayout::Thunk, r[5])], r11);
|
||||
|
||||
// movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[1])], xmm1);
|
||||
// movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[2])], xmm2);
|
||||
// movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[3])], xmm3);
|
||||
// movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[4])], xmm4);
|
||||
// movaps(qword[rsp + offsetof(StackLayout::Thunk, xmm[5])], xmm5);
|
||||
|
||||
mov(rax, rdx);
|
||||
mov(rcx, rsi); // context
|
||||
mov(rdx, r8);
|
||||
mov(r8, r9);
|
||||
mov(r9, r10);
|
||||
call(rax);
|
||||
|
||||
// movaps(xmm1, qword[rsp + offsetof(StackLayout::Thunk, xmm[1])]);
|
||||
// movaps(xmm2, qword[rsp + offsetof(StackLayout::Thunk, xmm[2])]);
|
||||
// movaps(xmm3, qword[rsp + offsetof(StackLayout::Thunk, xmm[3])]);
|
||||
// movaps(xmm4, qword[rsp + offsetof(StackLayout::Thunk, xmm[4])]);
|
||||
// movaps(xmm5, qword[rsp + offsetof(StackLayout::Thunk, xmm[5])]);
|
||||
|
||||
// mov(rcx, qword[rsp + offsetof(StackLayout::Thunk, r[0])]);
|
||||
// mov(rdx, qword[rsp + offsetof(StackLayout::Thunk, r[1])]);
|
||||
// mov(r8, qword[rsp + offsetof(StackLayout::Thunk, r[2])]);
|
||||
// mov(r9, qword[rsp + offsetof(StackLayout::Thunk, r[3])]);
|
||||
// mov(r10, qword[rsp + offsetof(StackLayout::Thunk, r[4])]);
|
||||
// mov(r11, qword[rsp + offsetof(StackLayout::Thunk, r[5])]);
|
||||
|
||||
add(rsp, stack_size);
|
||||
mov(rcx, qword[rsp + 8 * 1]);
|
||||
mov(rdx, qword[rsp + 8 * 2]);
|
||||
ret();
|
||||
|
||||
void* fn = Emplace(stack_size);
|
||||
return (GuestToHostThunk)fn;
|
||||
}
|
||||
|
||||
// X64Emitter handles actually resolving functions.
|
||||
extern "C" uint64_t ResolveFunction(void* raw_context, uint32_t target_address);
|
||||
|
||||
ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() {
|
||||
// ebx = target PPC address
|
||||
// rcx = context
|
||||
|
||||
uint32_t stack_size = 0x18;
|
||||
|
||||
// rsp + 0 = return address
|
||||
mov(qword[rsp + 8 * 2], rdx);
|
||||
mov(qword[rsp + 8 * 1], rcx);
|
||||
sub(rsp, stack_size);
|
||||
|
||||
mov(rcx, rsi); // context
|
||||
mov(rdx, rbx);
|
||||
mov(rax, uint64_t(&ResolveFunction));
|
||||
call(rax);
|
||||
|
||||
add(rsp, stack_size);
|
||||
mov(rcx, qword[rsp + 8 * 1]);
|
||||
mov(rdx, qword[rsp + 8 * 2]);
|
||||
jmp(rax);
|
||||
|
||||
void* fn = Emplace(stack_size);
|
||||
return (ResolveFunctionThunk)fn;
|
||||
}
|
||||
|
||||
} // namespace x64
|
||||
|
||||
@@ -174,15 +174,17 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||
// If we are going above the high water mark of committed memory, commit
|
||||
// some more. It's ok if multiple threads do this, as redundant commits
|
||||
// aren't harmful.
|
||||
size_t old_commit_mark = generated_code_commit_mark_;
|
||||
if (high_mark > old_commit_mark) {
|
||||
size_t new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
||||
size_t old_commit_mark, new_commit_mark;
|
||||
do {
|
||||
old_commit_mark = generated_code_commit_mark_;
|
||||
if (high_mark <= old_commit_mark) break;
|
||||
|
||||
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
||||
xe::memory::AllocFixed(generated_code_base_, new_commit_mark,
|
||||
xe::memory::AllocationType::kCommit,
|
||||
xe::memory::PageAccess::kExecuteReadWrite);
|
||||
generated_code_commit_mark_.compare_exchange_strong(old_commit_mark,
|
||||
new_commit_mark);
|
||||
}
|
||||
} while (generated_code_commit_mark_.compare_exchange_weak(
|
||||
old_commit_mark, new_commit_mark));
|
||||
|
||||
// Copy code.
|
||||
std::memcpy(code_address, machine_code, code_size);
|
||||
@@ -248,15 +250,17 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
|
||||
// If we are going above the high water mark of committed memory, commit some
|
||||
// more. It's ok if multiple threads do this, as redundant commits aren't
|
||||
// harmful.
|
||||
size_t old_commit_mark = generated_code_commit_mark_;
|
||||
if (high_mark > old_commit_mark) {
|
||||
size_t new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
||||
size_t old_commit_mark, new_commit_mark;
|
||||
do {
|
||||
old_commit_mark = generated_code_commit_mark_;
|
||||
if (high_mark <= old_commit_mark) break;
|
||||
|
||||
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
||||
xe::memory::AllocFixed(generated_code_base_, new_commit_mark,
|
||||
xe::memory::AllocationType::kCommit,
|
||||
xe::memory::PageAccess::kExecuteReadWrite);
|
||||
generated_code_commit_mark_.compare_exchange_strong(old_commit_mark,
|
||||
new_commit_mark);
|
||||
}
|
||||
} while (generated_code_commit_mark_.compare_exchange_weak(old_commit_mark,
|
||||
new_commit_mark));
|
||||
|
||||
// Copy code.
|
||||
std::memcpy(data_address, data, length);
|
||||
|
||||
@@ -56,12 +56,13 @@ static const size_t kStashOffset = 32;
|
||||
// static const size_t kStashOffsetHigh = 32 + 32;
|
||||
|
||||
const uint32_t X64Emitter::gpr_reg_map_[X64Emitter::GPR_COUNT] = {
|
||||
Xbyak::Operand::RBX, Xbyak::Operand::R12, Xbyak::Operand::R13,
|
||||
Xbyak::Operand::R14, Xbyak::Operand::R15,
|
||||
Xbyak::Operand::RBX, Xbyak::Operand::R10, Xbyak::Operand::R11,
|
||||
Xbyak::Operand::R12, Xbyak::Operand::R13, Xbyak::Operand::R14,
|
||||
Xbyak::Operand::R15,
|
||||
};
|
||||
|
||||
const uint32_t X64Emitter::xmm_reg_map_[X64Emitter::XMM_COUNT] = {
|
||||
6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
};
|
||||
|
||||
X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
|
||||
@@ -148,11 +149,13 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
|
||||
for (auto it = locals.begin(); it != locals.end(); ++it) {
|
||||
auto slot = *it;
|
||||
size_t type_size = GetTypeSize(slot->type);
|
||||
|
||||
// Align to natural size.
|
||||
stack_offset = xe::align(stack_offset, type_size);
|
||||
slot->set_constant((uint32_t)stack_offset);
|
||||
stack_offset += type_size;
|
||||
}
|
||||
|
||||
// Ensure 16b alignment.
|
||||
stack_offset -= StackLayout::GUEST_STACK_SIZE;
|
||||
stack_offset = xe::align(stack_offset, static_cast<size_t>(16));
|
||||
@@ -160,7 +163,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
|
||||
// Function prolog.
|
||||
// Must be 16b aligned.
|
||||
// Windows is very strict about the form of this and the epilog:
|
||||
// https://msdn.microsoft.com/en-us/library/tawsa7cb.aspx
|
||||
// https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=vs-2017
|
||||
// IMPORTANT: any changes to the prolog must be kept in sync with
|
||||
// X64CodeCache, which dynamically generates exception information.
|
||||
// Adding or changing anything here must be matched!
|
||||
@@ -168,6 +171,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
|
||||
assert_true((stack_size + 8) % 16 == 0);
|
||||
*out_stack_size = stack_size;
|
||||
stack_size_ = stack_size;
|
||||
|
||||
sub(rsp, (uint32_t)stack_size);
|
||||
mov(qword[rsp + StackLayout::GUEST_CTX_HOME], GetContextReg());
|
||||
mov(qword[rsp + StackLayout::GUEST_RET_ADDR], rcx);
|
||||
@@ -221,6 +225,8 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
|
||||
const Instr* new_tail = instr;
|
||||
if (!SelectSequence(this, instr, &new_tail)) {
|
||||
// No sequence found!
|
||||
// NOTE: If you encounter this after adding a new instruction, do a full
|
||||
// rebuild!
|
||||
assert_always();
|
||||
XELOGE("Unable to process HIR opcode %s", instr->opcode->name);
|
||||
break;
|
||||
@@ -340,13 +346,14 @@ void X64Emitter::UnimplementedInstr(const hir::Instr* i) {
|
||||
|
||||
// This is used by the X64ThunkEmitter's ResolveFunctionThunk.
|
||||
extern "C" uint64_t ResolveFunction(void* raw_context,
|
||||
uint32_t target_address) {
|
||||
uint64_t target_address) {
|
||||
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
|
||||
|
||||
// TODO(benvanik): required?
|
||||
assert_not_zero(target_address);
|
||||
|
||||
auto fn = thread_state->processor()->ResolveFunction(target_address);
|
||||
auto fn =
|
||||
thread_state->processor()->ResolveFunction((uint32_t)target_address);
|
||||
assert_not_null(fn);
|
||||
auto x64_fn = static_cast<X64Function*>(fn);
|
||||
uint64_t addr = reinterpret_cast<uint64_t>(x64_fn->machine_code());
|
||||
@@ -373,10 +380,7 @@ void X64Emitter::Call(const hir::Instr* instr, GuestFunction* function) {
|
||||
// Old-style resolve.
|
||||
// Not too important because indirection table is almost always available.
|
||||
// TODO: Overwrite the call-site with a straight call.
|
||||
mov(rax, reinterpret_cast<uint64_t>(ResolveFunction));
|
||||
mov(rcx, GetContextReg());
|
||||
mov(rdx, function->address());
|
||||
call(rax);
|
||||
CallNative(&ResolveFunction, function->address());
|
||||
}
|
||||
|
||||
// Actually jump/call to rax.
|
||||
@@ -457,16 +461,15 @@ void X64Emitter::CallExtern(const hir::Instr* instr, const Function* function) {
|
||||
auto builtin_function = static_cast<const BuiltinFunction*>(function);
|
||||
if (builtin_function->handler()) {
|
||||
undefined = false;
|
||||
// rcx = context
|
||||
// rdx = target host function
|
||||
// r8 = arg0
|
||||
// r9 = arg1
|
||||
mov(rcx, GetContextReg());
|
||||
mov(rdx, reinterpret_cast<uint64_t>(builtin_function->handler()));
|
||||
mov(r8, reinterpret_cast<uint64_t>(builtin_function->arg0()));
|
||||
mov(r9, reinterpret_cast<uint64_t>(builtin_function->arg1()));
|
||||
// rcx = target function
|
||||
// rdx = arg0
|
||||
// r8 = arg1
|
||||
// r9 = arg2
|
||||
auto thunk = backend()->guest_to_host_thunk();
|
||||
mov(rax, reinterpret_cast<uint64_t>(thunk));
|
||||
mov(rcx, reinterpret_cast<uint64_t>(builtin_function->handler()));
|
||||
mov(rdx, reinterpret_cast<uint64_t>(builtin_function->arg0()));
|
||||
mov(r8, reinterpret_cast<uint64_t>(builtin_function->arg1()));
|
||||
call(rax);
|
||||
// rax = host return
|
||||
}
|
||||
@@ -474,13 +477,15 @@ void X64Emitter::CallExtern(const hir::Instr* instr, const Function* function) {
|
||||
auto extern_function = static_cast<const GuestFunction*>(function);
|
||||
if (extern_function->extern_handler()) {
|
||||
undefined = false;
|
||||
// rcx = context
|
||||
// rdx = target host function
|
||||
mov(rcx, GetContextReg());
|
||||
mov(rdx, reinterpret_cast<uint64_t>(extern_function->extern_handler()));
|
||||
mov(r8, qword[GetContextReg() + offsetof(ppc::PPCContext, kernel_state)]);
|
||||
// rcx = target function
|
||||
// rdx = arg0
|
||||
// r8 = arg1
|
||||
// r9 = arg2
|
||||
auto thunk = backend()->guest_to_host_thunk();
|
||||
mov(rax, reinterpret_cast<uint64_t>(thunk));
|
||||
mov(rcx, reinterpret_cast<uint64_t>(extern_function->extern_handler()));
|
||||
mov(rdx,
|
||||
qword[GetContextReg() + offsetof(ppc::PPCContext, kernel_state)]);
|
||||
call(rax);
|
||||
// rax = host return
|
||||
}
|
||||
@@ -490,42 +495,30 @@ void X64Emitter::CallExtern(const hir::Instr* instr, const Function* function) {
|
||||
}
|
||||
}
|
||||
|
||||
void X64Emitter::CallNative(void* fn) {
|
||||
mov(rax, reinterpret_cast<uint64_t>(fn));
|
||||
mov(rcx, GetContextReg());
|
||||
call(rax);
|
||||
}
|
||||
void X64Emitter::CallNative(void* fn) { CallNativeSafe(fn); }
|
||||
|
||||
void X64Emitter::CallNative(uint64_t (*fn)(void* raw_context)) {
|
||||
mov(rax, reinterpret_cast<uint64_t>(fn));
|
||||
mov(rcx, GetContextReg());
|
||||
call(rax);
|
||||
CallNativeSafe(reinterpret_cast<void*>(fn));
|
||||
}
|
||||
|
||||
void X64Emitter::CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0)) {
|
||||
mov(rax, reinterpret_cast<uint64_t>(fn));
|
||||
mov(rcx, GetContextReg());
|
||||
call(rax);
|
||||
CallNativeSafe(reinterpret_cast<void*>(fn));
|
||||
}
|
||||
|
||||
void X64Emitter::CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0),
|
||||
uint64_t arg0) {
|
||||
mov(rax, reinterpret_cast<uint64_t>(fn));
|
||||
mov(rcx, GetContextReg());
|
||||
mov(rdx, arg0);
|
||||
call(rax);
|
||||
mov(GetNativeParam(0), arg0);
|
||||
CallNativeSafe(reinterpret_cast<void*>(fn));
|
||||
}
|
||||
|
||||
void X64Emitter::CallNativeSafe(void* fn) {
|
||||
// rcx = context
|
||||
// rdx = target function
|
||||
// r8 = arg0
|
||||
// r9 = arg1
|
||||
// r10 = arg2
|
||||
// rcx = target function
|
||||
// rdx = arg0
|
||||
// r8 = arg1
|
||||
// r9 = arg2
|
||||
auto thunk = backend()->guest_to_host_thunk();
|
||||
mov(rax, reinterpret_cast<uint64_t>(thunk));
|
||||
mov(rcx, GetContextReg());
|
||||
mov(rdx, reinterpret_cast<uint64_t>(fn));
|
||||
mov(rcx, reinterpret_cast<uint64_t>(fn));
|
||||
call(rax);
|
||||
// rax = host return
|
||||
}
|
||||
@@ -535,6 +528,18 @@ void X64Emitter::SetReturnAddress(uint64_t value) {
|
||||
mov(qword[rsp + StackLayout::GUEST_CALL_RET_ADDR], rax);
|
||||
}
|
||||
|
||||
Xbyak::Reg64 X64Emitter::GetNativeParam(uint32_t param) {
|
||||
if (param == 0)
|
||||
return rdx;
|
||||
else if (param == 1)
|
||||
return r8;
|
||||
else if (param == 2)
|
||||
return r9;
|
||||
|
||||
assert_always();
|
||||
return r9;
|
||||
}
|
||||
|
||||
// Important: If you change these, you must update the thunks in x64_backend.cc!
|
||||
Xbyak::Reg64 X64Emitter::GetContextReg() { return rsi; }
|
||||
Xbyak::Reg64 X64Emitter::GetMembaseReg() { return rdi; }
|
||||
|
||||
@@ -139,13 +139,13 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||
std::vector<SourceMapEntry>* out_source_map);
|
||||
|
||||
public:
|
||||
// Reserved: rsp
|
||||
// Reserved: rsp, rsi, rdi
|
||||
// Scratch: rax/rcx/rdx
|
||||
// xmm0-2
|
||||
// Available: rbx, r12-r15 (save to get r8-r11, rbp, rsi, rdi?)
|
||||
// xmm6-xmm15 (save to get xmm3-xmm5)
|
||||
static const int GPR_COUNT = 5;
|
||||
static const int XMM_COUNT = 10;
|
||||
// Available: rbx, r10-r15
|
||||
// xmm4-xmm15 (save to get xmm3)
|
||||
static const int GPR_COUNT = 7;
|
||||
static const int XMM_COUNT = 12;
|
||||
|
||||
static void SetupReg(const hir::Value* v, Xbyak::Reg8& r) {
|
||||
auto idx = gpr_reg_map_[v->reg.index];
|
||||
@@ -187,6 +187,8 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||
void CallNativeSafe(void* fn);
|
||||
void SetReturnAddress(uint64_t value);
|
||||
|
||||
Xbyak::Reg64 GetNativeParam(uint32_t param);
|
||||
|
||||
Xbyak::Reg64 GetContextReg();
|
||||
Xbyak::Reg64 GetMembaseReg();
|
||||
void ReloadContext();
|
||||
|
||||
629
src/xenia/cpu/backend/x64/x64_op.h
Normal file
629
src/xenia/cpu/backend/x64/x64_op.h
Normal file
@@ -0,0 +1,629 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2018 Xenia Developers. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
#ifndef XENIA_CPU_BACKEND_X64_X64_OP_H_
|
||||
#define XENIA_CPU_BACKEND_X64_X64_OP_H_
|
||||
|
||||
#include "xenia/cpu/backend/x64/x64_emitter.h"
|
||||
|
||||
#include "xenia/cpu/hir/instr.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
namespace x64 {
|
||||
|
||||
// TODO(benvanik): direct usings.
|
||||
using namespace xe::cpu;
|
||||
using namespace xe::cpu::hir;
|
||||
using namespace Xbyak;
|
||||
|
||||
// Selects the right byte/word/etc from a vector. We need to flip logical
|
||||
// indices (0,1,2,3,4,5,6,7,...) = (3,2,1,0,7,6,5,4,...)
|
||||
#define VEC128_B(n) ((n) ^ 0x3)
|
||||
#define VEC128_W(n) ((n) ^ 0x1)
|
||||
#define VEC128_D(n) (n)
|
||||
#define VEC128_F(n) (n)
|
||||
|
||||
enum KeyType {
|
||||
KEY_TYPE_X = OPCODE_SIG_TYPE_X,
|
||||
KEY_TYPE_L = OPCODE_SIG_TYPE_L,
|
||||
KEY_TYPE_O = OPCODE_SIG_TYPE_O,
|
||||
KEY_TYPE_S = OPCODE_SIG_TYPE_S,
|
||||
KEY_TYPE_V_I8 = OPCODE_SIG_TYPE_V + INT8_TYPE,
|
||||
KEY_TYPE_V_I16 = OPCODE_SIG_TYPE_V + INT16_TYPE,
|
||||
KEY_TYPE_V_I32 = OPCODE_SIG_TYPE_V + INT32_TYPE,
|
||||
KEY_TYPE_V_I64 = OPCODE_SIG_TYPE_V + INT64_TYPE,
|
||||
KEY_TYPE_V_F32 = OPCODE_SIG_TYPE_V + FLOAT32_TYPE,
|
||||
KEY_TYPE_V_F64 = OPCODE_SIG_TYPE_V + FLOAT64_TYPE,
|
||||
KEY_TYPE_V_V128 = OPCODE_SIG_TYPE_V + VEC128_TYPE,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
union InstrKey {
|
||||
struct {
|
||||
uint32_t opcode : 8;
|
||||
uint32_t dest : 5;
|
||||
uint32_t src1 : 5;
|
||||
uint32_t src2 : 5;
|
||||
uint32_t src3 : 5;
|
||||
uint32_t reserved : 4;
|
||||
};
|
||||
uint32_t value;
|
||||
|
||||
operator uint32_t() const { return value; }
|
||||
|
||||
InstrKey() : value(0) {}
|
||||
InstrKey(uint32_t v) : value(v) {}
|
||||
InstrKey(const Instr* i) : value(0) {
|
||||
opcode = i->opcode->num;
|
||||
uint32_t sig = i->opcode->signature;
|
||||
dest =
|
||||
GET_OPCODE_SIG_TYPE_DEST(sig) ? OPCODE_SIG_TYPE_V + i->dest->type : 0;
|
||||
src1 = GET_OPCODE_SIG_TYPE_SRC1(sig);
|
||||
if (src1 == OPCODE_SIG_TYPE_V) {
|
||||
src1 += i->src1.value->type;
|
||||
}
|
||||
src2 = GET_OPCODE_SIG_TYPE_SRC2(sig);
|
||||
if (src2 == OPCODE_SIG_TYPE_V) {
|
||||
src2 += i->src2.value->type;
|
||||
}
|
||||
src3 = GET_OPCODE_SIG_TYPE_SRC3(sig);
|
||||
if (src3 == OPCODE_SIG_TYPE_V) {
|
||||
src3 += i->src3.value->type;
|
||||
}
|
||||
}
|
||||
|
||||
template <Opcode OPCODE, KeyType DEST = KEY_TYPE_X, KeyType SRC1 = KEY_TYPE_X,
|
||||
KeyType SRC2 = KEY_TYPE_X, KeyType SRC3 = KEY_TYPE_X>
|
||||
struct Construct {
|
||||
static const uint32_t value =
|
||||
(OPCODE) | (DEST << 8) | (SRC1 << 13) | (SRC2 << 18) | (SRC3 << 23);
|
||||
};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(InstrKey) <= 4, "Key must be 4 bytes");
|
||||
|
||||
template <typename... Ts>
|
||||
struct CombinedStruct;
|
||||
template <>
|
||||
struct CombinedStruct<> {};
|
||||
template <typename T, typename... Ts>
|
||||
struct CombinedStruct<T, Ts...> : T, CombinedStruct<Ts...> {};
|
||||
|
||||
struct OpBase {};
|
||||
|
||||
template <typename T, KeyType KEY_TYPE>
|
||||
struct Op : OpBase {
|
||||
static const KeyType key_type = KEY_TYPE;
|
||||
};
|
||||
|
||||
struct VoidOp : Op<VoidOp, KEY_TYPE_X> {
|
||||
protected:
|
||||
template <typename T, KeyType KEY_TYPE>
|
||||
friend struct Op;
|
||||
template <hir::Opcode OPCODE, typename... Ts>
|
||||
friend struct I;
|
||||
void Load(const Instr::Op& op) {}
|
||||
};
|
||||
|
||||
struct OffsetOp : Op<OffsetOp, KEY_TYPE_O> {
|
||||
uint64_t value;
|
||||
|
||||
protected:
|
||||
template <typename T, KeyType KEY_TYPE>
|
||||
friend struct Op;
|
||||
template <hir::Opcode OPCODE, typename... Ts>
|
||||
friend struct I;
|
||||
void Load(const Instr::Op& op) { this->value = op.offset; }
|
||||
};
|
||||
|
||||
struct SymbolOp : Op<SymbolOp, KEY_TYPE_S> {
|
||||
Function* value;
|
||||
|
||||
protected:
|
||||
template <typename T, KeyType KEY_TYPE>
|
||||
friend struct Op;
|
||||
template <hir::Opcode OPCODE, typename... Ts>
|
||||
friend struct I;
|
||||
bool Load(const Instr::Op& op) {
|
||||
this->value = op.symbol;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct LabelOp : Op<LabelOp, KEY_TYPE_L> {
|
||||
hir::Label* value;
|
||||
|
||||
protected:
|
||||
template <typename T, KeyType KEY_TYPE>
|
||||
friend struct Op;
|
||||
template <hir::Opcode OPCODE, typename... Ts>
|
||||
friend struct I;
|
||||
void Load(const Instr::Op& op) { this->value = op.label; }
|
||||
};
|
||||
|
||||
template <typename T, KeyType KEY_TYPE, typename REG_TYPE, typename CONST_TYPE>
|
||||
struct ValueOp : Op<ValueOp<T, KEY_TYPE, REG_TYPE, CONST_TYPE>, KEY_TYPE> {
|
||||
typedef REG_TYPE reg_type;
|
||||
const Value* value;
|
||||
bool is_constant;
|
||||
virtual bool ConstantFitsIn32Reg() const { return true; }
|
||||
const REG_TYPE& reg() const {
|
||||
assert_true(!is_constant);
|
||||
return reg_;
|
||||
}
|
||||
operator const REG_TYPE&() const { return reg(); }
|
||||
bool IsEqual(const T& b) const {
|
||||
if (is_constant && b.is_constant) {
|
||||
return reinterpret_cast<const T*>(this)->constant() == b.constant();
|
||||
} else if (!is_constant && !b.is_constant) {
|
||||
return reg_.getIdx() == b.reg_.getIdx();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool IsEqual(const Xbyak::Reg& b) const {
|
||||
if (is_constant) {
|
||||
return false;
|
||||
} else if (!is_constant) {
|
||||
return reg_.getIdx() == b.getIdx();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool operator==(const T& b) const { return IsEqual(b); }
|
||||
bool operator!=(const T& b) const { return !IsEqual(b); }
|
||||
bool operator==(const Xbyak::Reg& b) const { return IsEqual(b); }
|
||||
bool operator!=(const Xbyak::Reg& b) const { return !IsEqual(b); }
|
||||
void Load(const Instr::Op& op) {
|
||||
value = op.value;
|
||||
is_constant = value->IsConstant();
|
||||
if (!is_constant) {
|
||||
X64Emitter::SetupReg(value, reg_);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
REG_TYPE reg_;
|
||||
};
|
||||
|
||||
struct I8Op : ValueOp<I8Op, KEY_TYPE_V_I8, Reg8, int8_t> {
|
||||
typedef ValueOp<I8Op, KEY_TYPE_V_I8, Reg8, int8_t> BASE;
|
||||
const int8_t constant() const {
|
||||
assert_true(BASE::is_constant);
|
||||
return BASE::value->constant.i8;
|
||||
}
|
||||
};
|
||||
struct I16Op : ValueOp<I16Op, KEY_TYPE_V_I16, Reg16, int16_t> {
|
||||
typedef ValueOp<I16Op, KEY_TYPE_V_I16, Reg16, int16_t> BASE;
|
||||
const int16_t constant() const {
|
||||
assert_true(BASE::is_constant);
|
||||
return BASE::value->constant.i16;
|
||||
}
|
||||
};
|
||||
struct I32Op : ValueOp<I32Op, KEY_TYPE_V_I32, Reg32, int32_t> {
|
||||
typedef ValueOp<I32Op, KEY_TYPE_V_I32, Reg32, int32_t> BASE;
|
||||
const int32_t constant() const {
|
||||
assert_true(BASE::is_constant);
|
||||
return BASE::value->constant.i32;
|
||||
}
|
||||
};
|
||||
struct I64Op : ValueOp<I64Op, KEY_TYPE_V_I64, Reg64, int64_t> {
|
||||
typedef ValueOp<I64Op, KEY_TYPE_V_I64, Reg64, int64_t> BASE;
|
||||
const int64_t constant() const {
|
||||
assert_true(BASE::is_constant);
|
||||
return BASE::value->constant.i64;
|
||||
}
|
||||
bool ConstantFitsIn32Reg() const override {
|
||||
int64_t v = BASE::value->constant.i64;
|
||||
if ((v & ~0x7FFFFFFF) == 0) {
|
||||
// Fits under 31 bits, so just load using normal mov.
|
||||
return true;
|
||||
} else if ((v & ~0x7FFFFFFF) == ~0x7FFFFFFF) {
|
||||
// Negative number that fits in 32bits.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
struct F32Op : ValueOp<F32Op, KEY_TYPE_V_F32, Xmm, float> {
|
||||
typedef ValueOp<F32Op, KEY_TYPE_V_F32, Xmm, float> BASE;
|
||||
const float constant() const {
|
||||
assert_true(BASE::is_constant);
|
||||
return BASE::value->constant.f32;
|
||||
}
|
||||
};
|
||||
struct F64Op : ValueOp<F64Op, KEY_TYPE_V_F64, Xmm, double> {
|
||||
typedef ValueOp<F64Op, KEY_TYPE_V_F64, Xmm, double> BASE;
|
||||
const double constant() const {
|
||||
assert_true(BASE::is_constant);
|
||||
return BASE::value->constant.f64;
|
||||
}
|
||||
};
|
||||
struct V128Op : ValueOp<V128Op, KEY_TYPE_V_V128, Xmm, vec128_t> {
|
||||
typedef ValueOp<V128Op, KEY_TYPE_V_V128, Xmm, vec128_t> BASE;
|
||||
const vec128_t& constant() const {
|
||||
assert_true(BASE::is_constant);
|
||||
return BASE::value->constant.v128;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename DEST, typename... Tf>
|
||||
struct DestField;
|
||||
template <typename DEST>
|
||||
struct DestField<DEST> {
|
||||
DEST dest;
|
||||
|
||||
protected:
|
||||
bool LoadDest(const Instr* i) {
|
||||
Instr::Op op;
|
||||
op.value = i->dest;
|
||||
dest.Load(op);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct DestField<VoidOp> {
|
||||
protected:
|
||||
bool LoadDest(const Instr* i) { return true; }
|
||||
};
|
||||
|
||||
template <hir::Opcode OPCODE, typename... Ts>
|
||||
struct I;
|
||||
template <hir::Opcode OPCODE, typename DEST>
|
||||
struct I<OPCODE, DEST> : DestField<DEST> {
|
||||
typedef DestField<DEST> BASE;
|
||||
static const hir::Opcode opcode = OPCODE;
|
||||
static const uint32_t key =
|
||||
InstrKey::Construct<OPCODE, DEST::key_type>::value;
|
||||
static const KeyType dest_type = DEST::key_type;
|
||||
const Instr* instr;
|
||||
|
||||
protected:
|
||||
template <typename SEQ, typename T>
|
||||
friend struct Sequence;
|
||||
bool Load(const Instr* i) {
|
||||
if (InstrKey(i).value == key && BASE::LoadDest(i)) {
|
||||
instr = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
template <hir::Opcode OPCODE, typename DEST, typename SRC1>
|
||||
struct I<OPCODE, DEST, SRC1> : DestField<DEST> {
|
||||
typedef DestField<DEST> BASE;
|
||||
static const hir::Opcode opcode = OPCODE;
|
||||
static const uint32_t key =
|
||||
InstrKey::Construct<OPCODE, DEST::key_type, SRC1::key_type>::value;
|
||||
static const KeyType dest_type = DEST::key_type;
|
||||
static const KeyType src1_type = SRC1::key_type;
|
||||
const Instr* instr;
|
||||
SRC1 src1;
|
||||
|
||||
protected:
|
||||
template <typename SEQ, typename T>
|
||||
friend struct Sequence;
|
||||
bool Load(const Instr* i) {
|
||||
if (InstrKey(i).value == key && BASE::LoadDest(i)) {
|
||||
instr = i;
|
||||
src1.Load(i->src1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
template <hir::Opcode OPCODE, typename DEST, typename SRC1, typename SRC2>
|
||||
struct I<OPCODE, DEST, SRC1, SRC2> : DestField<DEST> {
|
||||
typedef DestField<DEST> BASE;
|
||||
static const hir::Opcode opcode = OPCODE;
|
||||
static const uint32_t key =
|
||||
InstrKey::Construct<OPCODE, DEST::key_type, SRC1::key_type,
|
||||
SRC2::key_type>::value;
|
||||
static const KeyType dest_type = DEST::key_type;
|
||||
static const KeyType src1_type = SRC1::key_type;
|
||||
static const KeyType src2_type = SRC2::key_type;
|
||||
const Instr* instr;
|
||||
SRC1 src1;
|
||||
SRC2 src2;
|
||||
|
||||
protected:
|
||||
template <typename SEQ, typename T>
|
||||
friend struct Sequence;
|
||||
bool Load(const Instr* i) {
|
||||
if (InstrKey(i).value == key && BASE::LoadDest(i)) {
|
||||
instr = i;
|
||||
src1.Load(i->src1);
|
||||
src2.Load(i->src2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
template <hir::Opcode OPCODE, typename DEST, typename SRC1, typename SRC2,
|
||||
typename SRC3>
|
||||
struct I<OPCODE, DEST, SRC1, SRC2, SRC3> : DestField<DEST> {
|
||||
typedef DestField<DEST> BASE;
|
||||
static const hir::Opcode opcode = OPCODE;
|
||||
static const uint32_t key =
|
||||
InstrKey::Construct<OPCODE, DEST::key_type, SRC1::key_type,
|
||||
SRC2::key_type, SRC3::key_type>::value;
|
||||
static const KeyType dest_type = DEST::key_type;
|
||||
static const KeyType src1_type = SRC1::key_type;
|
||||
static const KeyType src2_type = SRC2::key_type;
|
||||
static const KeyType src3_type = SRC3::key_type;
|
||||
const Instr* instr;
|
||||
SRC1 src1;
|
||||
SRC2 src2;
|
||||
SRC3 src3;
|
||||
|
||||
protected:
|
||||
template <typename SEQ, typename T>
|
||||
friend struct Sequence;
|
||||
bool Load(const Instr* i) {
|
||||
if (InstrKey(i).value == key && BASE::LoadDest(i)) {
|
||||
instr = i;
|
||||
src1.Load(i->src1);
|
||||
src2.Load(i->src2);
|
||||
src3.Load(i->src3);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static const T GetTempReg(X64Emitter& e);
|
||||
template <>
|
||||
const Reg8 GetTempReg<Reg8>(X64Emitter& e) {
|
||||
return e.al;
|
||||
}
|
||||
template <>
|
||||
const Reg16 GetTempReg<Reg16>(X64Emitter& e) {
|
||||
return e.ax;
|
||||
}
|
||||
template <>
|
||||
const Reg32 GetTempReg<Reg32>(X64Emitter& e) {
|
||||
return e.eax;
|
||||
}
|
||||
template <>
|
||||
const Reg64 GetTempReg<Reg64>(X64Emitter& e) {
|
||||
return e.rax;
|
||||
}
|
||||
|
||||
template <typename SEQ, typename T>
|
||||
struct Sequence {
|
||||
typedef T EmitArgType;
|
||||
|
||||
static constexpr uint32_t head_key() { return T::key; }
|
||||
|
||||
static bool Select(X64Emitter& e, const Instr* i) {
|
||||
T args;
|
||||
if (!args.Load(i)) {
|
||||
return false;
|
||||
}
|
||||
SEQ::Emit(e, args);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename REG_FN>
|
||||
static void EmitUnaryOp(X64Emitter& e, const EmitArgType& i,
|
||||
const REG_FN& reg_fn) {
|
||||
if (i.src1.is_constant) {
|
||||
e.mov(i.dest, i.src1.constant());
|
||||
reg_fn(e, i.dest);
|
||||
} else {
|
||||
if (i.dest != i.src1) {
|
||||
e.mov(i.dest, i.src1);
|
||||
}
|
||||
reg_fn(e, i.dest);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename REG_REG_FN, typename REG_CONST_FN>
|
||||
static void EmitCommutativeBinaryOp(X64Emitter& e, const EmitArgType& i,
|
||||
const REG_REG_FN& reg_reg_fn,
|
||||
const REG_CONST_FN& reg_const_fn) {
|
||||
if (i.src1.is_constant) {
|
||||
if (i.src2.is_constant) {
|
||||
// Both constants.
|
||||
if (i.src1.ConstantFitsIn32Reg()) {
|
||||
e.mov(i.dest, i.src2.constant());
|
||||
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src1.constant()));
|
||||
} else if (i.src2.ConstantFitsIn32Reg()) {
|
||||
e.mov(i.dest, i.src1.constant());
|
||||
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src2.constant()));
|
||||
} else {
|
||||
e.mov(i.dest, i.src1.constant());
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, i.src2.constant());
|
||||
reg_reg_fn(e, i.dest, temp);
|
||||
}
|
||||
} else {
|
||||
// src1 constant.
|
||||
if (i.dest == i.src2) {
|
||||
if (i.src1.ConstantFitsIn32Reg()) {
|
||||
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src1.constant()));
|
||||
} else {
|
||||
auto temp = GetTempReg<typename decltype(i.src1)::reg_type>(e);
|
||||
e.mov(temp, i.src1.constant());
|
||||
reg_reg_fn(e, i.dest, temp);
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, i.src1.constant());
|
||||
reg_reg_fn(e, i.dest, i.src2);
|
||||
}
|
||||
}
|
||||
} else if (i.src2.is_constant) {
|
||||
if (i.dest == i.src1) {
|
||||
if (i.src2.ConstantFitsIn32Reg()) {
|
||||
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src2.constant()));
|
||||
} else {
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, i.src2.constant());
|
||||
reg_reg_fn(e, i.dest, temp);
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, i.src2.constant());
|
||||
reg_reg_fn(e, i.dest, i.src1);
|
||||
}
|
||||
} else {
|
||||
if (i.dest == i.src1) {
|
||||
reg_reg_fn(e, i.dest, i.src2);
|
||||
} else if (i.dest == i.src2) {
|
||||
reg_reg_fn(e, i.dest, i.src1);
|
||||
} else {
|
||||
e.mov(i.dest, i.src1);
|
||||
reg_reg_fn(e, i.dest, i.src2);
|
||||
}
|
||||
}
|
||||
}
|
||||
template <typename REG_REG_FN, typename REG_CONST_FN>
|
||||
static void EmitAssociativeBinaryOp(X64Emitter& e, const EmitArgType& i,
|
||||
const REG_REG_FN& reg_reg_fn,
|
||||
const REG_CONST_FN& reg_const_fn) {
|
||||
if (i.src1.is_constant) {
|
||||
assert_true(!i.src2.is_constant);
|
||||
if (i.dest == i.src2) {
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, i.src2);
|
||||
e.mov(i.dest, i.src1.constant());
|
||||
reg_reg_fn(e, i.dest, temp);
|
||||
} else {
|
||||
e.mov(i.dest, i.src1.constant());
|
||||
reg_reg_fn(e, i.dest, i.src2);
|
||||
}
|
||||
} else if (i.src2.is_constant) {
|
||||
if (i.dest == i.src1) {
|
||||
if (i.src2.ConstantFitsIn32Reg()) {
|
||||
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src2.constant()));
|
||||
} else {
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, i.src2.constant());
|
||||
reg_reg_fn(e, i.dest, temp);
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, i.src1);
|
||||
if (i.src2.ConstantFitsIn32Reg()) {
|
||||
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src2.constant()));
|
||||
} else {
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, i.src2.constant());
|
||||
reg_reg_fn(e, i.dest, temp);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (i.dest == i.src1) {
|
||||
reg_reg_fn(e, i.dest, i.src2);
|
||||
} else if (i.dest == i.src2) {
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, i.src2);
|
||||
e.mov(i.dest, i.src1);
|
||||
reg_reg_fn(e, i.dest, temp);
|
||||
} else {
|
||||
e.mov(i.dest, i.src1);
|
||||
reg_reg_fn(e, i.dest, i.src2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
static void EmitCommutativeBinaryXmmOp(X64Emitter& e, const EmitArgType& i,
|
||||
const FN& fn) {
|
||||
if (i.src1.is_constant) {
|
||||
assert_true(!i.src2.is_constant);
|
||||
e.LoadConstantXmm(e.xmm0, i.src1.constant());
|
||||
fn(e, i.dest, e.xmm0, i.src2);
|
||||
} else if (i.src2.is_constant) {
|
||||
assert_true(!i.src1.is_constant);
|
||||
e.LoadConstantXmm(e.xmm0, i.src2.constant());
|
||||
fn(e, i.dest, i.src1, e.xmm0);
|
||||
} else {
|
||||
fn(e, i.dest, i.src1, i.src2);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
static void EmitAssociativeBinaryXmmOp(X64Emitter& e, const EmitArgType& i,
|
||||
const FN& fn) {
|
||||
if (i.src1.is_constant) {
|
||||
assert_true(!i.src2.is_constant);
|
||||
e.LoadConstantXmm(e.xmm0, i.src1.constant());
|
||||
fn(e, i.dest, e.xmm0, i.src2);
|
||||
} else if (i.src2.is_constant) {
|
||||
assert_true(!i.src1.is_constant);
|
||||
e.LoadConstantXmm(e.xmm0, i.src2.constant());
|
||||
fn(e, i.dest, i.src1, e.xmm0);
|
||||
} else {
|
||||
fn(e, i.dest, i.src1, i.src2);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename REG_REG_FN, typename REG_CONST_FN>
|
||||
static void EmitCommutativeCompareOp(X64Emitter& e, const EmitArgType& i,
|
||||
const REG_REG_FN& reg_reg_fn,
|
||||
const REG_CONST_FN& reg_const_fn) {
|
||||
if (i.src1.is_constant) {
|
||||
assert_true(!i.src2.is_constant);
|
||||
if (i.src1.ConstantFitsIn32Reg()) {
|
||||
reg_const_fn(e, i.src2, static_cast<int32_t>(i.src1.constant()));
|
||||
} else {
|
||||
auto temp = GetTempReg<typename decltype(i.src1)::reg_type>(e);
|
||||
e.mov(temp, i.src1.constant());
|
||||
reg_reg_fn(e, i.src2, temp);
|
||||
}
|
||||
} else if (i.src2.is_constant) {
|
||||
assert_true(!i.src1.is_constant);
|
||||
if (i.src2.ConstantFitsIn32Reg()) {
|
||||
reg_const_fn(e, i.src1, static_cast<int32_t>(i.src2.constant()));
|
||||
} else {
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, i.src2.constant());
|
||||
reg_reg_fn(e, i.src1, temp);
|
||||
}
|
||||
} else {
|
||||
reg_reg_fn(e, i.src1, i.src2);
|
||||
}
|
||||
}
|
||||
template <typename REG_REG_FN, typename REG_CONST_FN>
|
||||
static void EmitAssociativeCompareOp(X64Emitter& e, const EmitArgType& i,
|
||||
const REG_REG_FN& reg_reg_fn,
|
||||
const REG_CONST_FN& reg_const_fn) {
|
||||
if (i.src1.is_constant) {
|
||||
assert_true(!i.src2.is_constant);
|
||||
if (i.src1.ConstantFitsIn32Reg()) {
|
||||
reg_const_fn(e, i.dest, i.src2, static_cast<int32_t>(i.src1.constant()),
|
||||
true);
|
||||
} else {
|
||||
auto temp = GetTempReg<typename decltype(i.src1)::reg_type>(e);
|
||||
e.mov(temp, i.src1.constant());
|
||||
reg_reg_fn(e, i.dest, i.src2, temp, true);
|
||||
}
|
||||
} else if (i.src2.is_constant) {
|
||||
assert_true(!i.src1.is_constant);
|
||||
if (i.src2.ConstantFitsIn32Reg()) {
|
||||
reg_const_fn(e, i.dest, i.src1, static_cast<int32_t>(i.src2.constant()),
|
||||
false);
|
||||
} else {
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, i.src2.constant());
|
||||
reg_reg_fn(e, i.dest, i.src1, temp, false);
|
||||
}
|
||||
} else {
|
||||
reg_reg_fn(e, i.dest, i.src1, i.src2, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace x64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_BACKEND_X64_X64_OP_H_
|
||||
553
src/xenia/cpu/backend/x64/x64_seq_control.cc
Normal file
553
src/xenia/cpu/backend/x64/x64_seq_control.cc
Normal file
@@ -0,0 +1,553 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2018 Xenia Developers. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/backend/x64/x64_sequences.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/cpu/backend/x64/x64_op.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
namespace x64 {
|
||||
|
||||
volatile int anchor_control = 0;
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_DEBUG_BREAK
|
||||
// ============================================================================
|
||||
struct DEBUG_BREAK : Sequence<DEBUG_BREAK, I<OPCODE_DEBUG_BREAK, VoidOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) { e.DebugBreak(); }
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_DEBUG_BREAK, DEBUG_BREAK);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_DEBUG_BREAK_TRUE
|
||||
// ============================================================================
|
||||
struct DEBUG_BREAK_TRUE_I8
|
||||
: Sequence<DEBUG_BREAK_TRUE_I8, I<OPCODE_DEBUG_BREAK_TRUE, VoidOp, I8Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.DebugBreak();
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct DEBUG_BREAK_TRUE_I16
|
||||
: Sequence<DEBUG_BREAK_TRUE_I16,
|
||||
I<OPCODE_DEBUG_BREAK_TRUE, VoidOp, I16Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.DebugBreak();
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct DEBUG_BREAK_TRUE_I32
|
||||
: Sequence<DEBUG_BREAK_TRUE_I32,
|
||||
I<OPCODE_DEBUG_BREAK_TRUE, VoidOp, I32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.DebugBreak();
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct DEBUG_BREAK_TRUE_I64
|
||||
: Sequence<DEBUG_BREAK_TRUE_I64,
|
||||
I<OPCODE_DEBUG_BREAK_TRUE, VoidOp, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.DebugBreak();
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct DEBUG_BREAK_TRUE_F32
|
||||
: Sequence<DEBUG_BREAK_TRUE_F32,
|
||||
I<OPCODE_DEBUG_BREAK_TRUE, VoidOp, F32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.DebugBreak();
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct DEBUG_BREAK_TRUE_F64
|
||||
: Sequence<DEBUG_BREAK_TRUE_F64,
|
||||
I<OPCODE_DEBUG_BREAK_TRUE, VoidOp, F64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.DebugBreak();
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_DEBUG_BREAK_TRUE, DEBUG_BREAK_TRUE_I8,
|
||||
DEBUG_BREAK_TRUE_I16, DEBUG_BREAK_TRUE_I32,
|
||||
DEBUG_BREAK_TRUE_I64, DEBUG_BREAK_TRUE_F32,
|
||||
DEBUG_BREAK_TRUE_F64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_TRAP
|
||||
// ============================================================================
|
||||
struct TRAP : Sequence<TRAP, I<OPCODE_TRAP, VoidOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.Trap(i.instr->flags);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_TRAP, TRAP);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_TRAP_TRUE
|
||||
// ============================================================================
|
||||
struct TRAP_TRUE_I8
|
||||
: Sequence<TRAP_TRUE_I8, I<OPCODE_TRAP_TRUE, VoidOp, I8Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Trap(i.instr->flags);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct TRAP_TRUE_I16
|
||||
: Sequence<TRAP_TRUE_I16, I<OPCODE_TRAP_TRUE, VoidOp, I16Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Trap(i.instr->flags);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct TRAP_TRUE_I32
|
||||
: Sequence<TRAP_TRUE_I32, I<OPCODE_TRAP_TRUE, VoidOp, I32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Trap(i.instr->flags);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct TRAP_TRUE_I64
|
||||
: Sequence<TRAP_TRUE_I64, I<OPCODE_TRAP_TRUE, VoidOp, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Trap(i.instr->flags);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct TRAP_TRUE_F32
|
||||
: Sequence<TRAP_TRUE_F32, I<OPCODE_TRAP_TRUE, VoidOp, F32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Trap(i.instr->flags);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct TRAP_TRUE_F64
|
||||
: Sequence<TRAP_TRUE_F64, I<OPCODE_TRAP_TRUE, VoidOp, F64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Trap(i.instr->flags);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_TRAP_TRUE, TRAP_TRUE_I8, TRAP_TRUE_I16,
|
||||
TRAP_TRUE_I32, TRAP_TRUE_I64, TRAP_TRUE_F32,
|
||||
TRAP_TRUE_F64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_CALL
|
||||
// ============================================================================
|
||||
struct CALL : Sequence<CALL, I<OPCODE_CALL, VoidOp, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src1.value->is_guest());
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src1.value));
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_CALL, CALL);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_CALL_TRUE
|
||||
// ============================================================================
|
||||
struct CALL_TRUE_I8
|
||||
: Sequence<CALL_TRUE_I8, I<OPCODE_CALL_TRUE, VoidOp, I8Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_I16
|
||||
: Sequence<CALL_TRUE_I16, I<OPCODE_CALL_TRUE, VoidOp, I16Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_I32
|
||||
: Sequence<CALL_TRUE_I32, I<OPCODE_CALL_TRUE, VoidOp, I32Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_I64
|
||||
: Sequence<CALL_TRUE_I64, I<OPCODE_CALL_TRUE, VoidOp, I64Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_F32
|
||||
: Sequence<CALL_TRUE_F32, I<OPCODE_CALL_TRUE, VoidOp, F32Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_F64
|
||||
: Sequence<CALL_TRUE_F64, I<OPCODE_CALL_TRUE, VoidOp, F64Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_CALL_TRUE, CALL_TRUE_I8, CALL_TRUE_I16,
|
||||
CALL_TRUE_I32, CALL_TRUE_I64, CALL_TRUE_F32,
|
||||
CALL_TRUE_F64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_CALL_INDIRECT
|
||||
// ============================================================================
|
||||
struct CALL_INDIRECT
|
||||
: Sequence<CALL_INDIRECT, I<OPCODE_CALL_INDIRECT, VoidOp, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.CallIndirect(i.instr, i.src1);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_CALL_INDIRECT, CALL_INDIRECT);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_CALL_INDIRECT_TRUE
|
||||
// ============================================================================
|
||||
struct CALL_INDIRECT_TRUE_I8
|
||||
: Sequence<CALL_INDIRECT_TRUE_I8,
|
||||
I<OPCODE_CALL_INDIRECT_TRUE, VoidOp, I8Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip, CodeGenerator::T_NEAR);
|
||||
e.CallIndirect(i.instr, i.src2);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_INDIRECT_TRUE_I16
|
||||
: Sequence<CALL_INDIRECT_TRUE_I16,
|
||||
I<OPCODE_CALL_INDIRECT_TRUE, VoidOp, I16Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip, CodeGenerator::T_NEAR);
|
||||
e.CallIndirect(i.instr, i.src2);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_INDIRECT_TRUE_I32
|
||||
: Sequence<CALL_INDIRECT_TRUE_I32,
|
||||
I<OPCODE_CALL_INDIRECT_TRUE, VoidOp, I32Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip, CodeGenerator::T_NEAR);
|
||||
e.CallIndirect(i.instr, i.src2);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_INDIRECT_TRUE_I64
|
||||
: Sequence<CALL_INDIRECT_TRUE_I64,
|
||||
I<OPCODE_CALL_INDIRECT_TRUE, VoidOp, I64Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip, CodeGenerator::T_NEAR);
|
||||
e.CallIndirect(i.instr, i.src2);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_INDIRECT_TRUE_F32
|
||||
: Sequence<CALL_INDIRECT_TRUE_F32,
|
||||
I<OPCODE_CALL_INDIRECT_TRUE, VoidOp, F32Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip, CodeGenerator::T_NEAR);
|
||||
e.CallIndirect(i.instr, i.src2);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_INDIRECT_TRUE_F64
|
||||
: Sequence<CALL_INDIRECT_TRUE_F64,
|
||||
I<OPCODE_CALL_INDIRECT_TRUE, VoidOp, F64Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip, CodeGenerator::T_NEAR);
|
||||
e.CallIndirect(i.instr, i.src2);
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_CALL_INDIRECT_TRUE, CALL_INDIRECT_TRUE_I8,
|
||||
CALL_INDIRECT_TRUE_I16, CALL_INDIRECT_TRUE_I32,
|
||||
CALL_INDIRECT_TRUE_I64, CALL_INDIRECT_TRUE_F32,
|
||||
CALL_INDIRECT_TRUE_F64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_CALL_EXTERN
|
||||
// ============================================================================
|
||||
struct CALL_EXTERN
|
||||
: Sequence<CALL_EXTERN, I<OPCODE_CALL_EXTERN, VoidOp, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.CallExtern(i.instr, i.src1.value);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_CALL_EXTERN, CALL_EXTERN);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_RETURN
|
||||
// ============================================================================
|
||||
struct RETURN : Sequence<RETURN, I<OPCODE_RETURN, VoidOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
// If this is the last instruction in the last block, just let us
|
||||
// fall through.
|
||||
if (i.instr->next || i.instr->block->next) {
|
||||
e.jmp(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
}
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_RETURN, RETURN);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_RETURN_TRUE
|
||||
// ============================================================================
|
||||
struct RETURN_TRUE_I8
|
||||
: Sequence<RETURN_TRUE_I8, I<OPCODE_RETURN_TRUE, VoidOp, I8Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jnz(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
}
|
||||
};
|
||||
struct RETURN_TRUE_I16
|
||||
: Sequence<RETURN_TRUE_I16, I<OPCODE_RETURN_TRUE, VoidOp, I16Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jnz(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
}
|
||||
};
|
||||
struct RETURN_TRUE_I32
|
||||
: Sequence<RETURN_TRUE_I32, I<OPCODE_RETURN_TRUE, VoidOp, I32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jnz(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
}
|
||||
};
|
||||
struct RETURN_TRUE_I64
|
||||
: Sequence<RETURN_TRUE_I64, I<OPCODE_RETURN_TRUE, VoidOp, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jnz(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
}
|
||||
};
|
||||
struct RETURN_TRUE_F32
|
||||
: Sequence<RETURN_TRUE_F32, I<OPCODE_RETURN_TRUE, VoidOp, F32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jnz(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
}
|
||||
};
|
||||
struct RETURN_TRUE_F64
|
||||
: Sequence<RETURN_TRUE_F64, I<OPCODE_RETURN_TRUE, VoidOp, F64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jnz(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_RETURN_TRUE, RETURN_TRUE_I8, RETURN_TRUE_I16,
|
||||
RETURN_TRUE_I32, RETURN_TRUE_I64, RETURN_TRUE_F32,
|
||||
RETURN_TRUE_F64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_SET_RETURN_ADDRESS
|
||||
// ============================================================================
|
||||
struct SET_RETURN_ADDRESS
|
||||
: Sequence<SET_RETURN_ADDRESS,
|
||||
I<OPCODE_SET_RETURN_ADDRESS, VoidOp, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.SetReturnAddress(i.src1.constant());
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_SET_RETURN_ADDRESS, SET_RETURN_ADDRESS);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_BRANCH
|
||||
// ============================================================================
|
||||
struct BRANCH : Sequence<BRANCH, I<OPCODE_BRANCH, VoidOp, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.jmp(i.src1.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_BRANCH, BRANCH);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_BRANCH_TRUE
|
||||
// ============================================================================
|
||||
struct BRANCH_TRUE_I8
|
||||
: Sequence<BRANCH_TRUE_I8, I<OPCODE_BRANCH_TRUE, VoidOp, I8Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_TRUE_I16
|
||||
: Sequence<BRANCH_TRUE_I16, I<OPCODE_BRANCH_TRUE, VoidOp, I16Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_TRUE_I32
|
||||
: Sequence<BRANCH_TRUE_I32, I<OPCODE_BRANCH_TRUE, VoidOp, I32Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_TRUE_I64
|
||||
: Sequence<BRANCH_TRUE_I64, I<OPCODE_BRANCH_TRUE, VoidOp, I64Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_TRUE_F32
|
||||
: Sequence<BRANCH_TRUE_F32, I<OPCODE_BRANCH_TRUE, VoidOp, F32Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_TRUE_F64
|
||||
: Sequence<BRANCH_TRUE_F64, I<OPCODE_BRANCH_TRUE, VoidOp, F64Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_BRANCH_TRUE, BRANCH_TRUE_I8, BRANCH_TRUE_I16,
|
||||
BRANCH_TRUE_I32, BRANCH_TRUE_I64, BRANCH_TRUE_F32,
|
||||
BRANCH_TRUE_F64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_BRANCH_FALSE
|
||||
// ============================================================================
|
||||
struct BRANCH_FALSE_I8
|
||||
: Sequence<BRANCH_FALSE_I8, I<OPCODE_BRANCH_FALSE, VoidOp, I8Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_FALSE_I16
|
||||
: Sequence<BRANCH_FALSE_I16,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, I16Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_FALSE_I32
|
||||
: Sequence<BRANCH_FALSE_I32,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, I32Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_FALSE_I64
|
||||
: Sequence<BRANCH_FALSE_I64,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, I64Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.test(i.src1, i.src1);
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_FALSE_F32
|
||||
: Sequence<BRANCH_FALSE_F32,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, F32Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_FALSE_F64
|
||||
: Sequence<BRANCH_FALSE_F64,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, F64Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_BRANCH_FALSE, BRANCH_FALSE_I8, BRANCH_FALSE_I16,
|
||||
BRANCH_FALSE_I32, BRANCH_FALSE_I64, BRANCH_FALSE_F32,
|
||||
BRANCH_FALSE_F64);
|
||||
|
||||
} // namespace x64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
1053
src/xenia/cpu/backend/x64/x64_seq_memory.cc
Normal file
1053
src/xenia/cpu/backend/x64/x64_seq_memory.cc
Normal file
File diff suppressed because it is too large
Load Diff
2553
src/xenia/cpu/backend/x64/x64_seq_vector.cc
Normal file
2553
src/xenia/cpu/backend/x64/x64_seq_vector.cc
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,8 @@
|
||||
|
||||
#include "xenia/cpu/hir/instr.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
@@ -19,7 +21,25 @@ namespace x64 {
|
||||
|
||||
class X64Emitter;
|
||||
|
||||
void RegisterSequences();
|
||||
typedef bool (*SequenceSelectFn)(X64Emitter&, const hir::Instr*);
|
||||
extern std::unordered_map<uint32_t, SequenceSelectFn> sequence_table;
|
||||
|
||||
template <typename T>
|
||||
bool Register() {
|
||||
sequence_table.insert({T::head_key(), T::Select});
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename Tn, typename... Ts>
|
||||
static bool Register() {
|
||||
bool b = true;
|
||||
b = b && Register<T>(); // Call the above function
|
||||
b = b && Register<Tn, Ts...>(); // Call ourself again (recursively)
|
||||
return b;
|
||||
}
|
||||
#define EMITTER_OPCODE_TABLE(name, ...) \
|
||||
const auto X64_INSTR_##name = Register<__VA_ARGS__>();
|
||||
|
||||
bool SelectSequence(X64Emitter* e, const hir::Instr* i,
|
||||
const hir::Instr** new_tail);
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#ifndef XENIA_CPU_COMPILER_COMPILER_PASSES_H_
|
||||
#define XENIA_CPU_COMPILER_COMPILER_PASSES_H_
|
||||
|
||||
#include "xenia/cpu/compiler/passes/conditional_group_pass.h"
|
||||
#include "xenia/cpu/compiler/passes/conditional_group_subpass.h"
|
||||
#include "xenia/cpu/compiler/passes/constant_propagation_pass.h"
|
||||
#include "xenia/cpu/compiler/passes/context_promotion_pass.h"
|
||||
#include "xenia/cpu/compiler/passes/control_flow_analysis_pass.h"
|
||||
|
||||
85
src/xenia/cpu/compiler/passes/conditional_group_pass.cc
Normal file
85
src/xenia/cpu/compiler/passes/conditional_group_pass.cc
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/compiler/passes/conditional_group_pass.h"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/cpu/compiler/compiler.h"
|
||||
#include "xenia/cpu/ppc/ppc_context.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace compiler {
|
||||
namespace passes {
|
||||
|
||||
// TODO(benvanik): remove when enums redefined.
|
||||
using namespace xe::cpu::hir;
|
||||
|
||||
using xe::cpu::hir::Block;
|
||||
using xe::cpu::hir::HIRBuilder;
|
||||
using xe::cpu::hir::Instr;
|
||||
using xe::cpu::hir::Value;
|
||||
|
||||
ConditionalGroupPass::ConditionalGroupPass() : CompilerPass() {}
|
||||
|
||||
ConditionalGroupPass::~ConditionalGroupPass() {}
|
||||
|
||||
bool ConditionalGroupPass::Initialize(Compiler* compiler) {
|
||||
if (!CompilerPass::Initialize(compiler)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < passes_.size(); ++i) {
|
||||
auto& pass = passes_[i];
|
||||
if (!pass->Initialize(compiler)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConditionalGroupPass::Run(HIRBuilder* builder) {
|
||||
bool dirty;
|
||||
int loops = 0;
|
||||
do {
|
||||
assert_true(loops < 20); // arbitrary number
|
||||
dirty = false;
|
||||
for (size_t i = 0; i < passes_.size(); ++i) {
|
||||
scratch_arena()->Reset();
|
||||
auto& pass = passes_[i];
|
||||
auto subpass = dynamic_cast<ConditionalGroupSubpass*>(pass.get());
|
||||
if (!subpass) {
|
||||
if (!pass->Run(builder)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
bool result = false;
|
||||
if (!subpass->Run(builder, result)) {
|
||||
return false;
|
||||
}
|
||||
dirty |= result;
|
||||
}
|
||||
}
|
||||
loops++;
|
||||
} while (dirty);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConditionalGroupPass::AddPass(std::unique_ptr<CompilerPass> pass) {
|
||||
passes_.push_back(std::move(pass));
|
||||
}
|
||||
|
||||
} // namespace passes
|
||||
} // namespace compiler
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
45
src/xenia/cpu/compiler/passes/conditional_group_pass.h
Normal file
45
src/xenia/cpu/compiler/passes/conditional_group_pass.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_COMPILER_PASSES_CONDITIONAL_GROUP_PASS_H_
|
||||
#define XENIA_CPU_COMPILER_PASSES_CONDITIONAL_GROUP_PASS_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/cpu/compiler/compiler_pass.h"
|
||||
#include "xenia/cpu/compiler/passes/conditional_group_subpass.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace compiler {
|
||||
namespace passes {
|
||||
|
||||
class ConditionalGroupPass : public CompilerPass {
|
||||
public:
|
||||
ConditionalGroupPass();
|
||||
virtual ~ConditionalGroupPass() override;
|
||||
|
||||
bool Initialize(Compiler* compiler) override;
|
||||
|
||||
bool Run(hir::HIRBuilder* builder) override;
|
||||
|
||||
void AddPass(std::unique_ptr<CompilerPass> pass);
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<CompilerPass>> passes_;
|
||||
};
|
||||
|
||||
} // namespace passes
|
||||
} // namespace compiler
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_COMPILER_PASSES_CONDITIONAL_GROUP_PASS_H_
|
||||
26
src/xenia/cpu/compiler/passes/conditional_group_subpass.cc
Normal file
26
src/xenia/cpu/compiler/passes/conditional_group_subpass.cc
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/compiler/passes/conditional_group_subpass.h"
|
||||
|
||||
#include "xenia/cpu/compiler/compiler.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace compiler {
|
||||
namespace passes {
|
||||
|
||||
ConditionalGroupSubpass::ConditionalGroupSubpass() : CompilerPass() {}
|
||||
|
||||
ConditionalGroupSubpass::~ConditionalGroupSubpass() = default;
|
||||
|
||||
} // namespace passes
|
||||
} // namespace compiler
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
47
src/xenia/cpu/compiler/passes/conditional_group_subpass.h
Normal file
47
src/xenia/cpu/compiler/passes/conditional_group_subpass.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_COMPILER_PASSES_CONDITIONAL_GROUP_SUBPASS_H_
|
||||
#define XENIA_CPU_COMPILER_PASSES_CONDITIONAL_GROUP_SUBPASS_H_
|
||||
|
||||
#include "xenia/base/arena.h"
|
||||
#include "xenia/cpu/compiler/compiler_pass.h"
|
||||
#include "xenia/cpu/hir/hir_builder.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
class Processor;
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace compiler {
|
||||
class Compiler;
|
||||
namespace passes {
|
||||
|
||||
class ConditionalGroupSubpass : public CompilerPass {
|
||||
public:
|
||||
ConditionalGroupSubpass();
|
||||
virtual ~ConditionalGroupSubpass();
|
||||
|
||||
bool Run(hir::HIRBuilder* builder) override {
|
||||
bool dummy;
|
||||
return Run(builder, dummy);
|
||||
}
|
||||
|
||||
virtual bool Run(hir::HIRBuilder* builder, bool& result) = 0;
|
||||
};
|
||||
|
||||
} // namespace passes
|
||||
} // namespace compiler
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_COMPILER_PASSES_CONDITIONAL_GROUP_SUBPASS_H_
|
||||
@@ -31,11 +31,12 @@ using xe::cpu::hir::HIRBuilder;
|
||||
using xe::cpu::hir::TypeName;
|
||||
using xe::cpu::hir::Value;
|
||||
|
||||
ConstantPropagationPass::ConstantPropagationPass() : CompilerPass() {}
|
||||
ConstantPropagationPass::ConstantPropagationPass()
|
||||
: ConditionalGroupSubpass() {}
|
||||
|
||||
ConstantPropagationPass::~ConstantPropagationPass() {}
|
||||
|
||||
bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool ConstantPropagationPass::Run(HIRBuilder* builder, bool& result) {
|
||||
// Once ContextPromotion has run there will likely be a whole slew of
|
||||
// constants that can be pushed through the function.
|
||||
// Example:
|
||||
@@ -63,6 +64,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
// v1 = 19
|
||||
// v2 = 0
|
||||
|
||||
result = false;
|
||||
auto block = builder->first_block();
|
||||
while (block) {
|
||||
auto i = block->instr_head;
|
||||
@@ -76,6 +78,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
} else {
|
||||
i->Remove();
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -86,6 +89,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
} else {
|
||||
i->Remove();
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -98,6 +102,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
} else {
|
||||
i->Remove();
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_CALL_INDIRECT:
|
||||
@@ -109,6 +114,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
}
|
||||
i->Replace(&OPCODE_CALL_info, i->flags);
|
||||
i->src1.symbol = function;
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_CALL_INDIRECT_TRUE:
|
||||
@@ -120,6 +126,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
} else {
|
||||
i->Remove();
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -132,6 +139,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
} else {
|
||||
i->Remove();
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_BRANCH_FALSE:
|
||||
@@ -143,6 +151,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
} else {
|
||||
i->Remove();
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -152,6 +161,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Cast(target_type);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_CONVERT:
|
||||
@@ -160,6 +170,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Convert(target_type, RoundMode(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_ROUND:
|
||||
@@ -167,6 +178,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Round(RoundMode(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_ZERO_EXTEND:
|
||||
@@ -175,6 +187,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->ZeroExtend(target_type);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_SIGN_EXTEND:
|
||||
@@ -183,6 +196,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->SignExtend(target_type);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_TRUNCATE:
|
||||
@@ -191,6 +205,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Truncate(target_type);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -210,6 +225,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
i->Replace(&OPCODE_LOAD_MMIO_info, 0);
|
||||
i->src1.offset = reinterpret_cast<uint64_t>(mmio_range);
|
||||
i->src2.offset = address;
|
||||
result = true;
|
||||
} else {
|
||||
auto heap = memory->LookupHeap(address);
|
||||
uint32_t protect;
|
||||
@@ -222,18 +238,22 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
case INT8_TYPE:
|
||||
v->set_constant(xe::load<uint8_t>(host_addr));
|
||||
i->Remove();
|
||||
result = true;
|
||||
break;
|
||||
case INT16_TYPE:
|
||||
v->set_constant(xe::load<uint16_t>(host_addr));
|
||||
i->Remove();
|
||||
result = true;
|
||||
break;
|
||||
case INT32_TYPE:
|
||||
v->set_constant(xe::load<uint32_t>(host_addr));
|
||||
i->Remove();
|
||||
result = true;
|
||||
break;
|
||||
case INT64_TYPE:
|
||||
v->set_constant(xe::load<uint64_t>(host_addr));
|
||||
i->Remove();
|
||||
result = true;
|
||||
break;
|
||||
case VEC128_TYPE:
|
||||
vec128_t val;
|
||||
@@ -241,6 +261,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
val.high = xe::load<uint64_t>(host_addr + 8);
|
||||
v->set_constant(val);
|
||||
i->Remove();
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(v->type);
|
||||
@@ -270,6 +291,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
i->src1.offset = reinterpret_cast<uint64_t>(mmio_range);
|
||||
i->src2.offset = address;
|
||||
i->set_src3(value);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -281,10 +303,12 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
auto src2 = i->src2.value;
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(src2);
|
||||
result = true;
|
||||
} else if (i->src1.value->IsConstantFalse()) {
|
||||
auto src3 = i->src3.value;
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(src3);
|
||||
result = true;
|
||||
} else if (i->src2.value->IsConstant() &&
|
||||
i->src3.value->IsConstant()) {
|
||||
// TODO: Select
|
||||
@@ -305,6 +329,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_constant(uint8_t(0));
|
||||
}
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_IS_FALSE:
|
||||
@@ -315,6 +340,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_constant(uint8_t(0));
|
||||
}
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_IS_NAN:
|
||||
@@ -329,6 +355,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_constant(uint8_t(0));
|
||||
}
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -338,6 +365,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantEQ(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_NE:
|
||||
@@ -345,6 +373,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantNE(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_SLT:
|
||||
@@ -352,6 +381,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantSLT(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_SLE:
|
||||
@@ -359,6 +389,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantSLE(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_SGT:
|
||||
@@ -366,6 +397,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantSGT(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_SGE:
|
||||
@@ -373,6 +405,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantSGE(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_ULT:
|
||||
@@ -380,6 +413,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantULT(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_ULE:
|
||||
@@ -387,6 +421,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantULE(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_UGT:
|
||||
@@ -394,6 +429,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantUGT(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_COMPARE_UGE:
|
||||
@@ -401,6 +437,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
bool value = i->src1.value->IsConstantUGE(i->src2.value);
|
||||
i->dest->set_constant(uint8_t(value));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -413,6 +450,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Add(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_ADD_CARRY:
|
||||
@@ -433,6 +471,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
i->set_src1(ca);
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_SUB:
|
||||
@@ -440,6 +479,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Sub(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_MUL:
|
||||
@@ -447,6 +487,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Mul(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
} else if (i->src1.value->IsConstant() ||
|
||||
i->src2.value->IsConstant()) {
|
||||
// Reorder the sources to make things simpler.
|
||||
@@ -460,12 +501,14 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
if (s2->type != VEC128_TYPE && s2->IsConstantOne()) {
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(s1);
|
||||
result = true;
|
||||
} else if (s2->type == VEC128_TYPE) {
|
||||
auto& c = s2->constant;
|
||||
if (c.v128.f32[0] == 1.f && c.v128.f32[1] == 1.f &&
|
||||
c.v128.f32[2] == 1.f && c.v128.f32[3] == 1.f) {
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(s1);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,6 +518,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->MulHi(i->src2.value, (i->flags & ARITHMETIC_UNSIGNED) != 0);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_DIV:
|
||||
@@ -482,6 +526,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Div(i->src2.value, (i->flags & ARITHMETIC_UNSIGNED) != 0);
|
||||
i->Remove();
|
||||
result = true;
|
||||
} else if (i->src2.value->IsConstant()) {
|
||||
// Division by one = no-op.
|
||||
Value* src1 = i->src1.value;
|
||||
@@ -489,12 +534,14 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
i->src2.value->IsConstantOne()) {
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(src1);
|
||||
result = true;
|
||||
} else if (i->src2.value->type == VEC128_TYPE) {
|
||||
auto& c = i->src2.value->constant;
|
||||
if (c.v128.f32[0] == 1.f && c.v128.f32[1] == 1.f &&
|
||||
c.v128.f32[2] == 1.f && c.v128.f32[3] == 1.f) {
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(src1);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -505,6 +552,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
Value::MulAdd(v, i->src1.value, i->src2.value, i->src3.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
} else {
|
||||
// Multiply part is constant.
|
||||
Value* mul = builder->AllocValue();
|
||||
@@ -515,6 +563,8 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
i->Replace(&OPCODE_ADD_info, 0);
|
||||
i->set_src1(mul);
|
||||
i->set_src2(add);
|
||||
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -525,6 +575,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
Value::MulSub(v, i->src1.value, i->src2.value, i->src3.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
} else {
|
||||
// Multiply part is constant.
|
||||
Value* mul = builder->AllocValue();
|
||||
@@ -535,6 +586,8 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
i->Replace(&OPCODE_SUB_info, 0);
|
||||
i->set_src1(mul);
|
||||
i->set_src2(add);
|
||||
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -543,6 +596,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Max(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_NEG:
|
||||
@@ -550,6 +604,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Neg();
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_ABS:
|
||||
@@ -557,6 +612,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Abs();
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_SQRT:
|
||||
@@ -564,6 +620,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Sqrt();
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_RSQRT:
|
||||
@@ -571,6 +628,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->RSqrt();
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_RECIP:
|
||||
@@ -578,6 +636,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Recip();
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_AND:
|
||||
@@ -585,6 +644,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->And(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_OR:
|
||||
@@ -592,6 +652,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Or(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_XOR:
|
||||
@@ -599,11 +660,13 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Xor(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
} else if (!i->src1.value->IsConstant() &&
|
||||
!i->src2.value->IsConstant() &&
|
||||
i->src1.value == i->src2.value) {
|
||||
v->set_zero(v->type);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_NOT:
|
||||
@@ -611,6 +674,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Not();
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_SHL:
|
||||
@@ -618,10 +682,12 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Shl(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
} else if (i->src2.value->IsConstantZero()) {
|
||||
auto src1 = i->src1.value;
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(src1);
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_SHR:
|
||||
@@ -629,10 +695,12 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Shr(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
} else if (i->src2.value->IsConstantZero()) {
|
||||
auto src1 = i->src1.value;
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(src1);
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_SHA:
|
||||
@@ -640,6 +708,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->Sha(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
// TODO(benvanik): ROTATE_LEFT
|
||||
@@ -648,6 +717,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->ByteSwap();
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_CNTLZ:
|
||||
@@ -655,6 +725,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_zero(v->type);
|
||||
v->CountLeadingZeros(i->src1.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
// TODO(benvanik): INSERT/EXTRACT
|
||||
@@ -664,6 +735,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_zero(v->type);
|
||||
v->Extract(i->src1.value, i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_SPLAT:
|
||||
@@ -671,6 +743,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_zero(v->type);
|
||||
v->Splat(i->src1.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_COMPARE_EQ:
|
||||
@@ -678,6 +751,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->VectorCompareEQ(i->src2.value, hir::TypeName(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_COMPARE_SGT:
|
||||
@@ -685,6 +759,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->VectorCompareSGT(i->src2.value, hir::TypeName(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_COMPARE_SGE:
|
||||
@@ -692,6 +767,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->VectorCompareSGE(i->src2.value, hir::TypeName(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_COMPARE_UGT:
|
||||
@@ -699,6 +775,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->VectorCompareUGT(i->src2.value, hir::TypeName(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_COMPARE_UGE:
|
||||
@@ -706,6 +783,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->VectorCompareUGE(i->src2.value, hir::TypeName(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_CONVERT_F2I:
|
||||
@@ -714,6 +792,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->VectorConvertF2I(i->src1.value,
|
||||
!!(i->flags & ARITHMETIC_UNSIGNED));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_CONVERT_I2F:
|
||||
@@ -722,6 +801,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->VectorConvertI2F(i->src1.value,
|
||||
!!(i->flags & ARITHMETIC_UNSIGNED));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_SHL:
|
||||
@@ -729,6 +809,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->VectorShl(i->src2.value, hir::TypeName(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_SHR:
|
||||
@@ -736,6 +817,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->VectorShr(i->src2.value, hir::TypeName(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_ROTATE_LEFT:
|
||||
@@ -743,6 +825,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->VectorRol(i->src2.value, hir::TypeName(i->flags));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_ADD:
|
||||
@@ -753,6 +836,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
!!(arith_flags & ARITHMETIC_UNSIGNED),
|
||||
!!(arith_flags & ARITHMETIC_SATURATE));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_VECTOR_SUB:
|
||||
@@ -763,6 +847,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
!!(arith_flags & ARITHMETIC_UNSIGNED),
|
||||
!!(arith_flags & ARITHMETIC_SATURATE));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -771,6 +856,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->DotProduct3(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -779,6 +865,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
v->set_from(i->src1.value);
|
||||
v->DotProduct4(i->src2.value);
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -790,6 +877,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
||||
!!(arith_flags & ARITHMETIC_UNSIGNED),
|
||||
!!(arith_flags & ARITHMETIC_SATURATE));
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -10,19 +10,19 @@
|
||||
#ifndef XENIA_CPU_COMPILER_PASSES_CONSTANT_PROPAGATION_PASS_H_
|
||||
#define XENIA_CPU_COMPILER_PASSES_CONSTANT_PROPAGATION_PASS_H_
|
||||
|
||||
#include "xenia/cpu/compiler/compiler_pass.h"
|
||||
#include "xenia/cpu/compiler/passes/conditional_group_subpass.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace compiler {
|
||||
namespace passes {
|
||||
|
||||
class ConstantPropagationPass : public CompilerPass {
|
||||
class ConstantPropagationPass : public ConditionalGroupSubpass {
|
||||
public:
|
||||
ConstantPropagationPass();
|
||||
~ConstantPropagationPass() override;
|
||||
|
||||
bool Run(hir::HIRBuilder* builder) override;
|
||||
bool Run(hir::HIRBuilder* builder, bool& result) override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
@@ -23,17 +23,18 @@ using xe::cpu::hir::HIRBuilder;
|
||||
using xe::cpu::hir::Instr;
|
||||
using xe::cpu::hir::Value;
|
||||
|
||||
SimplificationPass::SimplificationPass() : CompilerPass() {}
|
||||
SimplificationPass::SimplificationPass() : ConditionalGroupSubpass() {}
|
||||
|
||||
SimplificationPass::~SimplificationPass() {}
|
||||
|
||||
bool SimplificationPass::Run(HIRBuilder* builder) {
|
||||
EliminateConversions(builder);
|
||||
SimplifyAssignments(builder);
|
||||
bool SimplificationPass::Run(HIRBuilder* builder, bool& result) {
|
||||
result = false;
|
||||
result |= EliminateConversions(builder);
|
||||
result |= SimplifyAssignments(builder);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SimplificationPass::EliminateConversions(HIRBuilder* builder) {
|
||||
bool SimplificationPass::EliminateConversions(HIRBuilder* builder) {
|
||||
// First, we check for truncates/extensions that can be skipped.
|
||||
// This generates some assignments which then the second step will clean up.
|
||||
// Both zero/sign extends can be skipped:
|
||||
@@ -43,6 +44,7 @@ void SimplificationPass::EliminateConversions(HIRBuilder* builder) {
|
||||
// v1.i64 = zero/sign_extend v0.i32 (may be dead code removed later)
|
||||
// v2.i32 = v0.i32
|
||||
|
||||
bool result = false;
|
||||
auto block = builder->first_block();
|
||||
while (block) {
|
||||
auto i = block->instr_head;
|
||||
@@ -51,20 +53,21 @@ void SimplificationPass::EliminateConversions(HIRBuilder* builder) {
|
||||
// back to definition).
|
||||
if (i->opcode == &OPCODE_TRUNCATE_info) {
|
||||
// Matches zero/sign_extend + truncate.
|
||||
CheckTruncate(i);
|
||||
result |= CheckTruncate(i);
|
||||
} else if (i->opcode == &OPCODE_BYTE_SWAP_info) {
|
||||
// Matches byte swap + byte swap.
|
||||
// This is pretty rare within the same basic block, but is in the
|
||||
// memcpy hot path and (probably) worth it. Maybe.
|
||||
CheckByteSwap(i);
|
||||
result |= CheckByteSwap(i);
|
||||
}
|
||||
i = i->next;
|
||||
}
|
||||
block = block->next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void SimplificationPass::CheckTruncate(Instr* i) {
|
||||
bool SimplificationPass::CheckTruncate(Instr* i) {
|
||||
// Walk backward up src's chain looking for an extend. We may have
|
||||
// assigns, so skip those.
|
||||
auto src = i->src1.value;
|
||||
@@ -80,6 +83,7 @@ void SimplificationPass::CheckTruncate(Instr* i) {
|
||||
// Types match, use original by turning this into an assign.
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(def->src1.value);
|
||||
return true;
|
||||
}
|
||||
} else if (def->opcode == &OPCODE_ZERO_EXTEND_info) {
|
||||
// Value comes from a zero extend.
|
||||
@@ -87,12 +91,14 @@ void SimplificationPass::CheckTruncate(Instr* i) {
|
||||
// Types match, use original by turning this into an assign.
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(def->src1.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SimplificationPass::CheckByteSwap(Instr* i) {
|
||||
bool SimplificationPass::CheckByteSwap(Instr* i) {
|
||||
// Walk backward up src's chain looking for a byte swap. We may have
|
||||
// assigns, so skip those.
|
||||
auto src = i->src1.value;
|
||||
@@ -107,11 +113,13 @@ void SimplificationPass::CheckByteSwap(Instr* i) {
|
||||
// Types match, use original by turning this into an assign.
|
||||
i->Replace(&OPCODE_ASSIGN_info, 0);
|
||||
i->set_src1(def->src1.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SimplificationPass::SimplifyAssignments(HIRBuilder* builder) {
|
||||
bool SimplificationPass::SimplifyAssignments(HIRBuilder* builder) {
|
||||
// Run over the instructions and rename assigned variables:
|
||||
// v1 = v0
|
||||
// v2 = v1
|
||||
@@ -129,27 +137,35 @@ void SimplificationPass::SimplifyAssignments(HIRBuilder* builder) {
|
||||
// of that instr. Because we may have chains, we do this recursively until
|
||||
// we find a non-assign def.
|
||||
|
||||
bool result = false;
|
||||
auto block = builder->first_block();
|
||||
while (block) {
|
||||
auto i = block->instr_head;
|
||||
while (i) {
|
||||
uint32_t signature = i->opcode->signature;
|
||||
if (GET_OPCODE_SIG_TYPE_SRC1(signature) == OPCODE_SIG_TYPE_V) {
|
||||
i->set_src1(CheckValue(i->src1.value));
|
||||
bool modified = false;
|
||||
i->set_src1(CheckValue(i->src1.value, modified));
|
||||
result |= modified;
|
||||
}
|
||||
if (GET_OPCODE_SIG_TYPE_SRC2(signature) == OPCODE_SIG_TYPE_V) {
|
||||
i->set_src2(CheckValue(i->src2.value));
|
||||
bool modified = false;
|
||||
i->set_src2(CheckValue(i->src2.value, modified));
|
||||
result |= modified;
|
||||
}
|
||||
if (GET_OPCODE_SIG_TYPE_SRC3(signature) == OPCODE_SIG_TYPE_V) {
|
||||
i->set_src3(CheckValue(i->src3.value));
|
||||
bool modified = false;
|
||||
i->set_src3(CheckValue(i->src3.value, modified));
|
||||
result |= modified;
|
||||
}
|
||||
i = i->next;
|
||||
}
|
||||
block = block->next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Value* SimplificationPass::CheckValue(Value* value) {
|
||||
Value* SimplificationPass::CheckValue(Value* value, bool& result) {
|
||||
auto def = value->def;
|
||||
if (def && def->opcode == &OPCODE_ASSIGN_info) {
|
||||
// Value comes from an assignment - recursively find if it comes from
|
||||
@@ -162,8 +178,10 @@ Value* SimplificationPass::CheckValue(Value* value) {
|
||||
}
|
||||
replacement = def->src1.value;
|
||||
}
|
||||
result = true;
|
||||
return replacement;
|
||||
}
|
||||
result = false;
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,27 +10,27 @@
|
||||
#ifndef XENIA_CPU_COMPILER_PASSES_SIMPLIFICATION_PASS_H_
|
||||
#define XENIA_CPU_COMPILER_PASSES_SIMPLIFICATION_PASS_H_
|
||||
|
||||
#include "xenia/cpu/compiler/compiler_pass.h"
|
||||
#include "xenia/cpu/compiler/passes/conditional_group_subpass.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace compiler {
|
||||
namespace passes {
|
||||
|
||||
class SimplificationPass : public CompilerPass {
|
||||
class SimplificationPass : public ConditionalGroupSubpass {
|
||||
public:
|
||||
SimplificationPass();
|
||||
~SimplificationPass() override;
|
||||
|
||||
bool Run(hir::HIRBuilder* builder) override;
|
||||
bool Run(hir::HIRBuilder* builder, bool& result) override;
|
||||
|
||||
private:
|
||||
void EliminateConversions(hir::HIRBuilder* builder);
|
||||
void CheckTruncate(hir::Instr* i);
|
||||
void CheckByteSwap(hir::Instr* i);
|
||||
bool EliminateConversions(hir::HIRBuilder* builder);
|
||||
bool CheckTruncate(hir::Instr* i);
|
||||
bool CheckByteSwap(hir::Instr* i);
|
||||
|
||||
void SimplifyAssignments(hir::HIRBuilder* builder);
|
||||
hir::Value* CheckValue(hir::Value* value);
|
||||
bool SimplifyAssignments(hir::HIRBuilder* builder);
|
||||
hir::Value* CheckValue(hir::Value* value, bool& result);
|
||||
};
|
||||
|
||||
} // namespace passes
|
||||
|
||||
@@ -170,6 +170,7 @@ class Value {
|
||||
constant.v128 = value;
|
||||
}
|
||||
void set_from(const Value* other) {
|
||||
assert_true(other->IsConstant());
|
||||
type = other->type;
|
||||
flags = other->flags;
|
||||
constant.v128 = other->constant.v128;
|
||||
|
||||
@@ -53,15 +53,16 @@ PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) {
|
||||
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
|
||||
compiler_->AddPass(std::make_unique<passes::ContextPromotionPass>());
|
||||
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
|
||||
// TODO(gibbed): loop until these passes stop making changes?
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
compiler_->AddPass(std::make_unique<passes::SimplificationPass>());
|
||||
if (validate)
|
||||
compiler_->AddPass(std::make_unique<passes::ValidationPass>());
|
||||
compiler_->AddPass(std::make_unique<passes::ConstantPropagationPass>());
|
||||
if (validate)
|
||||
compiler_->AddPass(std::make_unique<passes::ValidationPass>());
|
||||
}
|
||||
|
||||
// Grouped simplification + constant propagation.
|
||||
// Loops until no changes are made.
|
||||
auto sap = std::make_unique<passes::ConditionalGroupPass>();
|
||||
sap->AddPass(std::make_unique<passes::SimplificationPass>());
|
||||
if (validate) sap->AddPass(std::make_unique<passes::ValidationPass>());
|
||||
sap->AddPass(std::make_unique<passes::ConstantPropagationPass>());
|
||||
if (validate) sap->AddPass(std::make_unique<passes::ValidationPass>());
|
||||
compiler_->AddPass(std::move(sap));
|
||||
|
||||
if (backend->machine_info()->supports_extended_load_store) {
|
||||
// Backend supports the advanced LOAD/STORE instructions.
|
||||
// These will save us a lot of HIR opcodes.
|
||||
|
||||
@@ -13,6 +13,7 @@ project("xenia-cpu-ppc-tests")
|
||||
"xenia-base",
|
||||
"gflags",
|
||||
"capstone", -- cpu-backend-x64
|
||||
"mspack",
|
||||
})
|
||||
files({
|
||||
"ppc_testing_main.cc",
|
||||
|
||||
@@ -8,6 +8,7 @@ project("xenia-cpu")
|
||||
language("C++")
|
||||
links({
|
||||
"xenia-base",
|
||||
"mspack",
|
||||
})
|
||||
includedirs({
|
||||
project_root.."/third_party/llvm/include",
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "third_party/crypto/rijndael-alg-fst.c"
|
||||
#include "third_party/crypto/rijndael-alg-fst.h"
|
||||
#include "third_party/mspack/lzx.h"
|
||||
#include "third_party/mspack/lzxd.c"
|
||||
#include "third_party/mspack/mspack.h"
|
||||
#include "third_party/pe/pe_image.h"
|
||||
|
||||
@@ -120,7 +119,7 @@ int lzx_decompress(const void* lzx_data, size_t lzx_len, void* dest,
|
||||
mspack_memory_file* lzxdst = mspack_memory_open(sys, dest, dest_len);
|
||||
lzxd_stream* lzxd =
|
||||
lzxd_init(sys, (struct mspack_file*)lzxsrc, (struct mspack_file*)lzxdst,
|
||||
window_bits, 0, 0x8000, (off_t)dest_len);
|
||||
window_bits, 0, 0x8000, (off_t)dest_len, 0);
|
||||
|
||||
if (lzxd) {
|
||||
if (window_data) {
|
||||
@@ -1120,23 +1119,23 @@ bool XexModule::LoadContinue() {
|
||||
processor_->backend()->CommitExecutableRange(low_address_, high_address_);
|
||||
|
||||
// Add all imports (variables/functions).
|
||||
xex2_opt_import_libraries* opt_import_header = nullptr;
|
||||
GetOptHeader(XEX_HEADER_IMPORT_LIBRARIES, &opt_import_header);
|
||||
xex2_opt_import_libraries* opt_import_libraries = nullptr;
|
||||
GetOptHeader(XEX_HEADER_IMPORT_LIBRARIES, &opt_import_libraries);
|
||||
|
||||
if (opt_import_header) {
|
||||
if (opt_import_libraries) {
|
||||
// FIXME: Don't know if 32 is the actual limit, but haven't seen more than
|
||||
// 2.
|
||||
const char* string_table[32];
|
||||
std::memset(string_table, 0, sizeof(string_table));
|
||||
size_t max_string_table_index = 0;
|
||||
|
||||
// Parse the string table
|
||||
for (size_t i = 0; i < opt_import_header->string_table_size;
|
||||
++max_string_table_index) {
|
||||
assert_true(max_string_table_index < xe::countof(string_table));
|
||||
const char* str = opt_import_header->string_table + i;
|
||||
for (size_t i = 0, o = 0; i < opt_import_libraries->string_table.size &&
|
||||
o < opt_import_libraries->string_table.count;
|
||||
++o) {
|
||||
assert_true(o < xe::countof(string_table));
|
||||
const char* str = &opt_import_libraries->string_table.data[i];
|
||||
|
||||
string_table[max_string_table_index] = str;
|
||||
string_table[o] = str;
|
||||
i += std::strlen(str) + 1;
|
||||
|
||||
// Padding
|
||||
@@ -1145,15 +1144,19 @@ bool XexModule::LoadContinue() {
|
||||
}
|
||||
}
|
||||
|
||||
auto libraries_ptr = reinterpret_cast<uint8_t*>(opt_import_header) +
|
||||
opt_import_header->string_table_size + 12;
|
||||
auto library_data = reinterpret_cast<uint8_t*>(opt_import_libraries) +
|
||||
opt_import_libraries->string_table.size + 12;
|
||||
uint32_t library_offset = 0;
|
||||
uint32_t library_count = opt_import_header->library_count;
|
||||
for (uint32_t i = 0; i < library_count; i++) {
|
||||
auto library = reinterpret_cast<xex2_import_library*>(libraries_ptr +
|
||||
library_offset);
|
||||
while (library_offset < opt_import_libraries->size) {
|
||||
auto library =
|
||||
reinterpret_cast<xex2_import_library*>(library_data + library_offset);
|
||||
if (!library->size) {
|
||||
break;
|
||||
}
|
||||
size_t library_name_index = library->name_index & 0xFF;
|
||||
assert_true(library_name_index < max_string_table_index);
|
||||
assert_true(library_name_index <
|
||||
opt_import_libraries->string_table.count);
|
||||
assert_not_null(string_table[library_name_index]);
|
||||
SetupLibraryImports(string_table[library_name_index], library);
|
||||
library_offset += library->size;
|
||||
}
|
||||
@@ -1313,10 +1316,12 @@ bool XexModule::SetupLibraryImports(const char* name,
|
||||
var_info->set_status(Symbol::Status::kDefined);
|
||||
} else if (record_type == 1) {
|
||||
// Thunk.
|
||||
assert_true(library_info.imports.size() > 0);
|
||||
auto& prev_import = library_info.imports[library_info.imports.size() - 1];
|
||||
assert_true(prev_import.ordinal == ordinal);
|
||||
prev_import.thunk_address = record_addr;
|
||||
if (library_info.imports.size() > 0) {
|
||||
auto& prev_import =
|
||||
library_info.imports[library_info.imports.size() - 1];
|
||||
assert_true(prev_import.ordinal == ordinal);
|
||||
prev_import.thunk_address = record_addr;
|
||||
}
|
||||
|
||||
if (kernel_export) {
|
||||
import_name.AppendFormat("%s", kernel_export->name);
|
||||
|
||||
@@ -38,6 +38,7 @@ project("xenia-gpu-vulkan-trace-viewer")
|
||||
"imgui",
|
||||
"libavcodec",
|
||||
"libavutil",
|
||||
"mspack",
|
||||
"snappy",
|
||||
"spirv-tools",
|
||||
"volk",
|
||||
@@ -110,6 +111,7 @@ project("xenia-gpu-vulkan-trace-dump")
|
||||
"imgui",
|
||||
"libavcodec",
|
||||
"libavutil",
|
||||
"mspack",
|
||||
"snappy",
|
||||
"spirv-tools",
|
||||
"volk",
|
||||
|
||||
@@ -486,29 +486,33 @@ void UserModule::Dump() {
|
||||
std::memset(string_table, 0, sizeof(string_table));
|
||||
|
||||
// Parse the string table
|
||||
for (size_t l = 0, j = 0; l < opt_import_libraries->string_table_size;
|
||||
j++) {
|
||||
assert_true(j < xe::countof(string_table));
|
||||
const char* str = opt_import_libraries->string_table + l;
|
||||
for (size_t j = 0, o = 0; j < opt_import_libraries->string_table.size &&
|
||||
o < opt_import_libraries->string_table.count;
|
||||
o++) {
|
||||
assert_true(o < xe::countof(string_table));
|
||||
const char* str = &opt_import_libraries->string_table.data[j];
|
||||
|
||||
string_table[j] = str;
|
||||
l += std::strlen(str) + 1;
|
||||
string_table[o] = str;
|
||||
j += std::strlen(str) + 1;
|
||||
|
||||
// Padding
|
||||
if ((l % 4) != 0) {
|
||||
l += 4 - (l % 4);
|
||||
if ((j % 4) != 0) {
|
||||
j += 4 - (j % 4);
|
||||
}
|
||||
}
|
||||
|
||||
auto libraries =
|
||||
auto library_data =
|
||||
reinterpret_cast<const uint8_t*>(opt_import_libraries) +
|
||||
opt_import_libraries->string_table_size + 12;
|
||||
opt_import_libraries->string_table.size + 12;
|
||||
uint32_t library_offset = 0;
|
||||
uint32_t library_count = opt_import_libraries->library_count;
|
||||
for (uint32_t l = 0; l < library_count; l++) {
|
||||
while (library_offset < opt_import_libraries->size) {
|
||||
auto library = reinterpret_cast<const xex2_import_library*>(
|
||||
libraries + library_offset);
|
||||
library_data + library_offset);
|
||||
if (!library->size) {
|
||||
break;
|
||||
}
|
||||
auto name = string_table[library->name_index & 0xFF];
|
||||
assert_not_null(name);
|
||||
sb.AppendFormat(" %s - %d imports\n", name,
|
||||
(uint16_t)library->count);
|
||||
|
||||
@@ -786,11 +790,11 @@ void UserModule::Dump() {
|
||||
}
|
||||
if (kernel_export &&
|
||||
kernel_export->type == cpu::Export::Type::kVariable) {
|
||||
sb.AppendFormat(" V %.8X %.3X (%3d) %s %s\n",
|
||||
sb.AppendFormat(" V %.8X %.3X (%4d) %s %s\n",
|
||||
info->value_address, info->ordinal, info->ordinal,
|
||||
implemented ? " " : "!!", name);
|
||||
} else if (info->thunk_address) {
|
||||
sb.AppendFormat(" F %.8X %.8X %.3X (%3d) %s %s\n",
|
||||
sb.AppendFormat(" F %.8X %.8X %.3X (%4d) %s %s\n",
|
||||
info->value_address, info->thunk_address,
|
||||
info->ordinal, info->ordinal,
|
||||
implemented ? " " : "!!", name);
|
||||
|
||||
@@ -474,10 +474,12 @@ struct xex2_opt_execution_info {
|
||||
static_assert_size(xex2_opt_execution_info, 0x18);
|
||||
|
||||
struct xex2_opt_import_libraries {
|
||||
xe::be<uint32_t> section_size; // 0x0
|
||||
xe::be<uint32_t> string_table_size; // 0x4
|
||||
xe::be<uint32_t> library_count; // 0x8
|
||||
char string_table[1]; // 0xC string_table_size bytes
|
||||
xe::be<uint32_t> size; // 0x0
|
||||
struct {
|
||||
xe::be<uint32_t> size; // 0x4
|
||||
xe::be<uint32_t> count; // 0x8
|
||||
char data[1]; // 0xC string_table_size bytes
|
||||
} string_table;
|
||||
};
|
||||
|
||||
struct xex2_import_library {
|
||||
|
||||
@@ -23,7 +23,7 @@ struct DeviceInfo {
|
||||
uint32_t device_type;
|
||||
uint64_t total_bytes;
|
||||
uint64_t free_bytes;
|
||||
std::wstring name;
|
||||
wchar_t name[28];
|
||||
};
|
||||
static const DeviceInfo dummy_device_info_ = {
|
||||
0xF00D0000,
|
||||
@@ -57,7 +57,7 @@ dword_result_t XamContentGetDeviceName(dword_t device_id,
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (name_capacity < dummy_device_info_.name.size() + 1) {
|
||||
if (name_capacity < wcslen(dummy_device_info_.name) + 1) {
|
||||
return X_ERROR_INSUFFICIENT_BUFFER;
|
||||
}
|
||||
|
||||
@@ -174,6 +174,35 @@ dword_result_t XamContentCreateEnumerator(dword_t user_index, dword_t device_id,
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentCreateEnumerator, kContent, kImplemented);
|
||||
|
||||
dword_result_t XamContentCreateDeviceEnumerator(dword_t content_type,
|
||||
dword_t content_flags,
|
||||
dword_t max_count,
|
||||
lpdword_t buffer_size_ptr,
|
||||
lpdword_t handle_out) {
|
||||
assert_not_null(handle_out);
|
||||
|
||||
if (buffer_size_ptr) {
|
||||
*buffer_size_ptr = sizeof(DeviceInfo) * max_count;
|
||||
}
|
||||
|
||||
auto e = new XStaticEnumerator(kernel_state(), max_count, sizeof(DeviceInfo));
|
||||
e->Initialize();
|
||||
|
||||
// Copy our dummy device into the enumerator
|
||||
DeviceInfo* dev = (DeviceInfo*)e->AppendItem();
|
||||
if (dev) {
|
||||
xe::store_and_swap(&dev->device_id, dummy_device_info_.device_id);
|
||||
xe::store_and_swap(&dev->device_type, dummy_device_info_.device_type);
|
||||
xe::store_and_swap(&dev->total_bytes, dummy_device_info_.total_bytes);
|
||||
xe::store_and_swap(&dev->free_bytes, dummy_device_info_.free_bytes);
|
||||
xe::copy_and_swap(dev->name, dummy_device_info_.name, 28);
|
||||
}
|
||||
|
||||
*handle_out = e->handle();
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentCreateDeviceEnumerator, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name,
|
||||
lpvoid_t content_data_ptr, dword_t flags,
|
||||
lpdword_t disposition_ptr,
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
#include "xenia/base/platform_win.h"
|
||||
#endif
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
@@ -24,6 +28,152 @@ namespace xam {
|
||||
constexpr uint32_t X_LANGUAGE_ENGLISH = 1;
|
||||
constexpr uint32_t X_LANGUAGE_JAPANESE = 2;
|
||||
|
||||
dword_result_t XamGetOnlineSchema() {
|
||||
static uint32_t schema_guest = 0;
|
||||
static uint32_t schema_ptr_guest = 0;
|
||||
|
||||
if (!schema_guest) {
|
||||
// create a dummy schema, 8 bytes of 0 seems to work fine
|
||||
// (with another 8 bytes for schema ptr/schema size)
|
||||
schema_guest = kernel_state()->memory()->SystemHeapAlloc(16);
|
||||
schema_ptr_guest = schema_guest + 8;
|
||||
|
||||
auto schema = kernel_state()->memory()->TranslateVirtual(schema_guest);
|
||||
memset(schema, 0, 16);
|
||||
|
||||
// store schema ptr + size
|
||||
xe::store_and_swap<uint32_t>(schema + 0x8, schema_guest);
|
||||
xe::store_and_swap<uint32_t>(schema + 0xC, 0x8);
|
||||
}
|
||||
|
||||
// return pointer to the schema ptr/schema size struct
|
||||
return schema_ptr_guest;
|
||||
}
|
||||
DECLARE_XAM_EXPORT2(XamGetOnlineSchema, kNone, kImplemented, kSketchy);
|
||||
|
||||
void XamFormatDateString(dword_t unk, qword_t filetime, lpvoid_t buffer,
|
||||
dword_t buffer_length) {
|
||||
std::memset(buffer, 0, buffer_length * 2);
|
||||
|
||||
// TODO: implement this for other platforms
|
||||
#if XE_PLATFORM_WIN32
|
||||
FILETIME t;
|
||||
t.dwHighDateTime = filetime >> 32;
|
||||
t.dwLowDateTime = (uint32_t)filetime;
|
||||
|
||||
SYSTEMTIME st;
|
||||
SYSTEMTIME stLocal;
|
||||
|
||||
FileTimeToSystemTime(&t, &st);
|
||||
SystemTimeToTzSpecificLocalTime(NULL, &st, &stLocal);
|
||||
|
||||
wchar_t buf[256];
|
||||
// TODO: format this depending on users locale?
|
||||
swprintf(buf, 256, L"%02d/%02d/%d", stLocal.wMonth, stLocal.wDay,
|
||||
stLocal.wYear);
|
||||
|
||||
xe::copy_and_swap((wchar_t*)buffer.host_address(), buf, buffer_length);
|
||||
#else
|
||||
assert_always();
|
||||
#endif
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamFormatDateString, kNone, kImplemented);
|
||||
|
||||
void XamFormatTimeString(dword_t unk, qword_t filetime, lpvoid_t buffer,
|
||||
dword_t buffer_length) {
|
||||
std::memset(buffer, 0, buffer_length * 2);
|
||||
|
||||
// TODO: implement this for other platforms
|
||||
#if XE_PLATFORM_WIN32
|
||||
FILETIME t;
|
||||
t.dwHighDateTime = filetime >> 32;
|
||||
t.dwLowDateTime = (uint32_t)filetime;
|
||||
|
||||
SYSTEMTIME st;
|
||||
SYSTEMTIME stLocal;
|
||||
|
||||
FileTimeToSystemTime(&t, &st);
|
||||
SystemTimeToTzSpecificLocalTime(NULL, &st, &stLocal);
|
||||
|
||||
wchar_t buf[256];
|
||||
swprintf(buf, 256, L"%02d:%02d", stLocal.wHour, stLocal.wMinute);
|
||||
|
||||
xe::copy_and_swap((wchar_t*)buffer.host_address(), buf, buffer_length);
|
||||
#else
|
||||
assert_always();
|
||||
#endif
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamFormatTimeString, kNone, kImplemented);
|
||||
|
||||
dword_result_t keXamBuildResourceLocator(uint64_t module,
|
||||
const wchar_t* container,
|
||||
const wchar_t* resource,
|
||||
lpvoid_t buffer,
|
||||
uint32_t buffer_length) {
|
||||
wchar_t buf[256];
|
||||
|
||||
if (!module) {
|
||||
swprintf(buf, 256, L"file://media:/%s.xzp#%s", container, resource);
|
||||
XELOGD(
|
||||
"XamBuildResourceLocator(%ws) returning locator to local file %ws.xzp",
|
||||
container, container);
|
||||
} else {
|
||||
swprintf(buf, 256, L"section://%X,%s#%s", (uint32_t)module, container,
|
||||
resource);
|
||||
}
|
||||
|
||||
xe::copy_and_swap((wchar_t*)buffer.host_address(), buf, buffer_length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dword_result_t XamBuildResourceLocator(qword_t module, lpwstring_t container,
|
||||
lpwstring_t resource, lpvoid_t buffer,
|
||||
dword_t buffer_length) {
|
||||
return keXamBuildResourceLocator(module, container.value().c_str(),
|
||||
resource.value().c_str(), buffer,
|
||||
buffer_length);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamBuildResourceLocator, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamBuildGamercardResourceLocator(lpwstring_t filename,
|
||||
lpvoid_t buffer,
|
||||
dword_t buffer_length) {
|
||||
// On an actual xbox these funcs would return a locator to xam.xex resources,
|
||||
// but for Xenia we can return a locator to the resources as local files. (big
|
||||
// thanks to MS for letting XamBuildResourceLocator return local file
|
||||
// locators!)
|
||||
|
||||
// If you're running an app that'll need them, make sure to extract xam.xex
|
||||
// resources with xextool ("xextool -d . xam.xex") and add a .xzp extension.
|
||||
|
||||
return keXamBuildResourceLocator(0, L"gamercrd", filename.value().c_str(),
|
||||
buffer, buffer_length);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamBuildGamercardResourceLocator, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamBuildSharedSystemResourceLocator(lpwstring_t filename,
|
||||
lpvoid_t buffer,
|
||||
dword_t buffer_length) {
|
||||
// see notes inside XamBuildGamercardResourceLocator above
|
||||
return keXamBuildResourceLocator(0, L"shrdres", filename.value().c_str(),
|
||||
buffer, buffer_length);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamBuildSharedSystemResourceLocator, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamBuildLegacySystemResourceLocator(lpwstring_t filename,
|
||||
lpvoid_t buffer,
|
||||
dword_t buffer_length) {
|
||||
return XamBuildSharedSystemResourceLocator(filename, buffer, buffer_length);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamBuildLegacySystemResourceLocator, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamBuildXamResourceLocator(lpwstring_t filename, lpvoid_t buffer,
|
||||
dword_t buffer_length) {
|
||||
return keXamBuildResourceLocator(0, L"xam", filename.value().c_str(), buffer,
|
||||
buffer_length);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamBuildXamResourceLocator, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamGetSystemVersion() {
|
||||
// eh, just picking one. If we go too low we may break new games, but
|
||||
// this value seems to be used for conditionally loading symbols and if
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
dword_result_t XamNotifyCreateListener(qword_t mask, dword_t one) {
|
||||
dword_result_t XamNotifyCreateListenerInternal(qword_t mask, dword_t unk,
|
||||
dword_t one) {
|
||||
// r4=1 may indicate user process?
|
||||
|
||||
auto listener =
|
||||
@@ -30,6 +31,12 @@ dword_result_t XamNotifyCreateListener(qword_t mask, dword_t one) {
|
||||
|
||||
return handle;
|
||||
}
|
||||
DECLARE_XAM_EXPORT2(XamNotifyCreateListenerInternal, kNone, kImplemented,
|
||||
kSketchy);
|
||||
|
||||
dword_result_t XamNotifyCreateListener(qword_t mask, dword_t one) {
|
||||
return XamNotifyCreateListenerInternal(mask, 0, one);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamNotifyCreateListener, kNone, kImplemented);
|
||||
|
||||
// https://github.com/CodeAsm/ffplay360/blob/master/Common/AtgSignIn.cpp
|
||||
|
||||
@@ -588,7 +588,7 @@ XE_EXPORT(xam, 0x00000318, XamVoiceGetMicArrayStatus,
|
||||
XE_EXPORT(xam, 0x00000319, XamVoiceSetAudioCaptureRoutine, kFunction),
|
||||
XE_EXPORT(xam, 0x0000031A, XamVoiceGetDirectionalData, kFunction),
|
||||
XE_EXPORT(xam, 0x0000031B, XamBuildResourceLocator, kFunction),
|
||||
XE_EXPORT(xam, 0x0000031C, XamBuildSharedSystemResourceLocator_, kFunction),
|
||||
XE_EXPORT(xam, 0x0000031C, XamBuildLegacySystemResourceLocator, kFunction),
|
||||
XE_EXPORT(xam, 0x0000031D, XamBuildGamercardResourceLocator, kFunction),
|
||||
XE_EXPORT(xam, 0x0000031E, XamBuildDynamicResourceLocator, kFunction),
|
||||
XE_EXPORT(xam, 0x0000031F, XamBuildXamResourceLocator, kFunction),
|
||||
|
||||
@@ -159,6 +159,14 @@ XboxkrnlModule::XboxkrnlModule(Emulator* emulator, KernelState* kernel_state)
|
||||
xe::store_and_swap<uint8_t>(lpXboxHardwareInfo + 4, 0x06); // cpu count
|
||||
// Remaining 11b are zeroes?
|
||||
|
||||
// ExConsoleGameRegion, probably same values as keyvault region uses?
|
||||
// Just return all 0xFF, should satisfy anything that checks it
|
||||
uint32_t pExConsoleGameRegion = memory_->SystemHeapAlloc(4);
|
||||
auto lpExConsoleGameRegion = memory_->TranslateVirtual(pExConsoleGameRegion);
|
||||
export_resolver_->SetVariableMapping(
|
||||
"xboxkrnl.exe", ordinals::ExConsoleGameRegion, pExConsoleGameRegion);
|
||||
xe::store<uint32_t>(lpExConsoleGameRegion, 0xFFFFFFFF);
|
||||
|
||||
// XexExecutableModuleHandle (?**)
|
||||
// Games try to dereference this to get a pointer to some module struct.
|
||||
// So far it seems like it's just in loader code, and only used to look up
|
||||
|
||||
@@ -1009,6 +1009,46 @@ SHIM_CALL _vsnprintf_shim(PPCContext* ppc_context, KernelState* kernel_state) {
|
||||
SHIM_SET_RETURN_32(count);
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/1kt27hek.aspx
|
||||
SHIM_CALL _vsnwprintf_shim(PPCContext* ppc_context, KernelState* kernel_state) {
|
||||
uint32_t buffer_ptr = SHIM_GET_ARG_32(0);
|
||||
int32_t buffer_count = SHIM_GET_ARG_32(1);
|
||||
uint32_t format_ptr = SHIM_GET_ARG_32(2);
|
||||
uint32_t arg_ptr = SHIM_GET_ARG_32(3);
|
||||
|
||||
XELOGD("_vsnwprintf(%08X, %i, %08X, %08X)", buffer_ptr, buffer_count,
|
||||
format_ptr, arg_ptr);
|
||||
|
||||
if (buffer_ptr == 0 || buffer_count <= 0 || format_ptr == 0) {
|
||||
SHIM_SET_RETURN_32(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
auto buffer = (uint16_t*)SHIM_MEM_ADDR(buffer_ptr);
|
||||
auto format = (const uint16_t*)SHIM_MEM_ADDR(format_ptr);
|
||||
|
||||
ArrayArgList args(ppc_context, arg_ptr);
|
||||
WideStringFormatData data(format);
|
||||
|
||||
int32_t count = format_core(ppc_context, data, args, true);
|
||||
if (count < 0) {
|
||||
// Error.
|
||||
if (buffer_count > 0) {
|
||||
buffer[0] = '\0'; // write a null, just to be safe
|
||||
}
|
||||
} else if (count <= buffer_count) {
|
||||
// Fit within the buffer.
|
||||
xe::copy_and_swap(buffer, (uint16_t*)data.wstr().c_str(), count);
|
||||
if (count < buffer_count) {
|
||||
buffer[count] = '\0';
|
||||
}
|
||||
} else {
|
||||
// Overflowed buffer. We still return the count we would have written.
|
||||
xe::copy_and_swap(buffer, (uint16_t*)data.wstr().c_str(), buffer_count);
|
||||
}
|
||||
SHIM_SET_RETURN_32(count);
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/28d5ce15.aspx
|
||||
SHIM_CALL vsprintf_shim(PPCContext* ppc_context, KernelState* kernel_state) {
|
||||
uint32_t buffer_ptr = SHIM_GET_ARG_32(0);
|
||||
@@ -1100,6 +1140,7 @@ void RegisterStringExports(xe::cpu::ExportResolver* export_resolver,
|
||||
SHIM_SET_MAPPING("xboxkrnl.exe", vsprintf, state);
|
||||
SHIM_SET_MAPPING("xboxkrnl.exe", _vscwprintf, state);
|
||||
SHIM_SET_MAPPING("xboxkrnl.exe", vswprintf, state);
|
||||
SHIM_SET_MAPPING("xboxkrnl.exe", _vsnwprintf, state);
|
||||
}
|
||||
|
||||
} // namespace xboxkrnl
|
||||
|
||||
Reference in New Issue
Block a user