Fix distant ARM64 memory fallback branches (#362)

Use an inverse local conditional branch followed by an unconditional branch for unbound or distant targets. Keep the original one-instruction form for bound near targets and preserve registers and NZCV. Adapt Eden 57c538a73d.
This commit is contained in:
Bruno 2026-10-07 13:02:58 +02:00 committed by GitHub
parent 97e12e0498
commit 7b7dc386c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 75 additions and 7 deletions

View File

@ -402,6 +402,7 @@ if(ARCHITECTURE_arm64)
dynarmic/backend/arm64/emit_arm64_data_processing.cpp
dynarmic/backend/arm64/emit_arm64_floating_point.cpp
dynarmic/backend/arm64/emit_arm64_memory.cpp
dynarmic/backend/arm64/emit_arm64_far_branch.h
dynarmic/backend/arm64/emit_arm64_memory.h
dynarmic/backend/arm64/emit_arm64_packed.cpp
dynarmic/backend/arm64/emit_arm64_saturation.cpp

View File

@ -9,6 +9,7 @@
#include "dynarmic/backend/arm64/a32_jitstate.h"
#include "dynarmic/backend/arm64/abi.h"
#include "dynarmic/backend/arm64/emit_arm64.h"
#include "dynarmic/backend/arm64/emit_arm64_far_branch.h"
#include "dynarmic/backend/arm64/emit_context.h"
#include "dynarmic/backend/arm64/fpsr_manager.h"
#include "dynarmic/backend/arm64/reg_alloc.h"
@ -172,7 +173,7 @@ void EmitA32CheckMemoryAbort(oaknut::CodeGenerator& code, EmitContext& ctx, IR::
code.LDAR(Xscratch0, Xhalt);
code.TST(Xscratch0, static_cast<u32>(HaltReason::MemoryAbort));
code.B(EQ, end);
EmitFarConditionalBranch(code, EQ, end);
EmitSetUpperLocationDescriptor(code, ctx, current_location, ctx.block.Location());
code.MOV(Wscratch0, current_location.PC());
code.STR(Wscratch0, Xstate, offsetof(A32JitState, regs) + sizeof(u32) * 15);

View File

@ -9,6 +9,7 @@
#include "dynarmic/backend/arm64/a64_jitstate.h"
#include "dynarmic/backend/arm64/abi.h"
#include "dynarmic/backend/arm64/emit_arm64.h"
#include "dynarmic/backend/arm64/emit_arm64_far_branch.h"
#include "dynarmic/backend/arm64/emit_context.h"
#include "dynarmic/backend/arm64/fpsr_manager.h"
#include "dynarmic/backend/arm64/reg_alloc.h"
@ -155,7 +156,7 @@ void EmitA64CheckMemoryAbort(oaknut::CodeGenerator& code, EmitContext& ctx, IR::
code.LDAR(Xscratch0, Xhalt);
code.TST(Xscratch0, static_cast<u32>(HaltReason::MemoryAbort));
code.B(EQ, end);
EmitFarConditionalBranch(code, EQ, end);
code.MOV(Xscratch0, current_location.PC());
code.STR(Xscratch0, Xstate, offsetof(A64JitState, pc));
EmitRelocation(code, ctx, LinkTarget::ReturnFromRunCode);

View File

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <oaknut/oaknut.hpp>
namespace Dynarmic::Backend::Arm64 {
// Deferred memory fallbacks can lie outside B.cond/CBZ/CBNZ's +/-1 MiB range.
// Keep the conditional leg local and use B for the distant target. AddressSpace
// limits the entire arm64 code cache to 128 MiB, within B's reach.
// Bound targets within signed imm19 range retain the original single instruction.
// Unbound fallbacks require the long form because Oaknut cannot expand a fixup.
inline bool IsNearConditionalTarget(const oaknut::CodeGenerator& code,
const oaknut::Label& target) {
if (!target.is_bound()) {
return false;
}
const auto distance = target.offset() - code.offset();
return distance >= -(1 << 20) && distance <= (1 << 20) - 4;
}
inline void EmitFarConditionalBranch(oaknut::CodeGenerator& code, oaknut::Cond cond,
oaknut::Label& target) {
if (IsNearConditionalTarget(code, target)) {
code.B(cond, target);
return;
}
oaknut::Label continue_execution;
code.B(oaknut::invert(cond), continue_execution);
code.B(target);
code.l(continue_execution);
}
template<typename Reg>
void EmitFarZeroBranch(oaknut::CodeGenerator& code, Reg reg, oaknut::Label& target) {
if (IsNearConditionalTarget(code, target)) {
code.CBZ(reg, target);
return;
}
oaknut::Label continue_execution;
code.CBNZ(reg, continue_execution);
code.B(target);
code.l(continue_execution);
}
template<typename Reg>
void EmitFarNonZeroBranch(oaknut::CodeGenerator& code, Reg reg, oaknut::Label& target) {
if (IsNearConditionalTarget(code, target)) {
code.CBNZ(reg, target);
return;
}
oaknut::Label continue_execution;
code.CBZ(reg, continue_execution);
code.B(target);
code.l(continue_execution);
}
} // namespace Dynarmic::Backend::Arm64

View File

@ -13,6 +13,7 @@
#include "dynarmic/backend/arm64/abi.h"
#include "dynarmic/backend/arm64/emit_arm64.h"
#include "dynarmic/backend/arm64/emit_arm64_far_branch.h"
#include "dynarmic/backend/arm64/emit_context.h"
#include "dynarmic/backend/arm64/fastmem.h"
#include "dynarmic/backend/arm64/fpsr_manager.h"
@ -237,12 +238,12 @@ void EmitDetectMisalignedVAddr(oaknut::CodeGenerator& code, EmitContext& ctx, oa
}();
code.TST(Xaddr, align_mask);
code.B(NE, *fallback);
EmitFarConditionalBranch(code, NE, *fallback);
} else {
// If (addr & page_mask) > page_size - byte_size, use fallback.
code.AND(Xscratch0, Xaddr, page_mask);
code.CMP(Xscratch0, page_size - bitsize / 8);
code.B(HI, *fallback);
EmitFarConditionalBranch(code, HI, *fallback);
}
}
@ -262,7 +263,7 @@ std::pair<oaknut::XReg, oaknut::XReg> InlinePageTableEmitVAddrLookup(oaknut::Cod
} else {
code.LSR(Xscratch0, Xaddr, page_bits);
code.TST(Xscratch0, u64(~u64(0)) << valid_page_index_bits);
code.B(NE, *fallback);
EmitFarConditionalBranch(code, NE, *fallback);
}
code.LDR(Xscratch0, Xpagetable, Xscratch0, LSL, 3);
@ -272,7 +273,7 @@ std::pair<oaknut::XReg, oaknut::XReg> InlinePageTableEmitVAddrLookup(oaknut::Cod
code.AND(Xscratch0, Xscratch0, mask);
}
code.CBZ(Xscratch0, *fallback);
EmitFarZeroBranch(code, Xscratch0, *fallback);
if (ctx.conf.absolute_offset_page_table) {
return std::make_pair(Xscratch0, Xaddr);
@ -517,7 +518,7 @@ std::pair<oaknut::XReg, oaknut::XReg> FastmemEmitVAddrLookup(oaknut::CodeGenerat
}
code.LSR(Xscratch0, Xaddr, ctx.conf.fastmem_address_space_bits);
code.CBNZ(Xscratch0, *fallback);
EmitFarNonZeroBranch(code, Xscratch0, *fallback);
return std::make_pair(Xfastmem, Xaddr);
}

View File

@ -51,6 +51,7 @@
<ClInclude Include="dynarmic\backend\arm64\address_space.h" />
<ClInclude Include="dynarmic\backend\arm64\devirtualize.h" />
<ClInclude Include="dynarmic\backend\arm64\emit_arm64.h" />
<ClInclude Include="dynarmic\backend\arm64\emit_arm64_far_branch.h" />
<ClInclude Include="dynarmic\backend\arm64\emit_arm64_memory.h" />
<ClInclude Include="dynarmic\backend\arm64\emit_context.h" />
<ClInclude Include="dynarmic\backend\arm64\fastmem.h" />

View File

@ -219,6 +219,9 @@
<ClInclude Include="dynarmic\backend\arm64\emit_arm64.h">
<Filter>Header Files\dynamic\backend\arm64</Filter>
</ClInclude>
<ClInclude Include="dynarmic\backend\arm64\emit_arm64_far_branch.h">
<Filter>Header Files\dynamic\backend\arm64</Filter>
</ClInclude>
<ClInclude Include="dynarmic\backend\arm64\emit_arm64_memory.h">
<Filter>Header Files\dynamic\backend\arm64</Filter>
</ClInclude>