#include "LogManager.h" #include "Common/MathUtils.h" #include "Interface/Context/Context.h" #include "Interface/Core/DebugData.h" #include "Interface/Core/InternalThreadState.h" #include "Interface/HLE/Syscalls.h" #include "LogManager.h" #include #include #include #include #include namespace FEXCore::CPU { #define DESTMAP_AS_MAP 0 #if DESTMAP_AS_MAP using DestMapType = std::unordered_map; #else using DestMapType = std::vector; #endif class InterpreterCore final : public CPUBackend { public: explicit InterpreterCore(FEXCore::Context::Context *ctx); ~InterpreterCore() override = default; std::string GetName() override { return "Interpreter"; } void *CompileCode(FEXCore::IR::IRListView const *IR, FEXCore::Core::DebugData *DebugData) override; void *MapRegion(void* HostPtr, uint64_t, uint64_t) override { return HostPtr; } bool NeedsOpDispatch() override { return true; } void ExecuteCode(FEXCore::Core::InternalThreadState *Thread); private: FEXCore::Context::Context *CTX; uint32_t AllocateTmpSpace(size_t Size); template Res GetDest(IR::OrderedNodeWrapper Op); template Res GetSrc(IR::OrderedNodeWrapper Src); std::vector TmpSpace; DestMapType DestMap; size_t TmpOffset{}; FEXCore::IR::IRListView *CurrentIR; }; static void InterpreterExecution(FEXCore::Core::InternalThreadState *Thread) { InterpreterCore *Core = reinterpret_cast(Thread->CPUBackend.get()); Core->ExecuteCode(Thread); } InterpreterCore::InterpreterCore(FEXCore::Context::Context *ctx) : CTX {ctx} { // Grab our space for temporary data TmpSpace.resize(4096 * 32); #if !DESTMAP_AS_MAP DestMap.resize(4096); #endif } uint32_t InterpreterCore::AllocateTmpSpace(size_t Size) { // XXX: IR generation has a bug where the size can periodically end up being zero // LogMan::Throw::A(Size !=0, "Dest Op had zero destination size"); Size = Size < 16 ? 16 : Size; // Force alignment by size size_t NewBase = AlignUp(TmpOffset, Size); size_t NewEnd = NewBase + Size; if (NewEnd >= TmpSpace.size()) { // If we are going to overrun the end of our temporary space then double the size of it TmpSpace.resize(TmpSpace.size() * 2); } // Make sure to set the new offset TmpOffset = NewEnd; return NewBase; } template Res InterpreterCore::GetDest(IR::OrderedNodeWrapper Op) { auto DstPtr = &TmpSpace.at(DestMap[Op.ID()]); return reinterpret_cast(DstPtr); } template Res InterpreterCore::GetSrc(IR::OrderedNodeWrapper Src) { #if DESTMAP_AS_MAP LogMan::Throw::A(DestMap.find(Src.ID()) != DestMap.end(), "Op had source but it wasn't in the destination map"); #endif auto DstPtr = &TmpSpace.at(DestMap[Src.ID()]); LogMan::Throw::A(DstPtr != nullptr, "Destmap had slot but didn't get allocated memory"); return reinterpret_cast(DstPtr); } void *InterpreterCore::CompileCode([[maybe_unused]] FEXCore::IR::IRListView const *IR, [[maybe_unused]] FEXCore::Core::DebugData *DebugData) { return reinterpret_cast(InterpreterExecution); } void InterpreterCore::ExecuteCode(FEXCore::Core::InternalThreadState *Thread) { auto IR = Thread->IRLists.find(Thread->State.State.rip); auto DebugData = Thread->DebugData.find(Thread->State.State.rip); CurrentIR = IR->second.get(); TmpOffset = 0; // Reset where we are in the temp data range uintptr_t ListBegin = CurrentIR->GetListData(); uintptr_t DataBegin = CurrentIR->GetData(); #if DESTMAP_AS_MAP DestMap.clear(); #else uintptr_t ListSize = CurrentIR->GetSSACount(); if (ListSize > DestMap.size()) { DestMap.resize(std::max(DestMap.size() * 2, ListSize)); } #endif static_assert(sizeof(FEXCore::IR::IROp_Header) == 4); static_assert(sizeof(FEXCore::IR::OrderedNode) == 16); auto HeaderIterator = CurrentIR->begin(); IR::OrderedNodeWrapper *HeaderNodeWrapper = HeaderIterator(); IR::OrderedNode *HeaderNode = HeaderNodeWrapper->GetNode(ListBegin); auto HeaderOp = HeaderNode->Op(DataBegin)->CW(); LogMan::Throw::A(HeaderOp->Header.Op == IR::OP_IRHEADER, "First op wasn't IRHeader"); IR::OrderedNode const *BlockNode = HeaderOp->Blocks.GetNode(ListBegin); #define GD *GetDest(*WrapperOp) #define GDP GetDest(*WrapperOp) while (1) { using namespace FEXCore::IR; auto BlockIROp = BlockNode->Op(DataBegin)->C(); LogMan::Throw::A(BlockIROp->Header.Op == IR::OP_CODEBLOCK, "IR type failed to be a code block"); // We grab these nodes this way so we can iterate easily auto CodeBegin = CurrentIR->at(BlockIROp->Begin); auto CodeLast = CurrentIR->at(BlockIROp->Last); struct { bool Quit; bool Redo; } BlockResults{}; auto HandleBlock = [&]() { while (1) { OrderedNodeWrapper *WrapperOp = CodeBegin(); OrderedNode *RealNode = WrapperOp->GetNode(ListBegin); FEXCore::IR::IROp_Header *IROp = RealNode->Op(DataBegin); uint8_t OpSize = IROp->Size; uint32_t Node = WrapperOp->ID(); if (IROp->HasDest) { uint64_t AllocSize = OpSize * std::min(static_cast(1), IROp->Elements); DestMap[Node] = AllocateTmpSpace(AllocSize); } switch (IROp->Op) { case IR::OP_DUMMY: case IR::OP_BEGINBLOCK: break; case IR::OP_ENDBLOCK: { auto Op = IROp->C(); Thread->State.State.rip += Op->RIPIncrement; break; } case IR::OP_EXITFUNCTION: BlockResults.Quit = true; return; break; case IR::OP_CONDJUMP: { auto Op = IROp->C(); uint64_t Arg = *GetSrc(Op->Header.Args[0]); if (!!Arg) { BlockNode = Op->Header.Args[1].GetNode(ListBegin); } else { BlockNode = Op->Header.Args[2].GetNode(ListBegin); } BlockResults.Redo = true; return; break; } case IR::OP_JUMP: { auto Op = IROp->C(); BlockNode = Op->Header.Args[0].GetNode(ListBegin); BlockResults.Redo = true; return; break; } case IR::OP_BREAK: { auto Op = IROp->C(); switch (Op->Reason) { case 4: // HLT Thread->State.RunningEvents.ShouldStop = true; BlockResults.Quit = true; return; break; default: LogMan::Msg::A("Unknown Break Reason: %d", Op->Reason); break; } break; } case IR::OP_SYSCALL: { auto Op = IROp->C(); FEXCore::HLE::SyscallArguments Args; for (size_t j = 0; j < 7; ++j) Args.Argument[j] = *GetSrc(Op->Header.Args[j]); uint64_t Res = CTX->SyscallHandler.HandleSyscall(Thread, &Args); GD = Res; break; } case IR::OP_CPUID: { auto Op = IROp->C(); uint64_t *DstPtr = GetDest(*WrapperOp); uint64_t Arg = *GetSrc(Op->Header.Args[0]); auto Results = CTX->CPUID.RunFunction(Arg); memcpy(DstPtr, &Results.Res, sizeof(uint32_t) * 4); break; } case IR::OP_PRINT: { auto Op = IROp->C(); if (OpSize <= 8) { uint64_t Src = *GetSrc(Op->Header.Args[0]); LogMan::Msg::I(">>>> Value in Arg: 0x%lx, %ld", Src, Src); } else if (OpSize == 16) { __uint128_t Src = *GetSrc<__uint128_t*>(Op->Header.Args[0]); uint64_t Src0 = Src; uint64_t Src1 = Src >> 64; LogMan::Msg::I(">>>> Value[0] in Arg: 0x%lx, %ld", Src0, Src0); LogMan::Msg::I(" Value[1] in Arg: 0x%lx, %ld", Src1, Src1); } else LogMan::Msg::A("Unknown value size: %d", OpSize); break; } case IR::OP_CYCLECOUNTER: { #ifdef DEBUG_CYCLES GD = 0; #else timespec time; clock_gettime(CLOCK_REALTIME, &time); GD = time.tv_nsec + time.tv_sec * 1000000000; #endif break; } case IR::OP_MOV: { auto Op = IROp->C(); memcpy(GDP, GetSrc(Op->Header.Args[0]), OpSize); break; } case IR::OP_VBITCAST: { auto Op = IROp->C(); memcpy(GDP, GetSrc(Op->Header.Args[0]), 16); break; } case IR::OP_CONSTANT: { auto Op = IROp->C(); GD = Op->Constant; break; } case IR::OP_LOADCONTEXT: { auto Op = IROp->C(); uintptr_t ContextPtr = reinterpret_cast(&Thread->State.State); ContextPtr += Op->Offset; #define LOAD_CTX(x, y) \ case x: { \ y const *Data = reinterpret_cast(ContextPtr); \ GD = *Data; \ break; \ } switch (Op->Size) { LOAD_CTX(1, uint8_t) LOAD_CTX(2, uint16_t) LOAD_CTX(4, uint32_t) LOAD_CTX(8, uint64_t) case 16: { void const *Data = reinterpret_cast(ContextPtr); memcpy(GDP, Data, Op->Size); break; } default: LogMan::Msg::A("Unhandled LoadContext size: %d", Op->Size); } #undef LOAD_CTX break; } case IR::OP_STORECONTEXT: { auto Op = IROp->C(); uintptr_t ContextPtr = reinterpret_cast(&Thread->State.State); ContextPtr += Op->Offset; void *Data = reinterpret_cast(ContextPtr); void *Src = GetSrc(Op->Header.Args[0]); memcpy(Data, Src, Op->Size); break; } case IR::OP_LOADFLAG: { auto Op = IROp->C(); uintptr_t ContextPtr = reinterpret_cast(&Thread->State.State); ContextPtr += offsetof(FEXCore::Core::CPUState, flags[0]); ContextPtr += Op->Flag; uint8_t const *Data = reinterpret_cast(ContextPtr); GD = *Data; break; } case IR::OP_STOREFLAG: { auto Op = IROp->C(); uint8_t Arg = *GetSrc(Op->Header.Args[0]) & 1; uintptr_t ContextPtr = reinterpret_cast(&Thread->State.State); ContextPtr += offsetof(FEXCore::Core::CPUState, flags[0]); ContextPtr += Op->Flag; uint8_t *Data = reinterpret_cast(ContextPtr); *Data = Arg; break; } case IR::OP_LOADMEM: { auto Op = IROp->C(); void const *Data = Thread->CTX->MemoryMapper.GetPointer(*GetSrc(Op->Header.Args[0])); LogMan::Throw::A(Data != nullptr, "Couldn't Map pointer to 0x%lx\n", *GetSrc(Op->Header.Args[0])); memcpy(GDP, Data, OpSize); break; } case IR::OP_STOREMEM: { #define STORE_DATA(x, y) \ case x: { \ uint64_t SrcPtr = *GetSrc(Op->Header.Args[0]); \ y *Data = Thread->CTX->MemoryMapper.GetPointer(SrcPtr); \ LogMan::Throw::A(Data != nullptr, "Couldn't Map pointer to 0x%lx for size %d store\n", *GetSrc(Op->Header.Args[0]), x);\ memcpy(Data, GetSrc(Op->Header.Args[1]), sizeof(y)); \ break; \ } auto Op = IROp->C(); switch (Op->Size) { STORE_DATA(1, uint8_t) STORE_DATA(2, uint16_t) STORE_DATA(4, uint32_t) STORE_DATA(8, uint64_t) case 16: { void *Mem = Thread->CTX->MemoryMapper.GetPointer(*GetSrc(Op->Header.Args[0])); void *Src = GetSrc(Op->Header.Args[1]); memcpy(Mem, Src, 16); break; } default: LogMan::Msg::A("Unhandled StoreMem size"); break; } #undef STORE_DATA break; } case IR::OP_ADD: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); GD = Src1 + Src2; break; } case IR::OP_SUB: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); GD = Src1 - Src2; break; } case IR::OP_OR: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); GD = Src1 | Src2; break; } case IR::OP_AND: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); GD = Src1 & Src2; break; } case IR::OP_XOR: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); GD = Src1 ^ Src2; break; } case IR::OP_LSHL: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); uint8_t Mask = OpSize * 8 - 1; GD = Src1 << (Src2 & Mask); break; } case IR::OP_LSHR: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); uint8_t Mask = OpSize * 8 - 1; GD = Src1 >> (Src2 & Mask); break; } case IR::OP_ASHR: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); uint8_t Mask = OpSize * 8 - 1; switch (OpSize) { case 1: GD = static_cast(Src1) >> (Src2 & Mask); break; case 2: GD = static_cast(Src1) >> (Src2 & Mask); break; case 4: GD = static_cast(Src1) >> (Src2 & Mask); break; case 8: GD = static_cast(Src1) >> (Src2 & Mask); break; default: LogMan::Msg::A("Unknown ASHR Size: %d\n", OpSize); break; }; break; } case IR::OP_ROR: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); auto Ror = [] (auto In, auto R) { auto RotateMask = sizeof(In) * 8 - 1; R &= RotateMask; return (In >> R) | (In << (sizeof(In) * 8 - R)); }; switch (OpSize) { case 1: GD = Ror(static_cast(Src1), static_cast(Src2)); break; case 2: GD = Ror(static_cast(Src1), static_cast(Src2)); break; case 4: GD = Ror(static_cast(Src1), static_cast(Src2)); break; case 8: { GD = Ror(static_cast(Src1), static_cast(Src2)); break; } default: LogMan::Msg::A("Unknown ROR Size: %d\n", OpSize); break; } break; } case IR::OP_ROL: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); auto Rol = [] (auto In, auto R) { auto RotateMask = sizeof(In) * 8 - 1; R &= RotateMask; return (In << R) | (In >> (sizeof(In) * 8 - R)); }; switch (OpSize) { case 1: GD = Rol(static_cast(Src1), static_cast(Src2)); break; case 2: GD = Rol(static_cast(Src1), static_cast(Src2)); break; case 4: GD = Rol(static_cast(Src1), static_cast(Src2)); break; case 8: { GD = Rol(static_cast(Src1), static_cast(Src2)); break; } default: LogMan::Msg::A("Unknown ROL Size: %d\n", OpSize); break; } break; } case IR::OP_NEG: { auto Op = IROp->C(); uint64_t Src = *GetSrc(Op->Header.Args[0]); GD = ~Src; break; } case IR::OP_ZEXT: { auto Op = IROp->C(); LogMan::Throw::A(Op->SrcSize <= 64, "Can't support Zext of size: %ld", Op->SrcSize); uint64_t Src = *GetSrc(Op->Header.Args[0]); if (Op->SrcSize == 64) { // Zext 64bit to 128bit __uint128_t SrcLarge = Src; memcpy(GDP, &SrcLarge, 16); } else { GD = Src & ((1ULL << Op->SrcSize) - 1); } break; } case IR::OP_SEXT: { auto Op = IROp->C(); LogMan::Throw::A(Op->SrcSize <= 64, "Can't support Zext of size: %ld", Op->SrcSize); switch (Op->SrcSize / 8) { case 1: GD = *GetSrc(Op->Header.Args[0]); break; case 2: GD = *GetSrc(Op->Header.Args[0]); break; case 4: GD = *GetSrc(Op->Header.Args[0]); break; case 8: GD = *GetSrc(Op->Header.Args[0]); break; default: LogMan::Msg::A("Unknown Sext size: %d", Op->SrcSize / 8); } break; } case IR::OP_MUL: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); switch (OpSize) { case 1: GD = static_cast(static_cast(Src1)) * static_cast(static_cast(Src2)); break; case 2: GD = static_cast(static_cast(Src1)) * static_cast(static_cast(Src2)); break; case 4: GD = static_cast(static_cast(Src1)) * static_cast(static_cast(Src2)); break; case 8: GD = static_cast(Src1) * static_cast(Src2); break; case 16: { __int128_t Tmp = static_cast<__int128_t>(static_cast(Src1)) * static_cast<__int128_t>(static_cast(Src2)); memcpy(GDP, &Tmp, 16); break; } default: LogMan::Msg::A("Unknown Mul Size: %d\n", OpSize); break; } break; } case IR::OP_MULH: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); switch (OpSize) { case 1: { int64_t Tmp = static_cast(static_cast(Src1)) * static_cast(static_cast(Src2)); GD = Tmp >> 8; break; } case 2: { int64_t Tmp = static_cast(static_cast(Src1)) * static_cast(static_cast(Src2)); GD = Tmp >> 16; break; } case 4: { int64_t Tmp = static_cast(static_cast(Src1)) * static_cast(static_cast(Src2)); GD = Tmp >> 32; break; } case 8: { __int128_t Tmp = static_cast<__int128_t>(static_cast(Src1)) * static_cast<__int128_t>(static_cast(Src2)); GD = Tmp >> 64; } break; default: LogMan::Msg::A("Unknown MulH Size: %d\n", OpSize); break; } break; } case IR::OP_UMUL: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); switch (OpSize) { case 1: GD = static_cast(Src1) * static_cast(Src2); break; case 2: GD = static_cast(Src1) * static_cast(Src2); break; case 4: GD = static_cast(Src1) * static_cast(Src2); break; case 8: GD = static_cast(Src1) * static_cast(Src2); break; case 16: { __uint128_t Tmp = static_cast<__uint128_t>(static_cast(Src1)) * static_cast<__uint128_t>(static_cast(Src2)); memcpy(GDP, &Tmp, 16); break; } default: LogMan::Msg::A("Unknown UMul Size: %d\n", OpSize); break; } break; } case IR::OP_UMULH: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); switch (OpSize) { case 1: GD = static_cast(Src1) * static_cast(Src2); GD >>= 8; break; case 2: GD = static_cast(Src1) * static_cast(Src2); GD >>= 16; break; case 4: GD = static_cast(Src1) * static_cast(Src2); GD >>= 32; break; case 8: { __uint128_t Tmp = static_cast<__uint128_t>(Src1) * static_cast<__uint128_t>(Src2); GD = Tmp >> 64; break; } case 16: { // XXX: This is incorrect __uint128_t Tmp = static_cast<__uint128_t>(Src1) * static_cast<__uint128_t>(Src2); GD = Tmp >> 64; break; } default: LogMan::Msg::A("Unknown UMulH Size: %d\n", OpSize); break; } break; } case IR::OP_DIV: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); switch (OpSize) { case 1: GD = static_cast(static_cast(Src1)) / static_cast(static_cast(Src2)); break; case 2: GD = static_cast(static_cast(Src1)) / static_cast(static_cast(Src2)); break; case 4: GD = static_cast(static_cast(Src1)) / static_cast(static_cast(Src2)); break; case 8: GD = static_cast(Src1) / static_cast(Src2); break; case 16: { __int128_t Tmp = *GetSrc<__int128_t*>(Op->Header.Args[0]) / *GetSrc<__int128_t*>(Op->Header.Args[1]); memcpy(GDP, &Tmp, 16); break; } default: LogMan::Msg::A("Unknown Mul Size: %d\n", OpSize); break; } break; } case IR::OP_UDIV: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); switch (OpSize) { case 1: GD = static_cast(static_cast(Src1)) / static_cast(static_cast(Src2)); break; case 2: GD = static_cast(static_cast(Src1)) / static_cast(static_cast(Src2)); break; case 4: GD = static_cast(static_cast(Src1)) / static_cast(static_cast(Src2)); break; case 8: GD = static_cast(Src1) / static_cast(Src2); break; case 16: { __uint128_t Tmp = *GetSrc<__uint128_t*>(Op->Header.Args[0]) / *GetSrc<__uint128_t*>(Op->Header.Args[1]); memcpy(GDP, &Tmp, 16); break; } default: LogMan::Msg::A("Unknown Mul Size: %d\n", OpSize); break; } break; } case IR::OP_REM: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); switch (OpSize) { case 1: GD = static_cast(static_cast(Src1)) % static_cast(static_cast(Src2)); break; case 2: GD = static_cast(static_cast(Src1)) % static_cast(static_cast(Src2)); break; case 4: GD = static_cast(static_cast(Src1)) % static_cast(static_cast(Src2)); break; case 8: GD = static_cast(Src1) % static_cast(Src2); break; case 16: { __int128_t Tmp = *GetSrc<__int128_t*>(Op->Header.Args[0]) % *GetSrc<__int128_t*>(Op->Header.Args[1]); memcpy(GDP, &Tmp, 16); break; } default: LogMan::Msg::A("Unknown Mul Size: %d\n", OpSize); break; } break; } case IR::OP_UREM: { auto Op = IROp->C(); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); switch (OpSize) { case 1: GD = static_cast(static_cast(Src1)) % static_cast(static_cast(Src2)); break; case 2: GD = static_cast(static_cast(Src1)) % static_cast(static_cast(Src2)); break; case 4: GD = static_cast(static_cast(Src1)) % static_cast(static_cast(Src2)); break; case 8: GD = static_cast(Src1) % static_cast(Src2); break; case 16: { __uint128_t Tmp = *GetSrc<__uint128_t*>(Op->Header.Args[0]) % *GetSrc<__uint128_t*>(Op->Header.Args[1]); memcpy(GDP, &Tmp, 16); break; } default: LogMan::Msg::A("Unknown Mul Size: %d\n", OpSize); break; } break; } case IR::OP_POPCOUNT: { auto Op = IROp->C(); uint64_t Src = *GetSrc(Op->Header.Args[0]); GD = __builtin_popcountl(Src); break; } case IR::OP_FINDLSB: { auto Op = IROp->C(); uint64_t Src = *GetSrc(Op->Header.Args[0]); uint64_t Result = __builtin_ffsll(Src); GD = Result - 1; break; } case IR::OP_FINDMSB: { auto Op = IROp->C(); uint64_t Src = *GetSrc(Op->Header.Args[0]); uint64_t Result = Op->Header.Size * 8 - __builtin_clzll(Src); GD = Result; break; } case IR::OP_REV: { auto Op = IROp->C(); switch (OpSize) { case 2: GD = __builtin_bswap16(*GetSrc(Op->Header.Args[0])); break; case 4: GD = __builtin_bswap32(*GetSrc(Op->Header.Args[0])); break; case 8: GD = __builtin_bswap64(*GetSrc(Op->Header.Args[0])); break; default: LogMan::Msg::A("Unknown REV size: %d", OpSize); break; } break; } case IR::OP_BFI: { auto Op = IROp->C(); uint64_t SourceMask = (1ULL << Op->Width) - 1; if (Op->Width == 64) SourceMask = ~0ULL; uint64_t DestMask = ~(SourceMask << Op->lsb); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); uint64_t Res = (Src1 & DestMask) | ((Src2 & SourceMask) << Op->lsb); GD = Res; break; } case IR::OP_BFE: { auto Op = IROp->C(); LogMan::Throw::A(OpSize <= 16, "OpSize is too large for BFE: %d", OpSize); if (OpSize == 16) { LogMan::Throw::A(Op->Width <= 64, "Can't extract width of %d", Op->Width); __uint128_t SourceMask = (1ULL << Op->Width) - 1; if (Op->Width == 64) SourceMask = ~0ULL; SourceMask <<= Op->lsb; __uint128_t Src = (*GetSrc<__uint128_t*>(Op->Header.Args[0]) & SourceMask) >> Op->lsb; memcpy(GDP, &Src, OpSize); } else { uint64_t SourceMask = (1ULL << Op->Width) - 1; if (Op->Width == 64) SourceMask = ~0ULL; SourceMask <<= Op->lsb; uint64_t Src = *GetSrc(Op->Header.Args[0]); GD = (Src & SourceMask) >> Op->lsb; } break; } case IR::OP_SELECT: { auto Op = IROp->C(); bool CompResult = false; uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); uint64_t ArgTrue = *GetSrc(Op->Header.Args[2]); uint64_t ArgFalse = *GetSrc(Op->Header.Args[3]); switch (Op->Cond.Val) { case FEXCore::IR::COND_EQ: CompResult = Src1 == Src2; break; case FEXCore::IR::COND_NEQ: CompResult = Src1 != Src2; break; case FEXCore::IR::COND_GE: CompResult = Src1 >= Src2; break; case FEXCore::IR::COND_LT: CompResult = Src1 < Src2; break; case FEXCore::IR::COND_GT: CompResult = Src1 > Src2; break; case FEXCore::IR::COND_LE: CompResult = Src1 <= Src2; break; case FEXCore::IR::COND_CS: case FEXCore::IR::COND_CC: case FEXCore::IR::COND_MI: case FEXCore::IR::COND_PL: case FEXCore::IR::COND_VS: case FEXCore::IR::COND_VC: case FEXCore::IR::COND_HI: case FEXCore::IR::COND_LS: default: LogMan::Msg::A("Unsupported compare type"); break; } GD = CompResult ? ArgTrue : ArgFalse; break; } case IR::OP_EXTRACTELEMENT: { auto ExtractElementOp = IROp->C(); uintptr_t DstPtr = GetDest(*WrapperOp); uintptr_t SrcPtr = GetSrc(ExtractElementOp->Header.Args[0]); // Offset to the element offset SrcPtr += IROp->Size * ExtractElementOp->Idx; memcpy(reinterpret_cast(DstPtr), reinterpret_cast(SrcPtr), IROp->Size); break; } case IR::OP_CAS: { auto Op = IROp->C(); auto Size = OpSize; switch (Size) { case 1: { std::atomic *Data = Thread->CTX->MemoryMapper.GetPointer *>(*GetSrc(Op->Header.Args[2])); LogMan::Throw::A(Data != nullptr, "Couldn't Map pointer to 0x%lx\n", *GetSrc(Op->Header.Args[2])); uint8_t Src1 = *GetSrc(Op->Header.Args[0]); uint8_t Src2 = *GetSrc(Op->Header.Args[1]); uint8_t Expected = Src1; bool Result = Data->compare_exchange_strong(Expected, Src2); GD = Result ? Src1 : Expected; break; } case 2: { std::atomic *Data = Thread->CTX->MemoryMapper.GetPointer *>(*GetSrc(Op->Header.Args[2])); LogMan::Throw::A(Data != nullptr, "Couldn't Map pointer to 0x%lx\n", *GetSrc(Op->Header.Args[2])); uint16_t Src1 = *GetSrc(Op->Header.Args[0]); uint16_t Src2 = *GetSrc(Op->Header.Args[1]); uint16_t Expected = Src1; bool Result = Data->compare_exchange_strong(Expected, Src2); GD = Result ? Src1 : Expected; break; } case 4: { std::atomic *Data = Thread->CTX->MemoryMapper.GetPointer *>(*GetSrc(Op->Header.Args[2])); LogMan::Throw::A(Data != nullptr, "Couldn't Map pointer to 0x%lx\n", *GetSrc(Op->Header.Args[2])); uint32_t Src1 = *GetSrc(Op->Header.Args[0]); uint32_t Src2 = *GetSrc(Op->Header.Args[1]); uint32_t Expected = Src1; bool Result = Data->compare_exchange_strong(Expected, Src2); GD = Result ? Src1 : Expected; break; } case 8: { std::atomic *Data = Thread->CTX->MemoryMapper.GetPointer *>(*GetSrc(Op->Header.Args[2])); LogMan::Throw::A(Data != nullptr, "Couldn't Map pointer to 0x%lx\n", *GetSrc(Op->Header.Args[2])); uint64_t Src1 = *GetSrc(Op->Header.Args[0]); uint64_t Src2 = *GetSrc(Op->Header.Args[1]); uint64_t Expected = Src1; bool Result = Data->compare_exchange_strong(Expected, Src2); GD = Result ? Src1 : Expected; break; } default: LogMan::Msg::A("Unknown CAS size: %d", Size); break; } break; } // Vector ops case IR::OP_CREATEVECTOR2: { auto Op = IROp->C(); LogMan::Throw::A(OpSize <= 16, "Can't handle a vector of size: %d", OpSize); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t ElementSize = OpSize / 2; #define CREATE_VECTOR(elementsize, type) \ case elementsize: { \ auto *Dst_d = reinterpret_cast(Tmp); \ auto *Src1_d = reinterpret_cast(Src1); \ auto *Src2_d = reinterpret_cast(Src2); \ Dst_d[0] = *Src1_d; \ Dst_d[1] = *Src2_d; \ break; \ } switch (ElementSize) { CREATE_VECTOR(1, uint8_t) CREATE_VECTOR(2, uint16_t) CREATE_VECTOR(4, uint32_t) CREATE_VECTOR(8, uint64_t) default: LogMan::Msg::A("Unknown Element Size: %d", ElementSize); break; } #undef CREATE_VECTOR memcpy(GDP, Tmp, OpSize); break; } case IR::OP_SPLATVECTOR4: case IR::OP_SPLATVECTOR3: case IR::OP_SPLATVECTOR2: { auto Op = IROp->C(); LogMan::Throw::A(OpSize <= 16, "Can't handle a vector of size: %d", OpSize); void *Src = GetSrc(Op->Header.Args[0]); uint8_t Tmp[16]; uint8_t Elements = 0; switch (Op->Header.Op) { case IR::OP_SPLATVECTOR4: Elements = 4; break; case IR::OP_SPLATVECTOR3: Elements = 3; break; case IR::OP_SPLATVECTOR2: Elements = 2; break; default: LogMan::Msg::A("Uknown Splat size"); break; } uint8_t ElementSize = OpSize / Elements; #define CREATE_VECTOR(elementsize, type) \ case elementsize: { \ auto *Dst_d = reinterpret_cast(Tmp); \ auto *Src_d = reinterpret_cast(Src); \ for (uint8_t i = 0; i < Elements; ++i) \ Dst_d[i] = *Src_d;\ break; \ } switch (ElementSize) { CREATE_VECTOR(1, uint8_t) CREATE_VECTOR(2, uint16_t) CREATE_VECTOR(4, uint32_t) CREATE_VECTOR(8, uint64_t) default: LogMan::Msg::A("Unknown Element Size: %d", ElementSize); break; } #undef CREATE_VECTOR memcpy(GDP, Tmp, OpSize); break; } case IR::OP_VOR: { auto Op = IROp->C(); __uint128_t Src1 = *GetSrc<__uint128_t*>(Op->Header.Args[0]); __uint128_t Src2 = *GetSrc<__uint128_t*>(Op->Header.Args[1]); __uint128_t Dst = Src1 | Src2; memcpy(GDP, &Dst, 16); break; } case IR::OP_VXOR: { auto Op = IROp->C(); __uint128_t Src1 = *GetSrc<__uint128_t*>(Op->Header.Args[0]); __uint128_t Src2 = *GetSrc<__uint128_t*>(Op->Header.Args[1]); __uint128_t Dst = Src1 ^ Src2; memcpy(GDP, &Dst, 16); break; } case IR::OP_VSLI: { auto Op = IROp->C(); __uint128_t Src1 = *GetSrc<__uint128_t*>(Op->Header.Args[0]); __uint128_t Src2 = Op->ByteShift; __uint128_t Dst = Src1 << (Src2 * 8); memcpy(GDP, &Dst, 16); break; } case IR::OP_VSRI: { auto Op = IROp->C(); __uint128_t Src1 = *GetSrc<__uint128_t*>(Op->Header.Args[0]); __uint128_t Src2 = Op->ByteShift; __uint128_t Dst = Src1 >> (Src2 * 8); memcpy(GDP, &Dst, 16); break; } #define DO_VECTOR_OP(size, type, func) \ case size: { \ auto *Dst_d = reinterpret_cast(Tmp); \ auto *Src1_d = reinterpret_cast(Src1); \ auto *Src2_d = reinterpret_cast(Src2); \ for (uint8_t i = 0; i < Elements; ++i) { \ Dst_d[i] = func(Src1_d[i], Src2_d[i]); \ } \ break; \ } #define DO_VECTOR_SCALAR_OP(size, type, func)\ case size: { \ auto *Dst_d = reinterpret_cast(Tmp); \ auto *Src1_d = reinterpret_cast(Src1); \ auto *Src2_d = reinterpret_cast(Src2); \ for (uint8_t i = 0; i < Elements; ++i) { \ Dst_d[i] = func(Src1_d[i], *Src2_d); \ } \ break; \ } case IR::OP_VADD: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return a + b; }; switch (Op->ElementSize) { DO_VECTOR_OP(1, uint8_t, Func) DO_VECTOR_OP(2, uint16_t, Func) DO_VECTOR_OP(4, uint32_t, Func) DO_VECTOR_OP(8, uint64_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VSUB: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return a - b; }; switch (Op->ElementSize) { DO_VECTOR_OP(1, uint8_t, Func) DO_VECTOR_OP(2, uint16_t, Func) DO_VECTOR_OP(4, uint32_t, Func) DO_VECTOR_OP(8, uint64_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VUMIN: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return std::min(a, b); }; switch (Op->ElementSize) { DO_VECTOR_OP(1, uint8_t, Func) DO_VECTOR_OP(2, uint16_t, Func) DO_VECTOR_OP(4, uint32_t, Func) DO_VECTOR_OP(8, uint64_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VSMIN: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return std::min(a, b); }; switch (Op->ElementSize) { DO_VECTOR_OP(1, int8_t, Func) DO_VECTOR_OP(2, int16_t, Func) DO_VECTOR_OP(4, int32_t, Func) DO_VECTOR_OP(8, int64_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VUSHL: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return a << b; }; switch (Op->ElementSize) { DO_VECTOR_OP(1, uint8_t, Func) DO_VECTOR_OP(2, uint16_t, Func) DO_VECTOR_OP(4, uint32_t, Func) DO_VECTOR_OP(8, uint64_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VUSHLS: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return a << b; }; switch (Op->ElementSize) { DO_VECTOR_SCALAR_OP(1, uint8_t, Func) DO_VECTOR_SCALAR_OP(2, uint16_t, Func) DO_VECTOR_SCALAR_OP(4, uint32_t, Func) DO_VECTOR_SCALAR_OP(8, uint64_t, Func) DO_VECTOR_SCALAR_OP(16, __uint128_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VUSHR: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return a >> b; }; switch (Op->ElementSize) { DO_VECTOR_OP(1, uint8_t, Func) DO_VECTOR_OP(2, uint16_t, Func) DO_VECTOR_OP(4, uint32_t, Func) DO_VECTOR_OP(8, uint64_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VZIP2: case IR::OP_VZIP: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; uint8_t BaseOffset = IROp->Op == IR::OP_VZIP2 ? (Elements / 2) : 0; Elements >>= 1; switch (Op->ElementSize) { case 1: { auto *Dst_d = reinterpret_cast(Tmp); auto *Src1_d = reinterpret_cast(Src1); auto *Src2_d = reinterpret_cast(Src2); for (unsigned i = 0; i < Elements; ++i) { Dst_d[i*2] = Src1_d[BaseOffset + i]; Dst_d[i*2+1] = Src2_d[BaseOffset + i]; } break; } case 2: { auto *Dst_d = reinterpret_cast(Tmp); auto *Src1_d = reinterpret_cast(Src1); auto *Src2_d = reinterpret_cast(Src2); for (unsigned i = 0; i < Elements; ++i) { Dst_d[i*2] = Src1_d[BaseOffset + i]; Dst_d[i*2+1] = Src2_d[BaseOffset + i]; } break; } case 4: { auto *Dst_d = reinterpret_cast(Tmp); auto *Src1_d = reinterpret_cast(Src1); auto *Src2_d = reinterpret_cast(Src2); for (unsigned i = 0; i < Elements; ++i) { Dst_d[i*2] = Src1_d[BaseOffset + i]; Dst_d[i*2+1] = Src2_d[BaseOffset + i]; } break; } case 8: { auto *Dst_d = reinterpret_cast(Tmp); auto *Src1_d = reinterpret_cast(Src1); auto *Src2_d = reinterpret_cast(Src2); for (unsigned i = 0; i < Elements; ++i) { Dst_d[i*2] = Src1_d[BaseOffset + i]; Dst_d[i*2+1] = Src2_d[BaseOffset + i]; } break; } default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VINSELEMENT: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; // Copy src1 in to dest memcpy(Tmp, Src1, Op->RegisterSize); switch (Op->ElementSize) { case 1: { auto *Dst_d = reinterpret_cast(Tmp); auto *Src2_d = reinterpret_cast(Src2); Dst_d[Op->DestIdx] = Src2_d[Op->SrcIdx]; break; } case 2: { auto *Dst_d = reinterpret_cast(Tmp); auto *Src2_d = reinterpret_cast(Src2); Dst_d[Op->DestIdx] = Src2_d[Op->SrcIdx]; break; } case 4: { auto *Dst_d = reinterpret_cast(Tmp); auto *Src2_d = reinterpret_cast(Src2); Dst_d[Op->DestIdx] = Src2_d[Op->SrcIdx]; break; } case 8: { auto *Dst_d = reinterpret_cast(Tmp); auto *Src2_d = reinterpret_cast(Src2); Dst_d[Op->DestIdx] = Src2_d[Op->SrcIdx]; break; } default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; }; memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VCMPEQ: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return a == b ? ~0ULL : 0; }; switch (Op->ElementSize) { DO_VECTOR_OP(1, uint8_t, Func) DO_VECTOR_OP(2, uint16_t, Func) DO_VECTOR_OP(4, uint32_t, Func) DO_VECTOR_OP(8, uint64_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_VCMPGT: { auto Op = IROp->C(); void *Src1 = GetSrc(Op->Header.Args[0]); void *Src2 = GetSrc(Op->Header.Args[1]); uint8_t Tmp[16]; uint8_t Elements = Op->RegisterSize / Op->ElementSize; auto Func = [](auto a, auto b) { return a > b ? ~0ULL : 0; }; switch (Op->ElementSize) { DO_VECTOR_OP(1, int8_t, Func) DO_VECTOR_OP(2, int16_t, Func) DO_VECTOR_OP(4, int32_t, Func) DO_VECTOR_OP(8, int64_t, Func) default: LogMan::Msg::A("Unknown Element Size: %d", Op->ElementSize); break; } memcpy(GDP, Tmp, Op->RegisterSize); break; } case IR::OP_LUDIV: { auto Op = IROp->C(); // Each source is OpSize in size // So you can have up to a 128bit divide from x86-64 switch (OpSize) { case 4: { uint32_t SrcLow = *GetSrc(Op->Header.Args[0]); uint32_t SrcHigh = *GetSrc(Op->Header.Args[1]); uint32_t Divisor = *GetSrc(Op->Header.Args[2]); uint64_t Source = (static_cast(SrcHigh) << 32) | SrcLow; uint64_t Res = Source / Divisor; // We only store the lower bits of the result GD = static_cast(Res); break; } case 8: { uint64_t SrcLow = *GetSrc(Op->Header.Args[0]); uint64_t SrcHigh = *GetSrc(Op->Header.Args[1]); uint64_t Divisor = *GetSrc(Op->Header.Args[2]); __uint128_t Source = (static_cast<__uint128_t>(SrcHigh) << 64) | SrcLow; __uint128_t Res = Source / Divisor; // We only store the lower bits of the result memcpy(GDP, &Res, OpSize); break; } default: LogMan::Msg::A("Unknown LUDIV Size: %d", OpSize); break; } break; } case IR::OP_LDIV: { auto Op = IROp->C(); // Each source is OpSize in size // So you can have up to a 128bit divide from x86-64 switch (OpSize) { case 4: { uint32_t SrcLow = *GetSrc(Op->Header.Args[0]); uint32_t SrcHigh = *GetSrc(Op->Header.Args[1]); int32_t Divisor = *GetSrc(Op->Header.Args[2]); int64_t Source = (static_cast(SrcHigh) << 32) | SrcLow; int64_t Res = Source / Divisor; // We only store the lower bits of the result GD = static_cast(Res); break; } case 8: { uint64_t SrcLow = *GetSrc(Op->Header.Args[0]); uint64_t SrcHigh = *GetSrc(Op->Header.Args[1]); int64_t Divisor = *GetSrc(Op->Header.Args[2]); __int128_t Source = (static_cast<__int128_t>(SrcHigh) << 64) | SrcLow; __int128_t Res = Source / Divisor; // We only store the lower bits of the result memcpy(GDP, &Res, OpSize); break; } default: LogMan::Msg::A("Unknown LDIV Size: %d", OpSize); break; } break; } case IR::OP_LUREM: { auto Op = IROp->C(); // Each source is OpSize in size // So you can have up to a 128bit Remainder from x86-64 switch (OpSize) { case 4: { uint32_t SrcLow = *GetSrc(Op->Header.Args[0]); uint32_t SrcHigh = *GetSrc(Op->Header.Args[1]); uint32_t Divisor = *GetSrc(Op->Header.Args[2]); uint64_t Source = (static_cast(SrcHigh) << 32) | SrcLow; uint64_t Res = Source % Divisor; // We only store the lower bits of the result GD = static_cast(Res); break; } case 8: { uint64_t SrcLow = *GetSrc(Op->Header.Args[0]); uint64_t SrcHigh = *GetSrc(Op->Header.Args[1]); uint64_t Divisor = *GetSrc(Op->Header.Args[2]); __uint128_t Source = (static_cast<__uint128_t>(SrcHigh) << 64) | SrcLow; __uint128_t Res = Source % Divisor; // We only store the lower bits of the result memcpy(GDP, &Res, OpSize); break; } default: LogMan::Msg::A("Unknown LUREM Size: %d", OpSize); break; } break; } case IR::OP_LREM: { auto Op = IROp->C(); // Each source is OpSize in size // So you can have up to a 128bit Remainder from x86-64 switch (OpSize) { case 4: { uint32_t SrcLow = *GetSrc(Op->Header.Args[0]); uint32_t SrcHigh = *GetSrc(Op->Header.Args[1]); int32_t Divisor = *GetSrc(Op->Header.Args[2]); int64_t Source = (static_cast(SrcHigh) << 32) | SrcLow; int64_t Res = Source % Divisor; // We only store the lower bits of the result GD = static_cast(Res); break; } case 8: { uint64_t SrcLow = *GetSrc(Op->Header.Args[0]); uint64_t SrcHigh = *GetSrc(Op->Header.Args[1]); int64_t Divisor = *GetSrc(Op->Header.Args[2]); __int128_t Source = (static_cast<__int128_t>(SrcHigh) << 64) | SrcLow; __int128_t Res = Source % Divisor; // We only store the lower bits of the result memcpy(GDP, &Res, OpSize); break; } default: LogMan::Msg::A("Unknown LREM Size: %d", OpSize); break; } break; } case IR::OP_VEXTR: { auto Op = IROp->C(); __uint128_t Src1 = *GetSrc<__uint128_t*>(Op->Header.Args[0]); __uint128_t Src2 = *GetSrc<__uint128_t*>(Op->Header.Args[1]); uint8_t Offset = Op->Index * 8; __uint128_t Dst = (Src1 << (sizeof(__uint128_t) - Offset)) | (Src2 >> Offset); memcpy(GDP, &Dst, 16); break; } default: LogMan::Msg::A("Unknown IR Op: %d(%s)", IROp->Op, FEXCore::IR::GetName(IROp->Op).data()); break; } // CodeLast is inclusive. So we still need to dump the CodeLast op as well if (CodeBegin == CodeLast) { break; } ++CodeBegin; } }; HandleBlock(); if (BlockResults.Redo) { continue; } if (BlockIROp->Next.ID() == 0 || BlockResults.Quit) { break; } else { BlockNode = BlockIROp->Next.GetNode(ListBegin); } } Thread->Stats.InstructionsExecuted.fetch_add(DebugData->second.GuestInstructionCount); } FEXCore::CPU::CPUBackend *CreateInterpreterCore(FEXCore::Context::Context *ctx) { return new InterpreterCore(ctx); } }