From d2b9bfd6eee03693db7ff57295b28dcc486d4788 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 22 Dec 2025 10:59:01 -0800 Subject: [PATCH] Arm64Emitter: Changes LoadConstant to support Pad and byte width Instead of just a trivial pad being on or off, support a tri-state on/off/auto where on will always pad, off will never pad, and auto will pad only if code caching is enabled. Further augment this by allowing a byte-width to be passed in, which can be used with pointers to force a 48-bit VA width to only ever pad to three instructions, reducing the common worst-case situation from 4 instructions to 3. This works because we're not going to expose a VA width larger than 47-bit to the guest. Fixes the handful of use-cases that explicitly chose their NOP padding, and a bug in Arm64Relocations.cpp where it was incorrectly asking to not receive padding even though it requires it. --- .../Core/ArchHelpers/Arm64Emitter.cpp | 21 ++++++++++++++----- .../Interface/Core/ArchHelpers/Arm64Emitter.h | 10 ++++++++- FEXCore/Source/Interface/Core/CodeCache.cpp | 9 +++++--- .../Interface/Core/JIT/Arm64Relocations.cpp | 6 ++++-- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.cpp b/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.cpp index 0b70ea370..ed090adcf 100644 --- a/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.cpp +++ b/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.cpp @@ -417,14 +417,25 @@ FEXCore::X86State::X86Reg Arm64Emitter::GetX86RegRelationToARMReg(ARMEmitter::Re return FEXCore::X86State::X86Reg::REG_INVALID; } -void Arm64Emitter::LoadConstant(ARMEmitter::Size s, ARMEmitter::Register Reg, uint64_t Constant, bool NOPPad) { - if (EnableCodeCaching) { - // Force NOP padding to ensure relocated constants always have enough encoding space available +void Arm64Emitter::LoadConstant(ARMEmitter::Size s, ARMEmitter::Register Reg, uint64_t Constant, PadType Pad, int MaxBytes) { + bool NOPPad = false; + if (Pad == PadType::DOPAD) { NOPPad = true; + } else if (Pad == PadType::NOPAD) { + NOPPad = false; + } else if (Pad == PadType::AUTOPAD) { + // Force NOP padding to ensure relocated constants always have enough encoding space available + NOPPad = EnableCodeCaching; } bool Is64Bit = s == ARMEmitter::Size::i64Bit; - int Segments = Is64Bit ? 4 : 2; + const auto UpperBound = Is64Bit ? 4 : 2; + int Segments = MaxBytes ? (MaxBytes / 2) : UpperBound; + + LOGMAN_THROW_A_FMT(MaxBytes >= 0 && MaxBytes <= (UpperBound * 2) && (MaxBytes & 1) == 0, + "MaxBytes must be bounded in the range of [0, {}] and 16-bit aligned", UpperBound); + // If MaxBytes specified then make sure to sanity check incoming data. + LOGMAN_THROW_A_FMT(MaxBytes == 0 || (Constant >> (MaxBytes * 8)) == 0, "MaxBytes provided but data can't fit within provided range."); if (Is64Bit && ((~Constant) >> 16) == 0) { movn(s, Reg, (~Constant) & 0xFFFF); @@ -441,7 +452,7 @@ void Arm64Emitter::LoadConstant(ARMEmitter::Size s, ARMEmitter::Register Reg, ui // If the upper 32-bits is all zero, we can now switch to a 32-bit move. s = ARMEmitter::Size::i32Bit; Is64Bit = false; - Segments = 2; + Segments = std::min(Segments, 2); } if (!Is64Bit && ((~Constant) & 0xFFFF0000) == 0) { diff --git a/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.h b/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.h index 82eb6e213..6ff8109fc 100644 --- a/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.h +++ b/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.h @@ -109,7 +109,15 @@ class Arm64Emitter : public ARMEmitter::Emitter { public: Arm64Emitter(FEXCore::Context::ContextImpl* ctx, void* EmissionPtr = nullptr, size_t size = 0); - void LoadConstant(ARMEmitter::Size s, ARMEmitter::Register Reg, uint64_t Constant, bool NOPPad = false); + enum class PadType { + // Explicitly does not need padding, even if code-caching is enabled. + NOPAD, + // Explicitly needs padding, even if code-caching is disabled. + DOPAD, + // Choose to pad or not depending on if code-caching is enabled. + AUTOPAD, + }; + void LoadConstant(ARMEmitter::Size s, ARMEmitter::Register Reg, uint64_t Constant, PadType Pad = PadType::AUTOPAD, int MaxBytes = 0); protected: FEXCore::Context::ContextImpl* EmitterCTX; diff --git a/FEXCore/Source/Interface/Core/CodeCache.cpp b/FEXCore/Source/Interface/Core/CodeCache.cpp index 1072c376f..6a41f1350 100644 --- a/FEXCore/Source/Interface/Core/CodeCache.cpp +++ b/FEXCore/Source/Interface/Core/CodeCache.cpp @@ -622,8 +622,9 @@ bool CodeCache::ApplyCodeRelocations(uint64_t GuestEntry, std::span C if (Pointer == ~0ULL) { return false; } - - Emitter.LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Register(Reloc.NamedThunkMove.RegisterIndex), Pointer, true); + // Pointers are required to fit within 48-bit VA space. + Emitter.LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Register(Reloc.NamedThunkMove.RegisterIndex), Pointer, + CPU::Arm64Emitter::PadType::DOPAD, 6); break; } case FEXCore::CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL: { @@ -632,7 +633,9 @@ bool CodeCache::ApplyCodeRelocations(uint64_t GuestEntry, std::span C } case FEXCore::CPU::RelocationTypes::RELOC_GUEST_RIP_MOVE: { uint64_t Pointer = Reloc.GuestRIP.GuestRIP + GuestEntry; - Emitter.LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Register(Reloc.GuestRIP.RegisterIndex), Pointer, true); + // Pointers are required to fit within 48-bit VA space. + Emitter.LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Register(Reloc.GuestRIP.RegisterIndex), Pointer, + CPU::Arm64Emitter::PadType::DOPAD, 6); break; } diff --git a/FEXCore/Source/Interface/Core/JIT/Arm64Relocations.cpp b/FEXCore/Source/Interface/Core/JIT/Arm64Relocations.cpp index 916bf00ce..7713501a7 100644 --- a/FEXCore/Source/Interface/Core/JIT/Arm64Relocations.cpp +++ b/FEXCore/Source/Interface/Core/JIT/Arm64Relocations.cpp @@ -28,7 +28,8 @@ void Arm64JITCore::InsertNamedThunkRelocation(ARMEmitter::Register Reg, const IR uint64_t Pointer = reinterpret_cast(EmitterCTX->ThunkHandler->LookupThunk(Sum)); - LoadConstant(ARMEmitter::Size::i64Bit, Reg, Pointer, false); + // Pointers are required to fit within 48-bit VA space. + LoadConstant(ARMEmitter::Size::i64Bit, Reg, Pointer, FEXCore::CPU::Arm64Emitter::PadType::AUTOPAD, 6); Relocations.emplace_back(MoveABI); } @@ -92,7 +93,8 @@ void Arm64JITCore::InsertGuestRIPMove(ARMEmitter::Register Reg, uint64_t Constan MoveABI.GuestRIP.GuestRIP = Constant; MoveABI.GuestRIP.RegisterIndex = Reg.Idx(); - LoadConstant(ARMEmitter::Size::i64Bit, Reg, Constant, false); + // Pointers are required to fit within 48-bit VA space. + LoadConstant(ARMEmitter::Size::i64Bit, Reg, Constant, FEXCore::CPU::Arm64Emitter::PadType::AUTOPAD, 6); Relocations.emplace_back(MoveABI); }