Initial PPU LLVM implementation for aarch64

This commit is contained in:
kd-11
2024-08-04 05:09:06 +03:00
committed by kd-11
parent a5f9256ac6
commit 56cc5d9355
6 changed files with 223 additions and 78 deletions
+81 -49
View File
@@ -222,7 +222,7 @@ const auto ppu_gateway = build_function_asm<void(*)(ppu_thread*)>("ppu_gateway",
#endif
// Save native stack pointer for longjmp emulation
c.mov(x86::qword_ptr(args[0], ::offset32(&ppu_thread::saved_native_sp)), x86::rsp);
c.mov(x86::qword_ptr(args[0], ::offset32(&ppu_thread::hv_ctx, &rpcs3::hypervisor_context_t::regs)), x86::rsp);
// Initialize args
c.mov(x86::r13, x86::qword_ptr(reinterpret_cast<u64>(&vm::g_exec_addr)));
@@ -291,37 +291,48 @@ const auto ppu_gateway = build_function_asm<void(*)(ppu_thread*)>("ppu_gateway",
// and https://developer.arm.com/documentation/den0024/a/The-ABI-for-ARM-64-bit-Architecture/Register-use-in-the-AArch64-Procedure-Call-Standard/Parameters-in-general-purpose-registers
// for AArch64 calling convention
// Save sp for native longjmp emulation
Label native_sp_offset = c.newLabel();
c.ldr(a64::x10, arm::Mem(native_sp_offset));
// sp not allowed to be used in load/stores directly
c.mov(a64::x15, a64::sp);
c.str(a64::x15, arm::Mem(args[0], a64::x10));
// Push callee saved registers to the stack
// Push callee saved registers to the hv context
// Assume our LLVM compiled code is unsafe and can clobber our stack. GHC on aarch64 treats stack as scratch.
// We also want to store the register context at a fixed place so we can read the hypervisor state from any lcoation.
// We need to save x18-x30 = 13 x 8B each + 8 bytes for 16B alignment = 112B
c.sub(a64::sp, a64::sp, Imm(112));
c.stp(a64::x18, a64::x19, arm::Mem(a64::sp));
c.stp(a64::x20, a64::x21, arm::Mem(a64::sp, 16));
c.stp(a64::x22, a64::x23, arm::Mem(a64::sp, 32));
c.stp(a64::x24, a64::x25, arm::Mem(a64::sp, 48));
c.stp(a64::x26, a64::x27, arm::Mem(a64::sp, 64));
c.stp(a64::x28, a64::x29, arm::Mem(a64::sp, 80));
c.str(a64::x30, arm::Mem(a64::sp, 96));
// Pre-context save
// Layout:
// pc, sp
// x18, x19...x30
// NOTE: Do not touch x19..x30 before saving the registers!
const u64 hv_register_array_offset = ::offset32(&ppu_thread::hv_ctx, &rpcs3::hypervisor_context_t::regs);
Label hv_ctx_pc = c.newLabel(); // Used to hold the far jump return address
// Sanity
ensure(hv_register_array_offset < 4096); // Imm10
c.mov(a64::x15, args[0]);
c.add(a64::x14, a64::x15, Imm(hv_register_array_offset)); // Per-thread context save
c.adr(a64::x15, hv_ctx_pc); // x15 = pc
c.mov(a64::x13, a64::sp); // x16 = sp
c.stp(a64::x15, a64::x13, arm::Mem(a64::x14));
c.stp(a64::x18, a64::x19, arm::Mem(a64::x14, 16));
c.stp(a64::x20, a64::x21, arm::Mem(a64::x14, 32));
c.stp(a64::x22, a64::x23, arm::Mem(a64::x14, 48));
c.stp(a64::x24, a64::x25, arm::Mem(a64::x14, 64));
c.stp(a64::x26, a64::x27, arm::Mem(a64::x14, 80));
c.stp(a64::x28, a64::x29, arm::Mem(a64::x14, 96));
c.str(a64::x30, arm::Mem(a64::x14, 112));
// Load REG_Base - use absolute jump target to bypass rel jmp range limits
Label exec_addr = c.newLabel();
c.ldr(a64::x19, arm::Mem(exec_addr));
c.mov(a64::x19, Imm(reinterpret_cast<u64>(&vm::g_exec_addr)));
c.ldr(a64::x19, arm::Mem(a64::x19));
// Load PPUThread struct base -> REG_Sp
const arm::GpX ppu_t_base = a64::x20;
c.mov(ppu_t_base, args[0]);
// Load PC
const arm::GpX pc = a64::x15;
Label cia_offset = c.newLabel();
const arm::GpX cia_addr_reg = a64::x11;
// Load offset value
c.ldr(cia_addr_reg, arm::Mem(cia_offset));
c.mov(cia_addr_reg, Imm(static_cast<u64>(::offset32(&ppu_thread::cia))));
// Load cia
c.ldr(a64::w15, arm::Mem(ppu_t_base, cia_addr_reg));
// Multiply by 2 to index into ptr table
@@ -343,44 +354,45 @@ const auto ppu_gateway = build_function_asm<void(*)(ppu_thread*)>("ppu_gateway",
c.lsr(call_target, call_target, Imm(16));
// Load registers
Label base_addr = c.newLabel();
c.ldr(a64::x22, arm::Mem(base_addr));
c.mov(a64::x22, Imm(reinterpret_cast<u64>(&vm::g_base_addr)));
c.ldr(a64::x22, arm::Mem(a64::x22));
Label gpr_addr_offset = c.newLabel();
const arm::GpX gpr_addr_reg = a64::x9;
c.ldr(gpr_addr_reg, arm::Mem(gpr_addr_offset));
c.mov(gpr_addr_reg, Imm(static_cast<u64>(::offset32(&ppu_thread::gpr))));
c.add(gpr_addr_reg, gpr_addr_reg, ppu_t_base);
c.ldr(a64::x23, arm::Mem(gpr_addr_reg));
c.ldr(a64::x24, arm::Mem(gpr_addr_reg, 8));
c.ldr(a64::x25, arm::Mem(gpr_addr_reg, 16));
// GHC frame for the guest. This seems dodgy but the only thing stored on stack is actually registers before making calls to C++ code.
// Injected stack frames also work, but are not free and are completely unnecessary.
c.sub(a64::sp, a64::sp, Imm(4096));
// Execute LLE call
c.blr(call_target);
// Restore registers from the stack
c.ldp(a64::x18, a64::x19, arm::Mem(a64::sp));
c.ldp(a64::x20, a64::x21, arm::Mem(a64::sp, 16));
c.ldp(a64::x22, a64::x23, arm::Mem(a64::sp, 32));
c.ldp(a64::x24, a64::x25, arm::Mem(a64::sp, 48));
c.ldp(a64::x26, a64::x27, arm::Mem(a64::sp, 64));
c.ldp(a64::x28, a64::x29, arm::Mem(a64::sp, 80));
c.ldr(a64::x30, arm::Mem(a64::sp, 96));
// Restore stack ptr
c.add(a64::sp, a64::sp, Imm(112));
// Return
c.ret(a64::x30);
// Return address after far jump. Reset sp and start unwinding...
c.bind(hv_ctx_pc);
c.bind(exec_addr);
c.embedUInt64(reinterpret_cast<u64>(&vm::g_exec_addr));
c.bind(base_addr);
c.embedUInt64(reinterpret_cast<u64>(&vm::g_base_addr));
c.bind(cia_offset);
c.embedUInt64(static_cast<u64>(::offset32(&ppu_thread::cia)));
c.bind(gpr_addr_offset);
c.embedUInt64(static_cast<u64>(::offset32(&ppu_thread::gpr)));
c.bind(native_sp_offset);
c.embedUInt64(static_cast<u64>(::offset32(&ppu_thread::saved_native_sp)));
// Execution guard undo (unneded since we're going to hard-reset the SP)
//c.add(a64::sp, a64::sp, Imm(4096));
// We either got here through normal "ret" which keeps our x20 intact, or we jumped here and the escape reset our x20 reg
// Either way, x20 contains our thread base and we forcefully reset the stack pointer
c.add(a64::x14, a64::x20, Imm(hv_register_array_offset)); // Per-thread context save
c.ldr(a64::x15, arm::Mem(a64::x14, 8));
c.ldp(a64::x18, a64::x19, arm::Mem(a64::x14, 16));
c.ldp(a64::x20, a64::x21, arm::Mem(a64::x14, 32));
c.ldp(a64::x22, a64::x23, arm::Mem(a64::x14, 48));
c.ldp(a64::x24, a64::x25, arm::Mem(a64::x14, 64));
c.ldp(a64::x26, a64::x27, arm::Mem(a64::x14, 80));
c.ldp(a64::x28, a64::x29, arm::Mem(a64::x14, 96));
c.ldr(a64::x30, arm::Mem(a64::x14, 112));
// Return
c.mov(a64::sp, a64::x15);
c.ret(a64::x30);
#endif
});
@@ -390,11 +402,20 @@ const extern auto ppu_escape = build_function_asm<void(*)(ppu_thread*)>("ppu_esc
#if defined(ARCH_X64)
// Restore native stack pointer (longjmp emulation)
c.mov(x86::rsp, x86::qword_ptr(args[0], ::offset32(&ppu_thread::saved_native_sp)));
c.mov(x86::rsp, x86::qword_ptr(args[0], ::offset32(&ppu_thread::hv_ctx, &rpcs3::hypervisor_context_t::regs)));
// Return to the return location
c.sub(x86::rsp, 8);
c.ret();
#else
// We really shouldn't be using this, but an implementation shoudln't hurt
// Far jump return. Only clobbers x30.
const arm::GpX ppu_t_base = a64::x20;
const u64 hv_register_array_offset = ::offset32(&ppu_thread::hv_ctx, &rpcs3::hypervisor_context_t::regs);
c.mov(ppu_t_base, args[0]);
c.mov(a64::x30, Imm(hv_register_array_offset));
c.ldr(a64::x30, arm::Mem(ppu_t_base, a64::x30));
c.ret(a64::x30);
#endif
});
@@ -2265,6 +2286,9 @@ void ppu_thread::exec_task()
{
if (g_cfg.core.ppu_decoder != ppu_decoder_type::_static)
{
// HVContext push to allow recursion. This happens with guest callback invocations.
const auto old_hv_ctx = hv_ctx;
while (true)
{
if (state) [[unlikely]]
@@ -2276,6 +2300,8 @@ void ppu_thread::exec_task()
ppu_gateway(this);
}
// HVContext pop
hv_ctx = old_hv_ctx;
return;
}
@@ -2314,6 +2340,8 @@ ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u3
{
prio.raw().prio = _prio;
memset(&hv_ctx, 0, sizeof(hv_ctx));
gpr[1] = stack_addr + stack_size - ppu_stack_start_offset;
gpr[13] = param.tls_addr;
@@ -3502,7 +3530,7 @@ static bool ppu_store_reservation(ppu_thread& ppu, u32 addr, u64 reg_value)
if (notify)
{
bool notified = false;
bool notified = false;
if (ppu.res_notify_time == (vm::reservation_acquire(notify) & -128))
{
@@ -5277,12 +5305,14 @@ static void ppu_initialize2(jit_compiler& jit, const ppu_module& module_part, co
// Translate
if (const auto func = translator.Translate(module_part.funcs[fi]))
{
#ifdef ARCH_X64 // TODO
// Run optimization passes
#if LLVM_VERSION_MAJOR < 17
pm.run(*func);
#else
fpm.run(*func, fam);
#endif
#endif // ARCH_X64
}
else
{
@@ -5297,12 +5327,14 @@ static void ppu_initialize2(jit_compiler& jit, const ppu_module& module_part, co
{
if (const auto func = translator.GetSymbolResolver(whole_module))
{
#ifdef ARCH_X64 // TODO
// Run optimization passes
#if LLVM_VERSION_MAJOR < 17
pm.run(*func);
#else
fpm.run(*func, fam);
#endif
#endif // ARCH_X64
}
else
{