mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-07-09 17:25:18 -06:00
AArch64: Fix leaf function tail-call conversion; extract emit_hv_return_addr
Move force_tail_call_terminators past the leaf-function check so that leaf functions (which contain only C-ABI calls) are never converted to tail calls. Forcing those to tail calls caused LLVM to emit a sibling- call branch (b sys_func) instead of bl+ret, making injected code between the call and return unreachable and crashing at the gateway return path. Extract the duplicated inline-asm block that loads hv_ctx return address into x30 into emit_hv_return_addr(), adding a bounds check on arg_index with a safe fallback to the raw base register.
This commit is contained in:
parent
8c89adf177
commit
9125ae483c
@ -275,19 +275,23 @@ namespace aarch64
|
||||
return;
|
||||
}
|
||||
|
||||
// Force tail calls on all terminators
|
||||
force_tail_call_terminators(f);
|
||||
|
||||
// Check for leaves
|
||||
if (function_info.is_leaf && !m_config.use_stack_frames)
|
||||
{
|
||||
// Sanity check. If this function had no returning calls, it should have been omitted from processing.
|
||||
ensure(function_info.clobbers_x30, "Function has no terminator and no non-tail calls but was allowed for frame processing!");
|
||||
DPRINT("Function %s is a leaf.", this_name.c_str());
|
||||
// NOTE: Do NOT call force_tail_call_terminators here. Leaf functions contain only non-tail C calls
|
||||
// (e.g. syscalls). Forcing those to tail calls causes LLVM to generate a sibling-call branch
|
||||
// (b sys_func) instead of bl+ret, which absorbs the ret and makes any code injected between
|
||||
// the call and the return unreachable.
|
||||
process_leaf_function(irb, f);
|
||||
return;
|
||||
}
|
||||
|
||||
// Force tail calls on all terminators (non-leaf functions with GHC tail calls only)
|
||||
force_tail_call_terminators(f);
|
||||
|
||||
// Asm snippets for patching stack frame
|
||||
ASMBlock frame_prologue, frame_epilogue;
|
||||
|
||||
@ -426,14 +430,7 @@ namespace aarch64
|
||||
}
|
||||
|
||||
// Patch the return path. No GHC call shall ever return to another. If we reach the function endpoint, immediately abort to GW
|
||||
auto thread_base_reg = get_base_register_for_call(f.getName().str());
|
||||
auto arg_index = static_cast<int>(thread_base_reg) - static_cast<int>(x19);
|
||||
ASMBlock c;
|
||||
|
||||
auto thread_arg = ensure(f.getArg(arg_index)); // Guaranteed to hold our original 'thread'
|
||||
c.mov(x30, UASM::Var(thread_arg));
|
||||
c.ldr(x30, x30, UASM::Imm(m_config.hypervisor_context_offset));
|
||||
c.insert(irb, f.getContext());
|
||||
emit_hv_return_addr(irb, f);
|
||||
|
||||
// Next
|
||||
return where;
|
||||
@ -504,6 +501,27 @@ namespace aarch64
|
||||
return std::find(x.begin(), x.end(), function_name) != x.end();
|
||||
}
|
||||
|
||||
void GHC_frame_preservation_pass::emit_hv_return_addr(llvm::IRBuilder<>* irb, llvm::Function& f)
|
||||
{
|
||||
// Emit inline asm that loads the gateway return address from hv_ctx into x30.
|
||||
// Uses the LLVM SSA value for the thread argument via an "r" input constraint so that
|
||||
// LLVM guarantees the correct thread pointer is in a register, regardless of whether the
|
||||
// base register (e.g. x20) was repurposed for other computations by the time this runs.
|
||||
ASMBlock c;
|
||||
auto thread_base_reg = get_base_register_for_call(f.getName().str());
|
||||
const auto arg_index = static_cast<int>(thread_base_reg) - static_cast<int>(x19);
|
||||
if (arg_index >= 0 && static_cast<u32>(arg_index) < f.arg_size())
|
||||
{
|
||||
c.mov(x30, UASM::Var(f.getArg(arg_index)));
|
||||
}
|
||||
else
|
||||
{
|
||||
c.mov(x30, thread_base_reg);
|
||||
}
|
||||
c.ldr(x30, x30, UASM::Imm(m_config.hypervisor_context_offset));
|
||||
c.insert(irb, f.getContext());
|
||||
}
|
||||
|
||||
void GHC_frame_preservation_pass::process_leaf_function(llvm::IRBuilder<>* irb, llvm::Function& f)
|
||||
{
|
||||
for (auto &bb : f)
|
||||
@ -530,15 +548,8 @@ namespace aarch64
|
||||
c.insert(irb, f.getContext());
|
||||
}
|
||||
|
||||
// Now we need to reload LR. We abuse the function's caller arg set for this to avoid messing with regs too much
|
||||
auto thread_base_reg = get_base_register_for_call(f.getName().str());
|
||||
auto arg_index = static_cast<int>(thread_base_reg) - static_cast<int>(x19);
|
||||
ASMBlock c;
|
||||
|
||||
auto thread_arg = ensure(f.getArg(arg_index)); // Guaranteed to hold our original 'thread'
|
||||
c.mov(x30, UASM::Var(thread_arg));
|
||||
c.ldr(x30, x30, UASM::Imm(m_config.hypervisor_context_offset));
|
||||
c.insert(irb, f.getContext());
|
||||
// Reload LR from the gateway return address stored in hv_ctx.
|
||||
emit_hv_return_addr(irb, f);
|
||||
|
||||
if (bit != bb.end())
|
||||
{
|
||||
|
||||
@ -69,6 +69,8 @@ namespace aarch64
|
||||
|
||||
gpr get_base_register_for_call(std::string_view callee_name, gpr default_reg = gpr::x19);
|
||||
|
||||
void emit_hv_return_addr(llvm::IRBuilder<>* irb, llvm::Function& f);
|
||||
|
||||
void process_leaf_function(llvm::IRBuilder<>* irb, llvm::Function& f);
|
||||
|
||||
llvm::BasicBlock::iterator patch_tail_call(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user