From fcba49768c2c01bf8eff7358aad5465a5796bfd8 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 2 Sep 2025 21:36:49 +0100 Subject: [PATCH] Frontend: Force acq/rel semantics for known Unity ringbuffer offsets Unity games crash with TSO disabled due to the SPSC GfxDevice ThreadedStreamBuffer read/write pointer updates missing acq/rel semantics. Rather than attempting to use heuristics to match cases like this, which could be overzealous and hit more accesses than necessary, just target the problem directly as this is consist across 32/64 bit Unity versions for at least the past 10 years. Gate this behind the existing Unity mono hacks to avoid false-positives in non-Unity games. --- FEXCore/Source/Interface/Core/Core.cpp | 16 +++++++++++----- FEXCore/Source/Interface/Core/Frontend.cpp | 16 ++++++++++++++++ .../Source/Interface/Core/X86Tables/X86Tables.h | 1 + 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index 5123baee2..e26e2bb11 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -602,11 +602,17 @@ ContextImpl::GenerateIR(FEXCore::Core::InternalThreadState* Thread, uint64_t Gue auto Fn = TableInfo->OpcodeDispatcher.OpDispatch; Thread->OpDispatcher->ResetHandledLock(); Thread->OpDispatcher->ResetDecodeFailure(); - IR::ForceTSOMode ForceTSO = - BlockInForceTSOValidRange ? - (InstForceTSOIt != ForceTSOInstructions.end() && *InstForceTSOIt == InstAddress ? IR::ForceTSOMode::ForceEnabled : - IR::ForceTSOMode::ForceDisabled) : - IR::ForceTSOMode::NoOverride; + IR::ForceTSOMode ForceTSO = IR::ForceTSOMode::NoOverride; + if (BlockInForceTSOValidRange) { + if (InstForceTSOIt != ForceTSOInstructions.end() && *InstForceTSOIt == InstAddress) { + ForceTSO = IR::ForceTSOMode::ForceEnabled; + } else { + ForceTSO = IR::ForceTSOMode::ForceDisabled; + } + } else if (DecodedInfo->ForceTSO) { + ForceTSO = IR::ForceTSOMode::ForceEnabled; + } + Thread->OpDispatcher->SetForceTSO(ForceTSO); std::invoke(Fn, Thread->OpDispatcher, DecodedInfo); if (Thread->OpDispatcher->HadDecodeFailure()) { diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index 368772432..83320250e 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -1046,6 +1046,22 @@ Decoder::DecodedBlockStatus Decoder::DecodeInstruction(uint64_t PC) { return DecodedBlockStatus::INVALID_INST; } + if (CTX->AreMonoHacksActive()) { + // Unity uses a standard SPSC ringbuffer with cached read/write pointers and thread waiting flags at the following + // offsets, which are consistent between 32-bit and 64-bit Unity versions from 2015 onwards. + auto IsKnownAtomicDisplacement = [](uint64_t Displacement) { + return Displacement == 0x80 || Displacement == 0x84 || Displacement == 0xC0 || Displacement == 0xC4; + }; + + if (DecodeInst->OP == 0x8b && DecodeInst->Src[0].IsGPRIndirect() && + IsKnownAtomicDisplacement(DecodeInst->Src[0].Data.GPRIndirect.Displacement)) { + DecodeInst->ForceTSO = true; + } + if (DecodeInst->OP == 0x89 && DecodeInst->Dest.IsGPRIndirect() && IsKnownAtomicDisplacement(DecodeInst->Dest.Data.GPRIndirect.Displacement)) { + DecodeInst->ForceTSO = true; + } + } + return DecodedBlockStatus::SUCCESS; } diff --git a/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h b/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h index 49f10e0b2..37579b575 100644 --- a/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h +++ b/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h @@ -199,6 +199,7 @@ struct DecodedInst { uint8_t LastEscapePrefix; bool DecodedModRM; bool DecodedSIB; + bool ForceTSO; }; union ModRMDecoded {