OpcodeDispatcher: Validate instruction encodings with crc

PR #5902 technically introduced a bug where we would read past the end
of bounds for thunk instructions when full smc was enabled. Luckily this
never occurs in practice as the Mono hacks never are on VDSO boundaries,
and no one is expected to enable full smc detection really.

Switch this path over to using crc32 unconditionally. This raises our
minspec technically to armv8-a+crc, but nothing that matters shipped
without crc so it's fine.

This also is a minor speed and JIT size reduction due less branches
polluting the BTB. But really only for mono/unity games.
Requires revving the DiskCache version again.
This commit is contained in:
Ryan Houdek committed 2026-09-02 18:35:08 -07:00
1 parent d472ce190c
commit f2c8adf7a3
4 files changed
+62 -37

No files matched your search

+25 -3
View File
@@ -76,6 +76,9 @@ $end_info$
#include <unordered_map>
#include <utility>
#include <xxhash.h>
#if defined(ARCHITECTURE_arm64)
#include <arm_acle.h>
#endif
namespace FEXCore::Context {
ContextImpl::ContextImpl(const FEXCore::HostFeatures& Features)
@@ -638,9 +641,28 @@ ContextImpl::GenerateIR(FEXCore::Core::InternalThreadState* Thread, uint64_t Gue
if (Config.SMCChecks == FEXCore::Config::CONFIG_SMC_FULL || Block.ForceFullSMCDetection) {
auto ExistingCodePtr = reinterpret_cast<uint8_t*>(Block.Entry + BlockInstructionsLength);
auto InstAddressReg = Thread->OpDispatcher->_EntrypointOffset(GPRSize, InstAddress - GuestRIP);
std::array<uint8_t, 0x10> CodeOriginal;
memcpy(CodeOriginal.data(), ExistingCodePtr, DecodedInfo->InstSize);
auto CodeChanged = Thread->OpDispatcher->_ValidateCode(CodeOriginal, InstAddressReg, DecodedInfo->InstSize);
auto crc32 = [](const uint8_t* Ptr, size_t Size) -> uint32_t {
#if defined(ARCHITECTURE_arm64)
uint32_t Result {};
#define do_crc(type, suffix) \
while (Size >= sizeof(type)) { \
Result = __crc32##suffix(Result, *reinterpret_cast<const type*>(Ptr)); \
Ptr += sizeof(type); \
Size -= sizeof(type); \
}
do_crc(uint64_t, d);
do_crc(uint32_t, w);
do_crc(uint16_t, h);
do_crc(uint8_t, b);
return Result;
#else
// Unsupported on non-arm.
return 0;
#endif
};
auto CodeChanged = Thread->OpDispatcher->_ValidateCode(
Thread->OpDispatcher->Constant(crc32(ExistingCodePtr, DecodedInfo->InstSize)), InstAddressReg, DecodedInfo->InstSize);
auto InvalidateCodeCond = Thread->OpDispatcher->CondJump(CodeChanged);
+35 -32
View File
@@ -356,48 +356,51 @@ DEF_OP(Thunk) {
DEF_OP(ValidateCode) {
auto Op = IROp->C<IR::IROp_ValidateCode>();
auto OldCode = Op->CodeOriginal.data();
auto Base = GetReg(Op->Header.Args[0]).X();
auto Base = GetReg(Op->Address).X();
int len = Op->CodeLength;
int Offset = 0;
ARMEmitter::ForwardLabel Fail;
const auto Dst = GetReg(Node);
const auto CRC32Reg = GetReg(Op->crc);
auto EmitCheck = [&](size_t Size, auto&& LoadData) {
while (len >= Size) {
LoadData();
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, TMP2);
cbnz_OrRestart(ARMEmitter::Size::i64Bit, TMP1, &Fail);
len -= Size;
Offset += Size;
}
};
// Changes to TMP1
auto WorkingReg = ARMEmitter::XReg::zr;
auto BaseReg = TMP2;
auto TmpDataReg = TMP3;
mov(ARMEmitter::Size::i64Bit, BaseReg, Base);
EmitCheck(8, [&]() {
ldr(TMP1, Base, Offset);
LoadConstant(ARMEmitter::Size::i64Bit, TMP2, *(const uint64_t*)(OldCode + Offset));
});
while (len >= 8) {
ldr<ARMEmitter::IndexType::POST>(TmpDataReg, BaseReg, 8);
crc32x(TMP1, WorkingReg, TmpDataReg);
len -= 8;
WorkingReg = TMP1;
}
EmitCheck(4, [&]() {
ldr(TMP1.W(), Base, Offset);
LoadConstant(ARMEmitter::Size::i32Bit, TMP2, *(const uint32_t*)(OldCode + Offset));
});
while (len >= 4) {
ldr<ARMEmitter::IndexType::POST>(TmpDataReg.W(), BaseReg, 4);
crc32w(TMP1.W(), WorkingReg.W(), TmpDataReg.W());
len -= 4;
WorkingReg = TMP1;
}
EmitCheck(2, [&]() {
ldrh(TMP1.W(), Base, Offset);
LoadConstant(ARMEmitter::Size::i32Bit, TMP2, *(const uint16_t*)(OldCode + Offset));
});
while (len >= 2) {
ldrh<ARMEmitter::IndexType::POST>(TmpDataReg.W(), BaseReg, 2);
crc32h(TMP1.W(), WorkingReg.W(), TmpDataReg.W());
len -= 2;
WorkingReg = TMP1;
}
EmitCheck(1, [&]() {
ldrb(TMP1.W(), Base, Offset);
LoadConstant(ARMEmitter::Size::i32Bit, TMP2, *(const uint8_t*)(OldCode + Offset));
});
while (len >= 1) {
ldrb<ARMEmitter::IndexType::POST>(TmpDataReg.W(), BaseReg, 1);
crc32b(TMP1.W(), WorkingReg.W(), TmpDataReg.W());
len -= 1;
WorkingReg = TMP1;
}
sub(ARMEmitter::Size::i32Bit, Dst, TMP1, CRC32Reg);
ARMEmitter::ForwardLabel End;
LoadConstant(ARMEmitter::Size::i32Bit, Dst, 0);
b_OrRestart(&End);
BindOrRestart(&Fail);
cbz_OrRestart(ARMEmitter::Size::i32Bit, Dst, &End);
LoadConstant(ARMEmitter::Size::i32Bit, Dst, 1);
BindOrRestart(&End);
}