diff --git a/FEXCore/Source/Interface/Context/Context.h b/FEXCore/Source/Interface/Context/Context.h index cac861949..b15d0a58d 100644 --- a/FEXCore/Source/Interface/Context/Context.h +++ b/FEXCore/Source/Interface/Context/Context.h @@ -260,6 +260,10 @@ public: static void ThreadRemoveCodeEntryFromJit(FEXCore::Core::CpuStateFrame* Frame, uint64_t GuestRIP); + // This is used as a replacement for the SMC writes in the mono callsite backpatcher that avoids atomic operations + // (safe as the invalidation mutex is locked) and manually invalidates the modified range. Allowing SMC to be detected + // even if faulting is disabled. + static void MonoBackpatcherWrite(FEXCore::Core::CpuStateFrame* Frame, uint8_t Size, uint64_t Address, uint64_t Value); void RemoveCustomIREntrypoint(uintptr_t Entrypoint); diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index 728df43ac..40131549f 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -1096,6 +1096,24 @@ void ContextImpl::RemoveCustomIREntrypoint(uintptr_t Entrypoint) { HasCustomIRHandlers = !CustomIRHandlers.empty(); } +void ContextImpl::MonoBackpatcherWrite(FEXCore::Core::CpuStateFrame* Frame, uint8_t Size, uint64_t Address, uint64_t Value) { + auto Thread = Frame->Thread; + auto CTX = static_cast(Thread->CTX); + { + auto lk = GuardSignalDeferringSection(CTX->CodeInvalidationMutex, Thread); + + if (Size == 8) { + *reinterpret_cast(Address) = Value; + } else if (Size == 4) { + *reinterpret_cast(Address) = Value; + } else { + ERROR_AND_DIE_FMT("Unexpected write size for backpatcher: {}", Size); + } + } + + CTX->SyscallHandler->InvalidateGuestCodeRange(Thread, Address, Size); +} + IR::AOTIRCacheEntry* ContextImpl::LoadAOTIRCacheEntry(const fextl::string& filename) { auto rv = IRCaptureCache.LoadAOTIRCacheEntry(filename); return rv; diff --git a/FEXCore/Source/Interface/Core/JIT/JIT.cpp b/FEXCore/Source/Interface/Core/JIT/JIT.cpp index 0296df7da..ffe04265f 100644 --- a/FEXCore/Source/Interface/Core/JIT/JIT.cpp +++ b/FEXCore/Source/Interface/Core/JIT/JIT.cpp @@ -639,6 +639,7 @@ Arm64JITCore::Arm64JITCore(FEXCore::Context::ContextImpl* ctx, FEXCore::Core::In Common.PrintValue = reinterpret_cast(PrintValue); Common.PrintVectorValue = reinterpret_cast(PrintVectorValue); Common.ThreadRemoveCodeEntryFromJIT = reinterpret_cast(&Context::ContextImpl::ThreadRemoveCodeEntryFromJit); + Common.MonoBackpatcherWrite = reinterpret_cast(&Context::ContextImpl::MonoBackpatcherWrite); Common.CPUIDObj = reinterpret_cast(&CTX->CPUID); { diff --git a/FEXCore/Source/Interface/Core/JIT/MiscOps.cpp b/FEXCore/Source/Interface/Core/JIT/MiscOps.cpp index 5a713ab1d..0c7c75a2e 100644 --- a/FEXCore/Source/Interface/Core/JIT/MiscOps.cpp +++ b/FEXCore/Source/Interface/Core/JIT/MiscOps.cpp @@ -286,4 +286,43 @@ DEF_OP(Yield) { yield(); } +DEF_OP(MonoBackpatcherWrite) { + auto Op = IROp->C(); + + mov(ARMEmitter::Size::i64Bit, TMP3, GetReg(Op->Addr)); + mov(ARMEmitter::Size::i64Bit, TMP4, GetReg(Op->Value)); + + PushDynamicRegs(TMP1); + SpillStaticRegs(TMP1); + + mov(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r0, STATE.R()); + mov(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r1, IR::OpSizeToSize(Op->Size)); + + if (!TMP_ABIARGS) { + mov(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r2, TMP3); + mov(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r3, TMP4); + } + +#ifdef _M_ARM_64EC + ldr(TMP2, ARMEmitter::XReg::x18, TEB_CPU_AREA_OFFSET); + LoadConstant(ARMEmitter::Size::i32Bit, TMP1, 1); + strb(TMP1.W(), TMP2, CPU_AREA_IN_SYSCALL_CALLBACK_OFFSET); +#endif + + ldr(ARMEmitter::XReg::x4, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.Common.MonoBackpatcherWrite)); + if (!CTX->Config.DisableVixlIndirectCalls) [[unlikely]] { + GenerateIndirectRuntimeCall(ARMEmitter::Reg::r4); + } else { + blr(ARMEmitter::Reg::r4); + } + +#ifdef _M_ARM_64EC + ldr(TMP2, ARMEmitter::XReg::x18, TEB_CPU_AREA_OFFSET); + strb(ARMEmitter::WReg::zr, TMP2, CPU_AREA_IN_SYSCALL_CALLBACK_OFFSET); +#endif + + FillStaticRegs(); + PopDynamicRegs(); +} + } // namespace FEXCore::CPU diff --git a/FEXCore/Source/Interface/IR/IR.json b/FEXCore/Source/Interface/IR/IR.json index 63f7d1f89..0e578627c 100644 --- a/FEXCore/Source/Interface/IR/IR.json +++ b/FEXCore/Source/Interface/IR/IR.json @@ -291,6 +291,14 @@ "Desc": [ "Implement a low power wait attempting to sleep until RDTSC >= Upper:Lower.", "Will spuriously wake up." + ] + }, + "MonoBackpatcherWrite OpSize:$Size, GPR:$Value, GPR:$Addr": { + "HasSideEffects": true, + "Desc": [ "Writes and invalidates the target address with the invalidation mutex locked. This is a fault-avoiding", + "replacement for the atomic SMC writes used in the mono callsite backpatcher." ], + "EmitValidation": [ + "Size == FEXCore::IR::OpSize::i32Bit || Size == FEXCore::IR::OpSize::i64Bit" ] } }, diff --git a/FEXCore/include/FEXCore/Core/CoreState.h b/FEXCore/include/FEXCore/Core/CoreState.h index 6e88f0a90..60121723c 100644 --- a/FEXCore/include/FEXCore/Core/CoreState.h +++ b/FEXCore/include/FEXCore/Core/CoreState.h @@ -305,6 +305,7 @@ struct JITPointers { uint64_t SyscallHandlerObj {}; uint64_t SyscallHandlerFunc {}; uint64_t ExitFunctionLink {}; + uint64_t MonoBackpatcherWrite {}; // Handles returning/calling ARM64EC code from the JIT, expects the target PC in TMP3 uint64_t ExitFunctionEC {};