// SPDX-License-Identifier: MIT /* $info$ tags: frontend|x86-to-ir, opcodes|dispatcher-implementations desc: Handles x86/64 ops to IR, no-pf opt, local-flags opt $end_info$ */ #include "FEXCore/Core/HostFeatures.h" #include "FEXCore/Utils/Telemetry.h" #include "Interface/Context/Context.h" #include "Interface/Core/OpcodeDispatcher.h" #include "Interface/Core/X86Tables/X86Tables.h" #include "Interface/IR/IR.h" #include "Interface/IR/IREmitter.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace FEXCore::IR { using X86Tables::OpToIndex; #define OpcodeArgs [[maybe_unused]] FEXCore::X86Tables::DecodedOp Op void OpDispatchBuilder::SyscallOp(OpcodeArgs, bool IsSyscallInst) { // Calculate flags early. CalculateDeferredFlags(); const auto GPRSize = GetGPROpSize(); auto NewRIP = GetRelocatedPC(Op, -Op->InstSize); _StoreContextGPR(GPRSize, NewRIP, offsetof(FEXCore::Core::CPUState, rip)); if (IsSyscallInst) { // If this is the `Syscall` instruction rather than `int 0x80` then we need to do some additional work. // RCX = RIP after this instruction // R11 = EFlags // Calculate flags. CalculateDeferredFlags(); auto RFLAG = GetPackedRFLAG(); StoreGPRRegister(X86State::REG_R11, RFLAG, OpSize::i64Bit); auto RIPAfterInst = GetRelocatedPC(Op); StoreGPRRegister(X86State::REG_RCX, RIPAfterInst, OpSize::i64Bit); } FlushRegisterCache(); _Syscall(); if (Op->TableInfo->Flags & X86Tables::InstFlags::FLAGS_BLOCK_END) { // RIP could have been updated after coming back from the Syscall. NewRIP = _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, rip)); ExitFunction(NewRIP); } } void OpDispatchBuilder::ThunkOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); uint8_t* sha256 = (uint8_t*)(Op->PC + 2); if (Is64BitMode) { // x86-64 ABI puts the function argument in RDI Thunk(LoadGPRRegister(X86State::REG_RDI), *reinterpret_cast(sha256)); } else { // x86 fastcall ABI puts the function argument in ECX Thunk(LoadGPRRegister(X86State::REG_RCX), *reinterpret_cast(sha256)); } auto NewRIP = Pop(GPRSize); // Store the new RIP ExitFunction(NewRIP, BranchHint::Return); BlockSetRIP = true; } void OpDispatchBuilder::LEAOp(OpcodeArgs) { // LEA specifically ignores segment prefixes const auto SrcSize = OpSizeFromSrc(Op); const auto OpAddr = X86Tables::DecodeFlags::GetOpAddr(Op->Flags, 0); OpSize DstSize {}; if (Is64BitMode) { DstSize = OpAddr == X86Tables::DecodeFlags::FLAG_OPERAND_SIZE_LAST ? OpSize::i16Bit : OpAddr == X86Tables::DecodeFlags::FLAG_WIDENING_SIZE_LAST ? OpSize::i64Bit : OpSize::i32Bit; } else { DstSize = OpAddr == X86Tables::DecodeFlags::FLAG_OPERAND_SIZE_LAST ? OpSize::i16Bit : OpSize::i32Bit; } auto Src = LoadSourceGPR_WithOpSize(Op, Op->Src[0], SrcSize, Op->Flags, {.LoadData = false, .AllowUpperGarbage = SrcSize > DstSize}); StoreResultGPR_WithOpSize(Op, Op->Dest, Src, DstSize); } void OpDispatchBuilder::NOPOp(OpcodeArgs) {} void OpDispatchBuilder::RETOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); Ref SP = _RMWHandle(LoadGPRRegister(X86State::REG_RSP)); Ref NewRIP = Pop(GPRSize, SP); if (Op->OP == 0xC2) { auto Offset = LoadSourceGPR(Op, Op->Src[0], Op->Flags); SP = Add(GPRSize, SP, Offset); } // Store the new stack pointer StoreGPRRegister(X86State::REG_RSP, SP); // Store the new RIP ExitFunction(NewRIP, BranchHint::Return); BlockSetRIP = true; } /* stack contains: Size of each member is 64-bit, 32-bit, or 16-bit depending on operating size RIP CS EFLAGS RSP SS */ void OpDispatchBuilder::IRETOp(OpcodeArgs) { // Operand Size override unsupported! if ((Op->Flags & X86Tables::DecodeFlags::FLAG_OPERAND_SIZE) != 0) { LogMan::Msg::EFmt("IRET only implemented for 64bit and 32bit sizes"); DecodeFailure = true; return; } const auto GPRSize = GetGPROpSize(); Ref SP = _RMWHandle(LoadGPRRegister(X86State::REG_RSP)); // RIP (64/32/16 bits) auto NewRIP = Pop(GPRSize, SP); // CS (lower 16 used) auto NewSegmentCS = Pop(GPRSize, SP); _StoreContextGPR(OpSize::i16Bit, NewSegmentCS, offsetof(FEXCore::Core::CPUState, cs_idx)); UpdatePrefixFromSegment(NewSegmentCS, FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX); // eflags (lower 16 used) SetPackedRFLAG(false, Pop(GPRSize, SP)); if (Is64BitMode) { // RSP and SS only happen in 64-bit mode or if this is a CPL mode jump! // FEX doesn't support a CPL mode switch, so don't need to worry about this on 32-bit StoreGPRRegister(X86State::REG_RSP, Pop(GPRSize, SP)); // ss auto NewSegmentSS = Pop(GPRSize, SP); _StoreContextGPR(OpSize::i16Bit, NewSegmentSS, offsetof(FEXCore::Core::CPUState, ss_idx)); UpdatePrefixFromSegment(NewSegmentSS, FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX); } else { // Store the stack in 32-bit mode StoreGPRRegister(X86State::REG_RSP, SP); } ExitFunction(NewRIP); BlockSetRIP = true; } void OpDispatchBuilder::CallbackReturnOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); // Store the new RIP _CallbackReturn(); auto NewRIP = _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, rip)); // This ExitFunction won't actually get hit but needs to exist ExitFunction(NewRIP); BlockSetRIP = true; } void OpDispatchBuilder::SecondaryALUOp(OpcodeArgs) { FEXCore::IR::IROps IROp, AtomicIROp; #define OPD(group, prefix, Reg) (((group - FEXCore::X86Tables::TYPE_GROUP_1) << 6) | (prefix) << 3 | (Reg)) switch (Op->OP) { case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 0): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 0): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 0): IROp = FEXCore::IR::IROps::OP_ADD; AtomicIROp = FEXCore::IR::IROps::OP_ATOMICFETCHADD; break; case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 1): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 1): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 1): IROp = FEXCore::IR::IROps::OP_OR; AtomicIROp = FEXCore::IR::IROps::OP_ATOMICFETCHOR; break; case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 4): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 4): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 4): IROp = FEXCore::IR::IROps::OP_ANDWITHFLAGS; AtomicIROp = FEXCore::IR::IROps::OP_ATOMICFETCHAND; break; case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 5): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 5): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 5): IROp = FEXCore::IR::IROps::OP_SUB; AtomicIROp = FEXCore::IR::IROps::OP_ATOMICFETCHSUB; break; case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 6): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 6): case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 6): IROp = FEXCore::IR::IROps::OP_XOR; AtomicIROp = FEXCore::IR::IROps::OP_ATOMICFETCHXOR; break; default: IROp = FEXCore::IR::IROps::OP_LAST; AtomicIROp = FEXCore::IR::IROps::OP_LAST; LogMan::Msg::EFmt("Unknown ALU Op: 0x{:x}", Op->OP); DecodeFailure = true; return; }; #undef OPD ALUOp(Op, IROp, AtomicIROp, 1, false); } void OpDispatchBuilder::ADCOp(OpcodeArgs, uint32_t SrcIndex, bool DestRAX) { // Calculate flags early. CalculateDeferredFlags(); Ref Src = LoadSourceGPR(Op, Op->Src[SrcIndex], Op->Flags, {.AllowUpperGarbage = true}); const auto Size = OpSizeFromDst(Op); const auto OpSize = std::max(OpSize::i32Bit, Size); Ref Before {}; if (DestIsLockedMem(Op)) { auto ALUOp = IncrementByCarry(OpSize, Src); HandledLock = true; Ref DestMem = MakeSegmentAddress(Op, Op->Dest); Before = _AtomicFetchAdd(Size, ALUOp, DestMem); } else if (DestRAX) { Before = LoadGPRRegister(X86State::REG_RAX, OpSizeFromSrc(Op), 0, true); } else { Before = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); } Ref Result; if (!DestIsLockedMem(Op) && Op->Src[SrcIndex].IsLiteral() && Op->Src[SrcIndex].Literal() == 0 && Size >= OpSize::i32Bit) { HandleNZCV_RMW(); RectifyCarryInvert(true); Result = _AdcZeroWithFlags(OpSize, Before); SetRFLAG(Before); CalculatePF(Result); CFInverted = false; } else { Result = CalculateFlags_ADC(Size, Before, Src); } if (DestRAX) { StoreGPRResultWithZExtSemantics(X86State::REG_RAX, Result, OpSizeFromDst(Op)); } else if (!DestIsLockedMem(Op)) { StoreResultGPR(Op, Result); } } void OpDispatchBuilder::SBBOp(OpcodeArgs, uint32_t SrcIndex, bool DestRAX) { // Calculate flags early. CalculateDeferredFlags(); Ref Src = LoadSourceGPR(Op, Op->Src[SrcIndex], Op->Flags, {.AllowUpperGarbage = true}); const auto Size = OpSizeFromDst(Op); const auto OpSize = std::max(OpSize::i32Bit, Size); Ref Result {}; Ref Before {}; if (DestIsLockedMem(Op)) { HandledLock = true; Ref DestMem = MakeSegmentAddress(Op, Op->Dest); auto SrcPlusCF = IncrementByCarry(OpSize, Src); Before = _AtomicFetchSub(Size, SrcPlusCF, DestMem); } else if (DestRAX) { Before = LoadGPRRegister(X86State::REG_RAX, OpSizeFromSrc(Op), 0, true); } else { Before = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); } Result = CalculateFlags_SBB(Size, Before, Src); if (DestRAX) { StoreGPRResultWithZExtSemantics(X86State::REG_RAX, Result, OpSizeFromDst(Op)); } else if (!DestIsLockedMem(Op)) { StoreResultGPR(Op, Result); } } void OpDispatchBuilder::SALCOp(OpcodeArgs) { CalculateDeferredFlags(); auto Result = NZCVSelect(OpSize::i32Bit, CondClass::UGE /* CF = 1 */, _InlineConstant(0xffffffff), _InlineConstant(0)); // This inserts in to the low 8-bits. StoreGPRRegister(X86State::REG_RAX, Result, OpSizeFromDst(Op)); } void OpDispatchBuilder::PUSHOp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); Push(Size, LoadSourceGPR(Op, Op->Src[0], Op->Flags)); } void OpDispatchBuilder::PUSHREGOp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); Push(Size, LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true})); } void OpDispatchBuilder::PUSHAOp(OpcodeArgs) { // 32bit only const auto Size = OpSizeFromSrc(Op); Ref OldSP = _Copy(LoadGPRRegister(X86State::REG_RSP)); Push(Size, LoadGPRRegister(X86State::REG_RAX)); Push(Size, LoadGPRRegister(X86State::REG_RCX)); Push(Size, LoadGPRRegister(X86State::REG_RDX)); Push(Size, LoadGPRRegister(X86State::REG_RBX)); Push(Size, OldSP); Push(Size, LoadGPRRegister(X86State::REG_RBP)); Push(Size, LoadGPRRegister(X86State::REG_RSI)); Push(Size, LoadGPRRegister(X86State::REG_RDI)); } void OpDispatchBuilder::PUSHSegmentOp(OpcodeArgs, uint32_t SegmentReg) { const auto SrcSize = OpSizeFromSrc(Op); const auto DstSize = OpSizeFromDst(Op); Ref Src {}; if (!Is64BitMode) { switch (SegmentReg) { case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX: { Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, es_idx)); break; } case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX: { Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, cs_idx)); break; } case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX: { Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, ss_idx)); break; } case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX: { Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, ds_idx)); break; } case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX: { Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, fs_idx)); break; } case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX: { Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, gs_idx)); break; } default: FEX_UNREACHABLE; } } else { switch (SegmentReg) { case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX: Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, es_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX: Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, cs_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX: Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, ss_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX: Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, ds_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX: Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, fs_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX: Src = _LoadContextGPR(SrcSize, offsetof(FEXCore::Core::CPUState, gs_cached)); break; default: FEX_UNREACHABLE; } } // Store our value to the new stack location // AMD hardware zexts segment selector to 32bit // Intel hardware inserts segment selector Push(DstSize, Src); } void OpDispatchBuilder::POPOp(OpcodeArgs) { Ref Value = Pop(OpSizeFromSrc(Op)); StoreResultGPR(Op, Value); } void OpDispatchBuilder::POPAOp(OpcodeArgs) { // 32bit only const auto Size = OpSizeFromSrc(Op); Ref SP = _RMWHandle(LoadGPRRegister(X86State::REG_RSP)); StoreGPRRegister(X86State::REG_RDI, Pop(Size, SP), Size); StoreGPRRegister(X86State::REG_RSI, Pop(Size, SP), Size); StoreGPRRegister(X86State::REG_RBP, Pop(Size, SP), Size); // Skip loading RSP because it'll be correct at the end SP = _RMWHandle(Add(OpSize::i64Bit, SP, IR::OpSizeToSize(Size))); StoreGPRRegister(X86State::REG_RBX, Pop(Size, SP), Size); StoreGPRRegister(X86State::REG_RDX, Pop(Size, SP), Size); StoreGPRRegister(X86State::REG_RCX, Pop(Size, SP), Size); StoreGPRRegister(X86State::REG_RAX, Pop(Size, SP), Size); // Store the new stack pointer StoreGPRRegister(X86State::REG_RSP, SP); } void OpDispatchBuilder::POPSegmentOp(OpcodeArgs, uint32_t SegmentReg) { const auto SrcSize = OpSizeFromSrc(Op); const auto DstSize = OpSizeFromDst(Op); auto NewSegment = Pop(SrcSize); switch (SegmentReg) { case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX: _StoreContextGPR(DstSize, NewSegment, offsetof(FEXCore::Core::CPUState, es_idx)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX: _StoreContextGPR(DstSize, NewSegment, offsetof(FEXCore::Core::CPUState, cs_idx)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX: // Unset the 'active' bit in the packed TF, skipping the single step exception after this instruction SetRFLAG(_And(OpSize::i32Bit, GetRFLAG(FEXCore::X86State::RFLAG_TF_RAW_LOC), Constant(1))); _StoreContextGPR(DstSize, NewSegment, offsetof(FEXCore::Core::CPUState, ss_idx)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX: _StoreContextGPR(DstSize, NewSegment, offsetof(FEXCore::Core::CPUState, ds_idx)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX: _StoreContextGPR(DstSize, NewSegment, offsetof(FEXCore::Core::CPUState, fs_idx)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX: _StoreContextGPR(DstSize, NewSegment, offsetof(FEXCore::Core::CPUState, gs_idx)); break; default: break; // Do nothing } UpdatePrefixFromSegment(NewSegment, SegmentReg); } void OpDispatchBuilder::LEAVEOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); const auto OperandSize = (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_OPERAND_SIZE) ? OpSize::i16Bit : GPRSize; // First we move RBP in to RSP and then behave effectively like a pop auto SP = _RMWHandle(LoadGPRRegister(X86State::REG_RBP)); auto NewGPR = Pop(OperandSize, SP); // Store the new stack pointer StoreGPRRegister(X86State::REG_RSP, SP, GPRSize); // Store what we loaded to RBP StoreGPRRegister(X86State::REG_RBP, NewGPR, OperandSize); } void OpDispatchBuilder::CALLOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); BlockSetRIP = true; // Call instruction only uses up to 32-bit signed displacement const int64_t TargetOffset = Op->Src[0].Literal(); const auto ConstantPC = GetRelocatedPC(Op); // Push the return address. Push(GPRSize, ConstantPC); if (TargetOffset != 0) { // Store the RIP const uint64_t NextRIP = Op->PC + Op->InstSize; ExitRelocatedPC(Op, TargetOffset, BranchHint::Call, ConstantPC, [&]() { auto CallReturnJumpTarget = JumpTargets.find(NextRIP); if (CallReturnJumpTarget != JumpTargets.end() && CallReturnJumpTarget->second.IsEntryPoint) { return CallReturnJumpTarget->second.BlockEntry; } return InvalidNode; }()); } else { NeedsBlockEnd = true; } } void OpDispatchBuilder::CALLAbsoluteOp(OpcodeArgs) { BlockSetRIP = true; const auto Size = OpSizeFromSrc(Op); Ref JMPPCOffset = LoadSourceGPR(Op, Op->Src[0], Op->Flags); // Push the return address. auto ConstantPC = GetRelocatedPC(Op); Push(Size, ConstantPC); // Store the RIP const uint64_t NextRIP = Op->PC + Op->InstSize; ExitFunction(JMPPCOffset, BranchHint::Call, ConstantPC, [&]() { auto CallReturnJumpTarget = JumpTargets.find(NextRIP); if (CallReturnJumpTarget != JumpTargets.end() && CallReturnJumpTarget->second.IsEntryPoint) { return CallReturnJumpTarget->second.BlockEntry; } return InvalidNode; }()); } std::optional OpDispatchBuilder::DecodeNZCVCondition(uint8_t OP) { switch (OP) { case 0x0: { // JO - Jump if OF == 1 return CondClass::FU; } case 0x1: { // JNO - Jump if OF == 0 return CondClass::FNU; } case 0x2: { // JC - Jump if CF == 1 return CFInverted ? CondClass::ULT : CondClass::UGE; } case 0x3: { // JNC - Jump if CF == 0 return CFInverted ? CondClass::UGE : CondClass::ULT; } case 0x4: { // JE - Jump if ZF == 1 return CondClass::EQ; } case 0x5: { // JNE - Jump if ZF == 0 return CondClass::NEQ; } case 0x6: { // JNA - Jump if CF == 1 || ZF == 1 // With CF, we want (C == 0 || Z == 1). By De Morgan's, that's // equivalent to !(C == 1 && Z == 0). That's .ls RectifyCarryInvert(true); return CondClass::ULE; } case 0x7: { // JA - Jump if CF == 0 && ZF == 0 // With CF inverted, we want (C == 1 && Z == 0). That's .hi RectifyCarryInvert(true); return CondClass::UGT; } case 0x8: { // JS - Jump if SF == 1 return CondClass::MI; } case 0x9: { // JNS - Jump if SF == 0 return CondClass::PL; } case 0xC: { // SF <> OF return CondClass::SLT; } case 0xD: { // SF = OF return CondClass::SGE; } case 0xE: { // ZF = 1 || SF <> OF return CondClass::SLE; } case 0xF: { // ZF = 0 && SF = OF return CondClass::SGT; } default: // Other conditions do not map directly, caller gets to deal with it. return std::nullopt; } } static bool ParityJumpIsJP(uint8_t OP) { LOGMAN_THROW_A_FMT(OP == 0xA || OP == 0xB, "JP or JNP"); return OP == 0xA; } Ref OpDispatchBuilder::SelectCC0All1(uint8_t OP) { if (auto Cond = DecodeNZCVCondition(OP); Cond) { // Use raw select since DecodeNZCVCondition handles the carry invert return _NZCVSelect(OpSize::i64Bit, *Cond, _InlineConstant(~0ULL), _InlineConstant(0)); } else { // Raw value contains inverted PF in bottom bit return _Sbfe(OpSize::i64Bit, 1, 0, LoadPFRaw(false, ParityJumpIsJP(OP))); } } void OpDispatchBuilder::SETccOp(OpcodeArgs) { CalculateDeferredFlags(); Ref SrcCond; if (auto Cond = DecodeNZCVCondition(Op->OP & 0xf); Cond) { // Use raw select since DecodeNZCVCondition handles the carry invert SrcCond = _NZCVSelect01(*Cond); } else { SrcCond = LoadPFRaw(true, ParityJumpIsJP(Op->OP & 0xf)); } StoreResultGPR(Op, SrcCond); } void OpDispatchBuilder::CMOVOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); const auto OP = Op->OP & 0xF; const auto ResultSize = std::max(OpSize::i32Bit, OpSizeFromSrc(Op)); CalculateDeferredFlags(); // Destination is always a GPR. Ref Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, GPRSize, Op->Flags); Ref Src {}, SrcCond {}; if (Op->Src[0].IsGPR()) { Src = LoadSourceGPR_WithOpSize(Op, Op->Src[0], GPRSize, Op->Flags); } else { Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags); } if (auto Cond = DecodeNZCVCondition(OP); Cond) { // Use raw select since DecodeNZCVCondition handles the carry invert SrcCond = _NZCVSelect(ResultSize, *Cond, Src, Dest); } else { // Raw value contains inverted PF in bottom bit Ref Cmp = LoadPFRaw(false, ParityJumpIsJP(OP)); SaveNZCV(); // Because we're only clobbering NZCV internally, we ignore all carry flag // shenanigans and just use the raw test and raw select. _TestNZ(OpSize::i32Bit, Cmp, _InlineConstant(1)); SrcCond = _NZCVSelect(ResultSize, CondClass::NEQ, Src, Dest); } StoreResultGPR(Op, SrcCond); } void OpDispatchBuilder::CondJUMPOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); BlockSetRIP = true; // Jump instruction only uses up to 32-bit signed displacement int64_t TargetOffset = Op->Src[0].Literal(); uint64_t InstRIP = Op->PC + Op->InstSize; uint64_t Target = InstRIP + TargetOffset; if (GetGPROpSize() == OpSize::i32Bit) { // If the GPRSize is 4 then we need to be careful about PC wrapping if (TargetOffset < 0 && -TargetOffset > InstRIP) { // Invert the signed value if we are underflowing TargetOffset = 0x1'0000'0000ULL + TargetOffset; } else if (TargetOffset >= 0 && Target >= 0x1'0000'0000ULL) { // We are overflowing, wrap around TargetOffset = TargetOffset - 0x1'0000'0000ULL; } Target &= 0xFFFFFFFFU; } FlushRegisterCache(); auto TrueBlock = JumpTargets.find(Target); auto FalseBlock = JumpTargets.find(Op->PC + Op->InstSize); auto CurrentBlock = GetCurrentBlock(); { IRPair CondJump_; auto OP = Op->OP & 0xF; auto Cond = DecodeNZCVCondition(OP); if (Cond) { CondJump_ = CondJumpNZCV(*Cond); } else { LOGMAN_THROW_A_FMT(OP == 0xA || OP == 0xB, "only PF left"); CondJump_ = CondJumpBit(LoadPFRaw(false, false), 0, OP == 0xB); } // Taking branch block if (TrueBlock != JumpTargets.end()) { SetTrueJumpTarget(CondJump_, TrueBlock->second.BlockEntry); } else { // Make sure to start a new block after ending this one auto JumpTarget = CreateNewCodeBlockAtEnd(); SetTrueJumpTarget(CondJump_, JumpTarget); SetCurrentCodeBlock(JumpTarget); StartNewBlock(); // Store the new RIP ExitRelocatedPC(Op, TargetOffset); } // Failure to take branch if (FalseBlock != JumpTargets.end()) { SetFalseJumpTarget(CondJump_, FalseBlock->second.BlockEntry); } else { // Make sure to start a new block after ending this one // Place it after this block for fallthrough optimization auto JumpTarget = CreateNewCodeBlockAfter(CurrentBlock); SetFalseJumpTarget(CondJump_, JumpTarget); SetCurrentCodeBlock(JumpTarget); StartNewBlock(); // Leave block & store the new RIP ExitRelocatedPC(Op); } } } void OpDispatchBuilder::CondJUMPRCXOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); BlockSetRIP = true; auto JcxGPRSize = GetGPROpSize(); JcxGPRSize = (Op->Flags & X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) ? (JcxGPRSize >> 1) : JcxGPRSize; uint64_t Target = Op->PC + Op->InstSize + Op->Src[0].Literal(); Ref CondReg = LoadGPRRegister(X86State::REG_RCX, JcxGPRSize); auto TrueBlock = JumpTargets.find(Target); auto FalseBlock = JumpTargets.find(Op->PC + Op->InstSize); auto CurrentBlock = GetCurrentBlock(); { auto CondJump_ = CondJump(CondReg, CondClass::EQ); // Taking branch block if (TrueBlock != JumpTargets.end()) { SetTrueJumpTarget(CondJump_, TrueBlock->second.BlockEntry); } else { // Make sure to start a new block after ending this one auto JumpTarget = CreateNewCodeBlockAtEnd(); SetTrueJumpTarget(CondJump_, JumpTarget); SetCurrentCodeBlock(JumpTarget); StartNewBlock(); // Store the new RIP ExitRelocatedPC(Op, Op->Src[0].Literal()); } // Failure to take branch if (FalseBlock != JumpTargets.end()) { SetFalseJumpTarget(CondJump_, FalseBlock->second.BlockEntry); } else { // Make sure to start a new block after ending this one // Place it after the current block for fallthrough behavior auto JumpTarget = CreateNewCodeBlockAfter(CurrentBlock); SetFalseJumpTarget(CondJump_, JumpTarget); SetCurrentCodeBlock(JumpTarget); StartNewBlock(); // Leave block & store the new RIP ExitRelocatedPC(Op); } } } void OpDispatchBuilder::LoopOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); bool CheckZF = Op->OP != 0xE2; bool ZFTrue = Op->OP == 0xE1; BlockSetRIP = true; auto SrcSize = (Op->Flags & X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) ? OpSize::i32Bit : OpSize::i64Bit; auto OpSize = SrcSize == OpSize::i64Bit ? OpSize::i64Bit : OpSize::i32Bit; if (!Is64BitMode) { // RCX size is 32-bit or 16-bit when executing in 32-bit mode. SrcSize = IR::SizeToOpSize(IR::OpSizeToSize(SrcSize) >> 1); OpSize = OpSize::i32Bit; } uint64_t Target = Op->PC + Op->InstSize + Op->Src[0].Literal(); Ref CondReg = LoadGPRRegister(X86State::REG_RCX, SrcSize); CondReg = Sub(OpSize, CondReg, 1); StoreGPRRegister(X86State::REG_RCX, CondReg, SrcSize); // If LOOPE then jumps to target if RCX != 0 && ZF == 1 // If LOOPNE then jumps to target if RCX != 0 && ZF == 0 // // To handle efficiently, smash RCX to zero if ZF is wrong (1 csel). if (CheckZF) { const auto cond = ZFTrue ? CondClass::EQ : CondClass::NEQ; CondReg = NZCVSelect(OpSize, cond, CondReg, _InlineConstant(0)); } CalculateDeferredFlags(); auto TrueBlock = JumpTargets.find(Target); auto FalseBlock = JumpTargets.find(Op->PC + Op->InstSize); { auto CondJump_ = CondJump(CondReg); // Taking branch block if (TrueBlock != JumpTargets.end()) { SetTrueJumpTarget(CondJump_, TrueBlock->second.BlockEntry); } else { // Make sure to start a new block after ending this one auto JumpTarget = CreateNewCodeBlockAtEnd(); SetTrueJumpTarget(CondJump_, JumpTarget); SetCurrentCodeBlock(JumpTarget); StartNewBlock(); // Store the new RIP ExitRelocatedPC(Op, Op->Src[0].Literal()); } // Failure to take branch if (FalseBlock != JumpTargets.end()) { SetFalseJumpTarget(CondJump_, FalseBlock->second.BlockEntry); } else { // Make sure to start a new block after ending this one // Place after this block for fallthrough behavior auto JumpTarget = CreateNewCodeBlockAfter(GetCurrentBlock()); SetFalseJumpTarget(CondJump_, JumpTarget); SetCurrentCodeBlock(JumpTarget); StartNewBlock(); // Leave block & store the new RIP ExitRelocatedPC(Op); } } } void OpDispatchBuilder::JUMPOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); BlockSetRIP = true; // Jump instruction only uses up to 32-bit signed displacement int64_t TargetOffset = Op->Src[0].Literal(); uint64_t InstRIP = Op->PC + Op->InstSize; uint64_t TargetRIP = InstRIP + TargetOffset; if (GetGPROpSize() == OpSize::i32Bit) { // If the GPRSize is 4 then we need to be careful about PC wrapping if (TargetOffset < 0 && -TargetOffset > InstRIP) { // Invert the signed value if we are underflowing TargetOffset = 0x1'0000'0000ULL + TargetOffset; } else if (TargetOffset >= 0 && TargetRIP >= 0x1'0000'0000ULL) { // We are overflowing, wrap around TargetOffset = TargetOffset - 0x1'0000'0000ULL; } TargetRIP &= 0xFFFFFFFFU; } CalculateDeferredFlags(); // This is just an unconditional relative literal jump if (Multiblock) { auto JumpBlock = JumpTargets.find(TargetRIP); if (JumpBlock != JumpTargets.end()) { Jump(GetNewJumpBlock(TargetRIP)); } else { // If the block isn't a jump target then we need to create an exit block auto Jump_ = Jump(); // Place after this block for fallthrough behavior auto JumpTarget = CreateNewCodeBlockAfter(GetCurrentBlock()); SetJumpTarget(Jump_, JumpTarget); SetCurrentCodeBlock(JumpTarget); StartNewBlock(); ExitRelocatedPC(Op, TargetOffset); } } else { ExitRelocatedPC(Op, TargetOffset); } } void OpDispatchBuilder::JUMPAbsoluteOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); BlockSetRIP = true; // This is just an unconditional jump // This uses ModRM to determine its location // No way to use this effectively in multiblock auto RIPOffset = LoadSourceGPR(Op, Op->Src[0], Op->Flags); // Store the new RIP ExitFunction(RIPOffset); } void OpDispatchBuilder::JUMPFARIndirectOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); BlockSetRIP = true; // This is just an unconditional jump // This uses ModRM to determine its location // No way to use this effectively in multiblock Ref Src = MakeSegmentAddress(Op, Op->Dest); AddressMode SrcCS = {.Base = Src, .Offset = 4, .AddrSize = OpSize::i64Bit}; auto RIPOffset = _LoadMemGPRAutoTSO(OpSize::i32Bit, Src, OpSize::i8Bit); auto NewSegmentCS = _LoadMemGPRAutoTSO(OpSize::i16Bit, SrcCS, OpSize::i8Bit); // Set up the new CSSegment. _StoreContextGPR(OpSize::i16Bit, NewSegmentCS, offsetof(FEXCore::Core::CPUState, cs_idx)); UpdatePrefixFromSegment(NewSegmentCS, FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX); // Store the new RIP ExitFunction(RIPOffset); } void OpDispatchBuilder::CALLFARIndirectOp(OpcodeArgs) { const auto SrcSize = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REX_WIDENING ? OpSize::i64Bit : OpSize::i32Bit; // Calculate flags early. CalculateDeferredFlags(); BlockSetRIP = true; Ref Src = MakeSegmentAddress(Op, Op->Dest); AddressMode SrcCS = {.Base = Src, .Offset = 4, .AddrSize = OpSize::i64Bit}; auto RIPOffset = _LoadMemGPRAutoTSO(OpSize::i32Bit, Src, OpSize::i8Bit); auto NewSegmentCS = _LoadMemGPRAutoTSO(OpSize::i16Bit, SrcCS, OpSize::i8Bit); auto CurrentCS = _LoadContextGPR(OpSize::i16Bit, offsetof(FEXCore::Core::CPUState, cs_idx)); auto NewRIP = GetRelocatedPC(Op); // Push the current CS Push(SrcSize, CurrentCS); // Push the return address. Push(SrcSize, NewRIP); // Set up the new CSSegment. _StoreContextGPR(OpSize::i16Bit, NewSegmentCS, offsetof(FEXCore::Core::CPUState, cs_idx)); UpdatePrefixFromSegment(NewSegmentCS, FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX); // Store the new RIP ExitFunction(RIPOffset); } void OpDispatchBuilder::RETFARIndirectOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); const auto SrcSize = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REX_WIDENING ? OpSize::i64Bit : OpSize::i32Bit; Ref SP = _RMWHandle(LoadGPRRegister(X86State::REG_RSP)); Ref NewRIP = Pop(SrcSize, SP); Ref NewSegmentCS = Pop(SrcSize, SP); // Optional SP offset. if (Op->Src[0].IsLiteral()) { SP = Add(GPRSize, SP, Op->Src[0].Literal()); } // Store the new stack pointer StoreGPRRegister(X86State::REG_RSP, SP); _StoreContextGPR(OpSize::i16Bit, NewSegmentCS, offsetof(FEXCore::Core::CPUState, cs_idx)); UpdatePrefixFromSegment(NewSegmentCS, FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX); // Store the new RIP ExitFunction(NewRIP); BlockSetRIP = true; } void OpDispatchBuilder::TESTOp(OpcodeArgs, uint32_t SrcIndex, bool DestRAX) { // TEST is an instruction that does an AND between the sources // Result isn't stored in result, only writes to flags Ref Src = LoadSourceGPR(Op, Op->Src[SrcIndex], Op->Flags, {.AllowUpperGarbage = true}); Ref Dest {}; if (DestRAX) { Dest = LoadGPRRegister(X86State::REG_RAX, OpSizeFromSrc(Op), 0, true); } else { Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); } const auto Size = OpSizeFromDst(Op); LOGMAN_THROW_A_FMT(Size >= IR::OpSize::i8Bit && Size <= IR::OpSize::i64Bit, "Invalid size"); uint64_t Const; bool AlwaysNonnegative = false; if (IsValueConstant(WrapNode(Src), &Const)) { // Optimize out masking constants if (Const == (Size == OpSize::i64Bit ? ~0ULL : ((1ull << IR::OpSizeAsBits(Size)) - 1))) { Src = Dest; } // Optimize test with non-sign bits AlwaysNonnegative = (Const & (1ull << (IR::OpSizeAsBits(Size) - 1))) == 0; } if (Dest == Src) { // Optimize out the AND. SetNZP_ZeroCV(Size, Src); } else if (Size < OpSize::i32Bit && AlwaysNonnegative) { // If we know the result is always nonnegative, we can use a 32-bit test. auto Res = _And(OpSize::i32Bit, Dest, Src); CalculatePF(Res); SetNZ_ZeroCV(OpSize::i32Bit, Res); } else { HandleNZ00Write(); CalculatePF(_AndWithFlags(Size, Dest, Src)); } InvalidateAF(); } void OpDispatchBuilder::ARPLOp(OpcodeArgs) { // ARPL r/m16, r16 // If the RPL field in the destination selector is less privileged than the // RPL field in the source selector, then adjust destination RPL to match // source RPL and set ZF=1. Otherwise ZF=0 and destination is unchanged. // // Only ZF is modified by ARPL. constexpr auto Size = OpSize::i16Bit; Ref Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, Size, Op->Flags, {.AllowUpperGarbage = true}); Ref Src = LoadSourceGPR_WithOpSize(Op, Op->Src[0], Size, Op->Flags, {.AllowUpperGarbage = true}); // RPL is the low two bits of the selector. Ref DestRPL = _Bfe(OpSize::i32Bit, 2, 0, Dest); Ref SrcRPL = _Bfe(OpSize::i32Bit, 2, 0, Src); // NeedUpdate is 1 when DestRPL < SrcRPL, else 0. Ref NeedUpdate = _Select(OpSize::i32Bit, OpSize::i32Bit, CondClass::ULT, DestRPL, SrcRPL, Constant(1), Constant(0)); SetRFLAG(NeedUpdate); // Compute adjusted destination selector: (Dest & ~3) | SrcRPL. auto NewDest = _Bfxil(OpSize::i32Bit, 2, 0, Dest, SrcRPL); // Conditionally select updated selector based on NeedUpdate. Ref FinalDest = _Select(OpSize::i32Bit, OpSize::i32Bit, CondClass::NEQ, NeedUpdate, Constant(0), NewDest, Dest); StoreResultGPR_WithOpSize(Op, Op->Dest, FinalDest, Size); } void OpDispatchBuilder::MOVSXDOp(OpcodeArgs) { // This instruction is a bit special // if SrcSize == 2 // Then lower 16 bits of destination is written without changing the upper 48 bits // else /* Size == 4 */ // if REX_WIDENING: // Sext(32, Src) // else // Zext(32, Src) // auto Size = std::min(OpSize::i32Bit, OpSizeFromSrc(Op)); bool Sext = (Size != OpSize::i16Bit) && Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REX_WIDENING; Ref Src = LoadSourceGPR_WithOpSize(Op, Op->Src[0], Size, Op->Flags, {.AllowUpperGarbage = Sext}); if (Size == OpSize::i16Bit) { // This'll make sure to insert in to the lower 16bits without modifying upper bits StoreResultGPR_WithOpSize(Op, Op->Dest, Src, Size); } else if (Sext) { // With REX.W then Sext Src = _Sbfe(OpSize::i64Bit, IR::OpSizeAsBits(Size), 0, Src); StoreResultGPR(Op, Src); } else { // Without REX.W then Zext (store result implicitly zero extends) StoreResultGPR(Op, Src); } } void OpDispatchBuilder::MOVSXOp(OpcodeArgs) { // Load garbage in upper bits, since we're sign extending anyway const auto Size = OpSizeFromSrc(Op); Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); // Sign-extend to DstSize and zero-extend to the register size, using a fast // path for 32-bit dests where the native 32-bit Sbfe zero extends the top. const auto DstSize = OpSizeFromDst(Op); Src = _Sbfe(DstSize == OpSize::i64Bit ? OpSize::i64Bit : OpSize::i32Bit, IR::OpSizeAsBits(Size), 0, Src); StoreResultGPR(Op, Op->Dest, Src); } void OpDispatchBuilder::MOVZXOp(OpcodeArgs) { Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags); // Store result implicitly zero extends StoreResultGPR(Op, Src); } void OpDispatchBuilder::CMPOp(OpcodeArgs, uint32_t SrcIndex, bool DestRAX) { // CMP is an instruction that does a SUB between the sources // Result isn't stored in result, only writes to flags Ref Src = LoadSourceGPR(Op, Op->Src[SrcIndex], Op->Flags, {.AllowUpperGarbage = true}); Ref Dest {}; if (DestRAX) { Dest = LoadGPRRegister(X86State::REG_RAX, OpSizeFromSrc(Op), 0, true); } else { Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); } CalculateFlags_SUB(OpSizeFromSrc(Op), Dest, Src); } void OpDispatchBuilder::CQOOp(OpcodeArgs) { auto Size = OpSizeFromSrc(Op); Ref Src = LoadGPRRegister(X86State::REG_RAX, Size, 0, true); Ref Upper = _Sbfe(std::max(OpSize::i32Bit, Size), 1, GetSrcBitSize(Op) - 1, Src); StoreGPRResultWithZExtSemantics(X86State::REG_RDX, Upper, Size); } std::optional OpDispatchBuilder::XCHGOpImpl(OpcodeArgs, Ref Src) { if (DestIsMem(Op)) { HandledLock = (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK) != 0; Ref Dest = MakeSegmentAddress(Op, Op->Dest); if (IsMonoBackpatcherBlock) { _MonoBackpatcherWrite(OpSizeFromSrc(Op), Src, Dest); } else { return _AtomicSwap(OpSizeFromSrc(Op), Src, Dest); } return std::nullopt; } else { // AllowUpperGarbage: OK to allow as it will be overwritten by StoreResult. Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); // Swap the contents // Order matters here since we don't want to swap context contents for one that effects the other StoreResultGPR(Op, Op->Dest, Src); return Dest; } } void OpDispatchBuilder::XCHGOp(OpcodeArgs) { // AllowUpperGarbage: OK to allow as it will be overwritten by StoreResult. Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Res = XCHGOpImpl(Op, Src); if (Res) { StoreResultGPR(Op, Op->Src[0], *Res); } } void OpDispatchBuilder::XCHGRAXOp(OpcodeArgs) { // Load both the source and the destination if (Op->Dest.IsGPR() && Op->Dest.Data.GPR.GPR == FEXCore::X86State::REG_RAX) { // This is one heck of a sucky special case // If we are the 0x90 XCHG opcode (Meaning source is GPR RAX) // and destination register is ALSO RAX // and in this very specific case we are 32bit or above // Then this is a no-op // This is because 0x90 without a prefix is technically `xchg eax, eax` // But this would result in a zext on 64bit, which would ruin the no-op nature of the instruction // So x86-64 spec mandates this special case that even though it is a 32bit instruction and // is supposed to zext the result, it is a true no-op // // x86 spec text here: // // XCHG (E)AX, (E)AX (encoded instruction byte is 90H) is an alias for // NOP regardless of data size prefixes, including REX.W. // // Note that also includes 16-bit so we don't gate this on size. The // sequence (66 90) is a valid two-byte nop that we also ignore. if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX) { // If this instruction has a REP prefix then this is architecturally // defined to be a `PAUSE` instruction. On older processors this ends up // being a true `REP NOP` which is why they stuck this here. _Yield(); } return; } // AllowUpperGarbage: OK to allow as it will be overwritten by StoreResult. Ref Src = LoadGPRRegister(X86State::REG_RAX, OpSize::iInvalid, 0, true); auto Res = XCHGOpImpl(Op, Src); if (Res) { StoreGPRResultWithZExtSemantics(X86State::REG_RAX, *Res, OpSizeFromDst(Op)); } } void OpDispatchBuilder::CDQOp(OpcodeArgs) { const auto DstSize = OpSizeFromDst(Op); const auto SrcSize = DstSize / 2; Ref Src = LoadGPRRegister(X86State::REG_RAX, SrcSize, 0, true); Src = _Sbfe(DstSize <= OpSize::i32Bit ? OpSize::i32Bit : OpSize::i64Bit, IR::OpSizeAsBits(SrcSize), 0, Src); // This inserts in to the low 16-bits. StoreGPRResultWithZExtSemantics(X86State::REG_RAX, Src, DstSize); } void OpDispatchBuilder::SAHFOp(OpcodeArgs) { // Extract AH Ref Src = LoadGPRRegister(X86State::REG_RAX, OpSize::i8Bit, 8); // Clear bits that aren't supposed to be set Src = _Andn(OpSize::i64Bit, Src, Constant(0b101000)); // Set the bit that is always set here Src = _Or(OpSize::i64Bit, Src, _InlineConstant(0b10)); // Store the lower 8 bits in to RFLAGS SetPackedRFLAG(true, Src); } void OpDispatchBuilder::LAHFOp(OpcodeArgs) { // Load the lower 8 bits of the Rflags register auto RFLAG = GetPackedRFLAG(0xFF); // Store the lower 8 bits of the rflags register in to AH StoreGPRRegister(X86State::REG_RAX, RFLAG, OpSize::i8Bit, 8); } void OpDispatchBuilder::FLAGControlOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); switch (Op->OP) { case 0xF5: // CMC CarryInvert(); break; case 0xF8: // CLC SetCFInverted(Constant(1)); break; case 0xF9: // STC SetCFInverted(Constant(0)); break; case 0xFC: // CLD // Transformed StoreDF(Constant(1)); break; case 0xFD: // STD StoreDF(Constant(-1)); break; } } void OpDispatchBuilder::MOVSegOp(OpcodeArgs, bool ToSeg) { // In x86-64 mode the accesses to the segment registers end up being constant zero moves // Aside from FS/GS // In x86-64 mode the accesses to segment registers can actually still touch the segments // These write to the selector portion of the register // // FS and GS are specially handled here though // AMD documentation is /wrong/ in this regard // AMD documentation claims that the MOV to SReg and POP SReg registers will load a 32bit // value in to the HIDDEN portions of the FS and GS registers /OR/ ignored if a null selector is // selected for the registers // This statement is actually untrue, the instructions will /actually/ load 16bits in to the selector portion of the register! // Tested on a Zen+ CPU, the selector is the portion that is modified! // We don't currently support FS/GS selector modifying, so this needs to be asserted out // The loads here also load the selector, NOT the base if (ToSeg) { Ref Src = LoadSourceGPR_WithOpSize(Op, Op->Src[0], OpSize::i16Bit, Op->Flags); switch (Op->Dest.Data.GPR.GPR) { case FEXCore::X86State::REG_RAX: // ES case FEXCore::X86State::REG_R8: // ES _StoreContextGPR(OpSize::i16Bit, Src, offsetof(FEXCore::Core::CPUState, es_idx)); UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX); break; case FEXCore::X86State::REG_RBX: // DS case FEXCore::X86State::REG_R11: // DS _StoreContextGPR(OpSize::i16Bit, Src, offsetof(FEXCore::Core::CPUState, ds_idx)); UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX); break; case FEXCore::X86State::REG_RCX: // CS case FEXCore::X86State::REG_R9: // CS // CPL3 can't write to this Break(FEXCore::IR::BreakDefinition { .ErrorRegister = 0, .Signal = SIGILL, .TrapNumber = 0, .si_code = 0, }); break; case FEXCore::X86State::REG_RDX: // SS case FEXCore::X86State::REG_R10: // SS _StoreContextGPR(OpSize::i16Bit, Src, offsetof(FEXCore::Core::CPUState, ss_idx)); UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX); break; case FEXCore::X86State::REG_RBP: // GS case FEXCore::X86State::REG_R13: // GS if (!Is64BitMode) { _StoreContextGPR(OpSize::i16Bit, Src, offsetof(FEXCore::Core::CPUState, gs_idx)); UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX); } else { LogMan::Msg::EFmt("We don't support modifying GS selector in 64bit mode!"); DecodeFailure = true; } break; case FEXCore::X86State::REG_RSP: // FS case FEXCore::X86State::REG_R12: // FS if (!Is64BitMode) { _StoreContextGPR(OpSize::i16Bit, Src, offsetof(FEXCore::Core::CPUState, fs_idx)); UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX); } else { LogMan::Msg::EFmt("We don't support modifying FS selector in 64bit mode!"); DecodeFailure = true; } break; default: UnimplementedOp(Op); return; } } else { Ref Segment {}; switch (Op->Src[0].Data.GPR.GPR) { case FEXCore::X86State::REG_RAX: // ES case FEXCore::X86State::REG_R8: // ES Segment = _LoadContextGPR(OpSize::i16Bit, offsetof(FEXCore::Core::CPUState, es_idx)); break; case FEXCore::X86State::REG_RBX: // DS case FEXCore::X86State::REG_R11: // DS Segment = _LoadContextGPR(OpSize::i16Bit, offsetof(FEXCore::Core::CPUState, ds_idx)); break; case FEXCore::X86State::REG_RCX: // CS case FEXCore::X86State::REG_R9: // CS Segment = _LoadContextGPR(OpSize::i16Bit, offsetof(FEXCore::Core::CPUState, cs_idx)); break; case FEXCore::X86State::REG_RDX: // SS case FEXCore::X86State::REG_R10: // SS Segment = _LoadContextGPR(OpSize::i16Bit, offsetof(FEXCore::Core::CPUState, ss_idx)); break; case FEXCore::X86State::REG_RBP: // GS case FEXCore::X86State::REG_R13: // GS if (Is64BitMode) { Segment = Constant(0); } else { Segment = _LoadContextGPR(OpSize::i16Bit, offsetof(FEXCore::Core::CPUState, gs_idx)); } break; case FEXCore::X86State::REG_RSP: // FS case FEXCore::X86State::REG_R12: // FS if (Is64BitMode) { Segment = Constant(0); } else { Segment = _LoadContextGPR(OpSize::i16Bit, offsetof(FEXCore::Core::CPUState, fs_idx)); } break; default: UnimplementedOp(Op); return; } if (DestIsMem(Op)) { // If the destination is memory then we always store 16-bits only StoreResultGPR_WithOpSize(Op, Op->Dest, Segment, OpSize::i16Bit); } else { // If the destination is a GPR then we follow register storing rules StoreResultGPR(Op, Segment); } } } void OpDispatchBuilder::MOVOffsetOp(OpcodeArgs) { switch (Op->OP) { case 0xA0: case 0xA1: { // Source is memory(literal) // Dest is GPR auto Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.ForceLoad = true}); StoreGPRResultWithZExtSemantics(X86State::REG_RAX, Src, OpSizeFromDst(Op)); break; } case 0xA2: case 0xA3: { // Source is GPR // Dest is memory(literal) Ref Src = LoadGPRRegister(X86State::REG_RAX); // This one is a bit special since the destination is a literal // So the destination gets stored in Src[1] StoreResultGPR(Op, Op->Src[0], Src); break; } } } void OpDispatchBuilder::CPUIDOp(OpcodeArgs) { Ref Src = LoadGPRRegister(X86State::REG_RAX); Ref Leaf = LoadGPRRegister(X86State::REG_RCX); Ref RAX = _AllocateGPR(false); Ref RBX = _AllocateGPR(false); Ref RCX = _AllocateGPR(false); Ref RDX = _AllocateGPR(false); _CPUID(Src, Leaf, RAX, RBX, RCX, RDX); StoreGPRRegister(X86State::REG_RAX, RAX); StoreGPRRegister(X86State::REG_RBX, RBX); StoreGPRRegister(X86State::REG_RCX, RCX); StoreGPRRegister(X86State::REG_RDX, RDX); } uint32_t OpDispatchBuilder::GetConstantShift(X86Tables::DecodedOp Op, bool Is1Bit) { if (Is1Bit) { return 1; } else { // x86 masks the shift by 0x3F or 0x1F depending on size of op const auto Size = OpSizeFromSrc(Op); uint64_t Mask = Size == OpSize::i64Bit ? 0x3F : 0x1F; return Op->Src[1].Literal() & Mask; } } void OpDispatchBuilder::XGetBVOp(OpcodeArgs) { Ref Function = LoadGPRRegister(X86State::REG_RCX); auto RAX = _AllocateGPR(false); auto RDX = _AllocateGPR(false); _XGetBV(Function, RAX, RDX); StoreGPRRegister(X86State::REG_RAX, RAX); StoreGPRRegister(X86State::REG_RDX, RDX); } void OpDispatchBuilder::SHLOp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); auto Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); auto Src = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); Ref Result = _Lshl(Size == OpSize::i64Bit ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src); HandleShift(Op, Result, Dest, ShiftType::LSL, Src); } void OpDispatchBuilder::SHLImmediateOp(OpcodeArgs, bool SHL1Bit) { Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); uint64_t Shift = GetConstantShift(Op, SHL1Bit); const auto Size = GetSrcBitSize(Op); Ref Result = _Lshl(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Constant(Shift)); CalculateFlags_ShiftLeftImmediate(OpSizeFromSrc(Op), Result, Dest, Shift); CalculateDeferredFlags(); StoreResultGPR(Op, Result); } void OpDispatchBuilder::SHROp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); auto Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = Size >= OpSize::i32Bit}); auto Src = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); auto ALUOp = _Lshr(std::max(OpSize::i32Bit, Size), Dest, Src); HandleShift(Op, ALUOp, Dest, ShiftType::LSR, Src); } void OpDispatchBuilder::SHRImmediateOp(OpcodeArgs, bool SHR1Bit) { const auto Size = GetSrcBitSize(Op); auto Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = Size >= 32}); uint64_t Shift = GetConstantShift(Op, SHR1Bit); auto ALUOp = _Lshr(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Constant(Shift)); CalculateFlags_ShiftRightImmediate(OpSizeFromSrc(Op), ALUOp, Dest, Shift); CalculateDeferredFlags(); StoreResultGPR(Op, ALUOp); } void OpDispatchBuilder::SHLDOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); const auto Size = GetSrcBitSize(Op); // Allow garbage on the Src if it will be ignored by the Lshr below Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = Size >= 32}); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags); // Allow garbage on the shift, we're masking it anyway. Ref Shift = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); // x86 masks the shift by 0x3F or 0x1F depending on size of op. if (Size == 64) { Shift = _And(OpSize::i64Bit, Shift, _InlineConstant(0x3F)); } else { Shift = _And(OpSize::i64Bit, Shift, _InlineConstant(0x1F)); } // a64 masks the bottom bits, so if we're using a native 32/64-bit shift, we // can negate to do the subtract (it's congruent), which saves a constant. auto ShiftRight = Size >= 32 ? _Neg(OpSize::i64Bit, Shift) : Sub(OpSize::i64Bit, Constant(Size), Shift); auto Tmp1 = _Lshl(OpSize::i64Bit, Dest, Shift); auto Tmp2 = _Lshr(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Src, ShiftRight); Ref Res = _Or(OpSize::i64Bit, Tmp1, Tmp2); // If shift count was zero then output doesn't change // Needs to be checked for the 32bit operand case // where shift = 0 and the source register still gets Zext // // TODO: With a backwards pass ahead-of-time, we could stick this in the // if(shift) used for flags. // // TODO: This whole function wants to be wrapped in the if. Maybe b/w pass is // a good idea after all. Res = _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::EQ, Shift, Constant(0), Dest, Res); HandleShift(Op, Res, Dest, ShiftType::LSL, Shift); } void OpDispatchBuilder::SHLDImmediateOp(OpcodeArgs) { uint64_t Shift = GetConstantShift(Op, false); const auto Size = GetSrcBitSize(Op); Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = Size >= 32}); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = Size >= 32}); if (Shift != 0) { Ref Res {}; if (Size < 32) { Ref ShiftLeft = Constant(Shift); auto ShiftRight = Size - Shift; auto Tmp1 = _Lshl(OpSize::i64Bit, Dest, ShiftLeft); Ref Tmp2 = ShiftRight ? _Lshr(OpSize::i32Bit, Src, Constant(ShiftRight)) : Src; Res = _Or(OpSize::i64Bit, Tmp1, Tmp2); } else { // 32-bit and 64-bit SHLD behaves like an EXTR where the lower bits are filled from the source. Res = _Extr(OpSizeFromSrc(Op), Dest, Src, Size - Shift); } CalculateFlags_ShiftLeftImmediate(OpSizeFromSrc(Op), Res, Dest, Shift, true); CalculateDeferredFlags(); StoreResultGPR(Op, Res); } else if (Shift == 0 && Size == 32) { // Ensure Zext still occurs StoreResultGPR(Op, Dest); } } void OpDispatchBuilder::SHRDOp(OpcodeArgs) { // Calculate flags early. // This instruction conditionally generates flags so we need to insure sane state going in. CalculateDeferredFlags(); Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags); Ref Shift = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); const auto Size = GetDstBitSize(Op); // x86 masks the shift by 0x3F or 0x1F depending on size of op if (Size == 64) { Shift = _And(OpSize::i64Bit, Shift, _InlineConstant(0x3F)); } else { Shift = _And(OpSize::i64Bit, Shift, _InlineConstant(0x1F)); } auto ShiftLeft = Sub(OpSize::i64Bit, Constant(Size), Shift); auto Tmp1 = _Lshr(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Shift); auto Tmp2 = _Lshl(OpSize::i64Bit, Src, ShiftLeft); Ref Res = _Or(OpSize::i64Bit, Tmp1, Tmp2); // If shift count was zero then output doesn't change // Needs to be checked for the 32bit operand case // where shift = 0 and the source register still gets Zext Res = _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::EQ, Shift, Constant(0), Dest, Res); HandleShift(Op, Res, Dest, ShiftType::LSR, Shift); } void OpDispatchBuilder::SHRDImmediateOp(OpcodeArgs) { Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags); uint64_t Shift = GetConstantShift(Op, false); const auto Size = GetSrcBitSize(Op); if (Shift != 0) { Ref Res {}; if (Size < 32) { Ref ShiftRight = Constant(Shift); auto ShiftLeft = Constant(Size - Shift); auto Tmp1 = _Lshr(OpSize::i32Bit, Dest, ShiftRight); auto Tmp2 = _Lshl(OpSize::i64Bit, Src, ShiftLeft); Res = _Or(OpSize::i64Bit, Tmp1, Tmp2); } else { // 32-bit and 64-bit SHRD behaves like an EXTR where the upper bits are filled from the source. Res = _Extr(OpSizeFromSrc(Op), Src, Dest, Shift); } StoreResultGPR(Op, Res); CalculateFlags_ShiftRightDoubleImmediate(OpSizeFromSrc(Op), Res, Dest, Shift); } else if (Shift == 0 && Size == 32) { // Ensure Zext still occurs StoreResultGPR(Op, Dest); } } void OpDispatchBuilder::ASHROp(OpcodeArgs, bool Immediate, bool SHR1Bit) { const auto Size = OpSizeFromSrc(Op); const auto OpSize = std::max(OpSize::i32Bit, OpSizeFromDst(Op)); // If Size < 4, then we Sbfe the Dest so we can have garbage. // Otherwise, if Size = Opsize, then both are 4 or 8 and match the a64 // semantics directly, so again we can have garbage. The only case where we // need zero-extension here is when the sizes mismatch. auto Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = (OpSize == Size) || (Size < OpSize::i32Bit)}); if (Size < OpSize::i32Bit) { Dest = _Sbfe(OpSize::i64Bit, IR::OpSizeAsBits(Size), 0, Dest); } if (Immediate) { uint64_t Shift = GetConstantShift(Op, SHR1Bit); Ref Result = _Ashr(OpSize, Dest, Constant(Shift)); CalculateFlags_SignShiftRightImmediate(OpSizeFromSrc(Op), Result, Dest, Shift); CalculateDeferredFlags(); StoreResultGPR(Op, Result); } else { auto Src = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); Ref Result = _Ashr(OpSize, Dest, Src); HandleShift(Op, Result, Dest, ShiftType::ASR, Src); } } void OpDispatchBuilder::RotateOp(OpcodeArgs, bool Left, bool IsImmediate, bool Is1Bit) { CalculateDeferredFlags(); const uint32_t Size = GetSrcBitSize(Op); const auto OpSize = Size == 64 ? OpSize::i64Bit : OpSize::i32Bit; uint64_t UnmaskedConst {}; // x86 masks the shift by 0x3F or 0x1F depending on size of op. But it's // equivalent to mask to the actual size of the op, that way we can bound // things tighter for 8-bit later in the function. uint64_t Mask = Size == 8 ? 7 : (Size == 64 ? 0x3F : 0x1F); ArithRef UnmaskedSrc; if (Is1Bit || IsImmediate) { UnmaskedConst = GetConstantShift(Op, Is1Bit); UnmaskedSrc = ARef(UnmaskedConst); } else { UnmaskedSrc = ARef(LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true)); } auto Src = UnmaskedSrc.And(Mask); // We fill the upper bits so we allow garbage on load. auto Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); if (Size < 32) { // ARM doesn't support 8/16bit rotates. Emulate with an insert // StoreResult truncates back to a 8/16 bit value Dest = _Bfi(OpSize, Size, Left ? (32 - Size) : Size, Dest, Dest); } // To rotate 64-bits left, right-rotate by (64 - Shift) = -Shift mod 64. auto Res = _Ror(OpSize, Dest, (Left ? Src.Neg() : Src).Ref()); StoreResultGPR(Op, Res); if (Is1Bit || IsImmediate) { if (UnmaskedSrc.C) { // Extract the last bit shifted in to CF SetCFDirect(Res, Left ? 0 : Size - 1, true); // For ROR, OF is the XOR of the new CF bit and the most significant bit of the result. // For ROL, OF is the LSB and MSB XOR'd together. // OF is architecturally only defined for 1-bit rotate. if (UnmaskedSrc.C == 1) { auto NewOF = _XorShift(OpSize, Res, Res, ShiftType::LSR, Left ? Size - 1 : 1); SetRFLAG(NewOF, Left ? 0 : Size - 2, true); } } } else { HandleNZCVWrite(); RectifyCarryInvert(true); // We deferred the masking for 8-bit to the flag section, do it here. if (Size == 8) { Src = UnmaskedSrc.And(0x1F); } _RotateFlags(OpSizeFromSrc(Op), Res, Src.Ref(), Left); } } void OpDispatchBuilder::ANDNBMIOp(OpcodeArgs) { auto Src1 = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Src2 = LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true}); auto Dest = _Andn(OpSizeFromSrc(Op), Src2, Src1); StoreResultGPR(Op, Dest); CalculateFlags_Logical(OpSizeFromSrc(Op), Dest); } void OpDispatchBuilder::BEXTRBMIOp(OpcodeArgs) { // Essentially (Src1 >> Start) & ((1 << Length) - 1) // along with some edge-case handling and flag setting. LOGMAN_THROW_A_FMT(Op->InstSize >= 4, "No masking needed"); auto Src1 = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Src2 = LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true}); const auto Size = OpSizeFromSrc(Op); const auto SrcSize = IR::OpSizeAsBits(Size); const auto MaxSrcBit = SrcSize - 1; auto MaxSrcBitOp = Constant(MaxSrcBit); // Shift the operand down to the starting bit auto Start = _Bfe(OpSizeFromSrc(Op), 8, 0, Src2); auto Shifted = _Lshr(Size, Src1, Start); // Shifts larger than operand size need to be set to zero. auto SanitizedShifted = _Select(Size, Size, CondClass::ULE, Start, MaxSrcBitOp, Shifted, Constant(0)); // Now handle the length specifier. auto Length = _Bfe(Size, 8, 8, Src2); // Now build up the mask // (1 << Length) - 1 = ~(~0 << Length) auto AllOnes = Constant(~0ull); auto InvertedMask = _Lshl(Size, AllOnes, Length); // Now put it all together and make the result. auto Masked = _Andn(Size, SanitizedShifted, InvertedMask); // Sanitize the length. If it is above the max, we don't do the masking. auto Dest = _Select(Size, Size, CondClass::ULE, Length, MaxSrcBitOp, Masked, SanitizedShifted); // Finally store the result. StoreResultGPR(Op, Dest); // ZF is set properly. CF and OF are defined as being set to zero. SF, PF, and // AF are undefined. SetNZ_ZeroCV(GetOpSize(Dest), Dest); InvalidatePF_AF(); } void OpDispatchBuilder::BLSIBMIOp(OpcodeArgs) { // Equivalent to performing: SRC & -SRC LOGMAN_THROW_A_FMT(Op->InstSize >= 4, "No masking needed"); const auto Size = OpSizeFromSrc(Op); auto Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto NegatedSrc = _Neg(Size, Src); auto Result = _And(Size, Src, NegatedSrc); StoreResultGPR(Op, Result); // CF is cleared if Src is zero, otherwise it's set. However, Src is zero iff // Result is zero, so we can test the result instead. So, CF is just the // inverted ZF. // // ZF/SF/OF set as usual. SetNZ_ZeroCV(Size, Result); InvalidatePF_AF(); SetCFInverted(GetRFLAG(X86State::RFLAG_ZF_RAW_LOC)); } void OpDispatchBuilder::BLSMSKBMIOp(OpcodeArgs) { // Equivalent to: (Src - 1) ^ Src LOGMAN_THROW_A_FMT(Op->InstSize >= 4, "No masking needed"); const auto Size = OpSizeFromSrc(Op); auto Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Result = _Xor(Size, Sub(Size, Src, 1), Src); StoreResultGPR(Op, Result); InvalidatePF_AF(); // CF set according to the Src auto CFInv = To01(Size, Src); // The output of BLSMSK is always nonzero, so TST will clear Z (along with C // and O) while setting S. SetNZ_ZeroCV(Size, Result); SetCFInverted(CFInv); } void OpDispatchBuilder::BLSRBMIOp(OpcodeArgs) { // Equivalent to: (Src - 1) & Src LOGMAN_THROW_A_FMT(Op->InstSize >= 4, "No masking needed"); const auto Size = OpSizeFromSrc(Op); auto Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Result = _And(Size, Sub(Size, Src, 1), Src); StoreResultGPR(Op, Result); auto CFInv = To01(Size, Src); SetNZ_ZeroCV(Size, Result); SetCFInverted(CFInv); InvalidatePF_AF(); } // Handles SARX, SHLX, and SHRX void OpDispatchBuilder::BMI2Shift(OpcodeArgs) { // In the event the source is a memory operand, use the // exact width instead of the GPR size. const auto GPRSize = GetGPROpSize(); const auto Size = OpSizeFromSrc(Op); const auto SrcSize = Op->Src[0].IsGPR() ? GPRSize : Size; auto Src = LoadSourceGPR_WithOpSize(Op, Op->Src[0], SrcSize, Op->Flags); auto Shift = LoadSourceGPR_WithOpSize(Op, Op->Src[1], GPRSize, Op->Flags, {.AllowUpperGarbage = true}); Ref Result; if (Op->OP == 0x6F7) { // SARX Result = _Ashr(Size, Src, Shift); } else if (Op->OP == 0x5F7) { // SHLX Result = _Lshl(Size, Src, Shift); } else { // SHRX Result = _Lshr(Size, Src, Shift); } StoreResultGPR(Op, Result); } void OpDispatchBuilder::BZHI(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); const auto OperandSize = IR::OpSizeAsBits(Size); // In 32-bit mode we only look at bottom 32-bit, no 8 or 16-bit BZHI so no // need to zero-extend sources auto Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Index = LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true}); // Clear the high bits specified by the index. A64 only considers bottom bits // of the shift, so we don't need to mask bottom 8-bits ourselves. // Out-of-bounds results ignored after. auto Mask = _Lshl(Size, Constant(-1), Index); auto MaskResult = _Andn(Size, Src, Mask); // If the index is above OperandSize, we don't clear anything. BZHI only // considers the bottom 8-bits, so we really want to know if the bottom 8-bits // have their top bits set. Test exactly that. // // Because we're clobbering flags internally we ignore all carry invert // shenanigans and use the raw versions here. _TestNZ(OpSize::i64Bit, Index, Constant(0xFF & ~(OperandSize - 1))); auto Result = _NZCVSelect(Size, CondClass::NEQ, Src, MaskResult); StoreResultGPR(Op, Result); auto CFInv = _NZCVSelect01(CondClass::EQ); InvalidatePF_AF(); SetNZ_ZeroCV(Size, Result); SetCFInverted(CFInv); } void OpDispatchBuilder::RORX(OpcodeArgs) { const auto SrcSize = OpSizeFromSrc(Op); const auto SrcSizeBits = IR::OpSizeAsBits(SrcSize); const auto Amount = Op->Src[1].Literal() & (SrcSizeBits - 1); const auto GPRSize = GetGPROpSize(); const auto DoRotation = Amount != 0 && Amount < SrcSizeBits; const auto IsSameGPR = Op->Src[0].IsGPR() && Op->Dest.IsGPR() && Op->Src[0].Data.GPR.GPR == Op->Dest.Data.GPR.GPR; const auto SrcSizeIsGPRSize = SrcSize == GPRSize; // If we don't need to rotate and our source is the same as the destination // then we don't need to do anything at all. We still need to be careful, // since 32-bit operations on 64-bit mode still need to zero-extend the // destination register. So also compare source size and GPR size. // // Very unlikely, but hey, we can do nothing faster. if (!DoRotation && IsSameGPR && SrcSizeIsGPRSize) [[unlikely]] { return; } auto Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Result = Src; if (DoRotation) [[likely]] { Result = _Ror(OpSizeFromSrc(Op), Src, _InlineConstant(Amount)); } StoreResultGPR(Op, Result); } void OpDispatchBuilder::MULX(OpcodeArgs) { // RDX is the implied source operand in the instruction const auto OpSize = OpSizeFromSrc(Op); // Src1 can be a memory operand, so ensure we constrain to the // absolute width of the access in that scenario. const auto GPRSize = GetGPROpSize(); const auto Src1Size = Op->Src[1].IsGPR() ? GPRSize : OpSize; Ref Src1 = LoadSourceGPR_WithOpSize(Op, Op->Src[1], Src1Size, Op->Flags); Ref Src2 = LoadGPRRegister(X86State::REG_RDX, GPRSize); // As per the Intel Software Development Manual, if the destination and // first operand correspond to the same register, then the result // will be the high half of the multiplication result. if (Op->Dest.Data.GPR.GPR == Op->Src[0].Data.GPR.GPR) { Ref ResultHi = _UMulH(OpSize, Src1, Src2); StoreResultGPR(Op, Op->Dest, ResultHi); } else { Ref ResultLo = _UMul(OpSize, Src1, Src2); Ref ResultHi = _UMulH(OpSize, Src1, Src2); StoreResultGPR(Op, Op->Src[0], ResultLo); StoreResultGPR(Op, Op->Dest, ResultHi); } } void OpDispatchBuilder::PDEP(OpcodeArgs) { LOGMAN_THROW_A_FMT(Op->InstSize >= 4, "No masking needed"); auto Input = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Mask = LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true}); auto Result = _PDep(OpSizeFromSrc(Op), Input, Mask); StoreResultGPR(Op, Op->Dest, Result); } void OpDispatchBuilder::PEXT(OpcodeArgs) { LOGMAN_THROW_A_FMT(Op->InstSize >= 4, "No masking needed"); auto Input = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Mask = LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true}); auto Result = _PExt(OpSizeFromSrc(Op), Input, Mask); StoreResultGPR(Op, Op->Dest, Result); } void OpDispatchBuilder::ADXOp(OpcodeArgs) { const auto OpSize = OpSizeFromSrc(Op); // Only 32/64-bit anyway so allow garbage, we use 32-bit ops. auto Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Before = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); // Handles ADCX and ADOX const bool IsADCX = Op->OP == 0x1F6; auto Zero = Constant(0); // Before we go trashing NZCV, save the current NZCV state. Ref OldNZCV = GetNZCV(); // We want to use arm64 adc. For ADOX, copy the overflow flag into CF. For // ADCX, we just rectify the carry. if (IsADCX) { RectifyCarryInvert(false); } else { // If overflow, 0 - 0 sets carry. Else, forces carry to 0. _CondSubNZCV(OpSize::i32Bit, Zero, Zero, CondClass::FU, 0x0 /* nzcv */); } // Do the actual add. HandleNZCV_RMW(); auto Result = _AdcWithFlags(OpSize, Src, Before); StoreResultGPR(Op, Result); // Now restore all flags except the one we're updating. if (CTX->HostFeatures.SupportsFlagM) { // For ADOX, we need to copy the new carry into the overflow flag. If carry is clear (ULT with uninverted // carry), 0 - 0 clears overflow. Else, force overflow on. if (!IsADCX) { _CondSubNZCV(OpSize::i32Bit, Zero, Zero, CondClass::ULT, 0x1 /* nzcV */); } _RmifNZCV(OldNZCV, 28, IsADCX ? 0xd /* NzcV */ : 0xe /* NZCv */); } else { // For either operation, insert the new flag into the old NZCV. bool SavedCFInvert = CFInverted; CFInverted = false; Ref OutputCF = GetRFLAG(X86State::RFLAG_CF_RAW_LOC, IsADCX); CFInverted = IsADCX ? true : SavedCFInvert; Ref NewNZCV = _Bfi(OpSize::i32Bit, 1, IsADCX ? 29 : 28, OldNZCV, OutputCF); SetNZCV(NewNZCV); } } void OpDispatchBuilder::RCROp1Bit(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); // We expliclty mask for <32-bit so allow garbage Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); const auto Size = GetSrcBitSize(Op); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); Ref Res; // Our new CF will be bit 0 of the source. Set upfront to avoid a move. SetCFDirect(Dest, 0, true); uint32_t Shift = 1; if (Size == 32 || Size == 64) { // Rotate and insert CF in the upper bit Res = _Extr(OpSizeFromSrc(Op), CF, Dest, Shift); } else { // Res = Src >> Shift Res = _Bfe(OpSize::i32Bit, Size - Shift, Shift, Dest); // inject the CF Res = _Orlshl(OpSize::i32Bit, Res, CF, Size - Shift); } StoreResultGPR(Op, Res); // OF is the top two MSBs XOR'd together // Only when Shift == 1, it is undefined otherwise SetRFLAG(_XorShift(OpSize::i64Bit, Res, Res, ShiftType::LSR, 1), Size - 2, true); } void OpDispatchBuilder::RCROp8x1Bit(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags); const auto SizeBit = GetSrcBitSize(Op); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); // Our new CF will be bit (Shift - 1) of the source SetCFDirect(Dest, 0, true); // Rotate and insert CF in the upper bit Ref Res = _Bfe(OpSize::i32Bit, 7, 1, Dest); Res = _Bfi(OpSize::i32Bit, 1, 7, Res, CF); StoreResultGPR(Op, Res); // OF is the top two MSBs XOR'd together SetRFLAG(_XorShift(OpSize::i32Bit, Res, Res, ShiftType::LSR, 1), SizeBit - 2, true); } void OpDispatchBuilder::RCROp(OpcodeArgs, bool UseRCX) { const auto Size = GetSrcBitSize(Op); if (Size == 8 || Size == 16) { RCRSmallerOp(Op, UseRCX); return; } const auto Mask = (Size == 64) ? 0x3F : 0x1F; // Calculate flags early. CalculateDeferredFlags(); const auto OpSize = OpSizeFromSrc(Op); if (!UseRCX) { Ref Src = LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true}); uint64_t Const; if (IsValueConstant(WrapNode(Src), &Const)) { Const &= Mask; if (!Const) { ZeroShiftResult(Op); return; } Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); // Res = Src >> Shift Ref Res = _Lshr(OpSize, Dest, Src); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); // Constant folded version of the above, with fused shifts. if (Const > 1) { Res = _Orlshl(OpSize, Res, Dest, Size + 1 - Const); } // Our new CF will be bit (Shift - 1) of the source. SetCFDirect(Dest, Const - 1, true); // Since shift != 0 we can inject the CF Res = _Orlshl(OpSize, Res, CF, Size - Const); // OF is the top two MSBs XOR'd together // Only when Shift == 1, it is undefined otherwise if (Const == 1) { auto Xor = _XorShift(OpSize, Res, Res, ShiftType::LSR, 1); SetRFLAG(Xor, Size - 2, true); } StoreResultGPR(Op, Res); return; } } // x86 masks the shift by 0x3F or 0x1F depending on size of op auto Src = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); Ref SrcMasked = _And(OpSize, Src, _InlineConstant(Mask)); Calculate_ShiftVariable( Op, SrcMasked, [this, Op, Size, OpSize]() { // Rematerialize loads to avoid crossblock liveness auto Src = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); // Res = Src >> Shift Ref Res = _Lshr(OpSize, Dest, Src); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); // Res |= (Dest << (Size - Shift + 1)); // Expressed as Res | ((Src << (Size - Shift)) << 1) to get correct // behaviour for Shift without clobbering NZCV. Then observe that modulo // Size, Size - Shift = -Shift so we can use a simple Neg. // // The masking of Lshl means we don't need mask the source, since: // // -(x & Mask) & Mask = (-x) & Mask Ref NegSrc = _Neg(OpSize, Src); Res = _Orlshl(OpSize, Res, _Lshl(OpSize, Dest, NegSrc), 1); // Our new CF will be bit (Shift - 1) of the source. this is hoisted up to // avoid the need to copy the source. Again, the Lshr absorbs the masking. auto NewCF = _Lshr(OpSize, Dest, Sub(OpSize, Src, 1)); SetCFDirect(NewCF, 0, true); // Since shift != 0 we can inject the CF Res = _Or(OpSize, Res, _Lshl(OpSize, CF, NegSrc)); // OF is the top two MSBs XOR'd together // Only when Shift == 1, it is undefined otherwise auto Xor = _XorShift(OpSize, Res, Res, ShiftType::LSR, 1); SetRFLAG(Xor, Size - 2, true); StoreResultGPR(Op, Res); }, OpSizeFromSrc(Op) == OpSize::i32Bit ? std::make_optional(&OpDispatchBuilder::ZeroShiftResult) : std::nullopt); } void OpDispatchBuilder::RCRSmallerOp(OpcodeArgs, bool UseRCX) { CalculateDeferredFlags(); const auto Size = GetSrcBitSize(Op); // x86 masks the shift by 0x3F or 0x1F depending on size of op auto GetShift = [this, Op, UseRCX]() { if (UseRCX) { auto Src = ARef(LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true)); return Src.And(0x1F); } else { auto Src = ARef(LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true})); return Src.And(0x1F); } }; auto Src = GetShift(); // CF only changes if we actually shifted. OF undefined if we didn't shift. // The result is unchanged if we didn't shift. So branch over the whole thing. Calculate_ShiftVariable(Op, Src.Ref(), [this, Op, Size, GetShift]() { // Rematerialized to avoid crossblock liveness auto Src = GetShift(); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags); Ref Tmp {}; // Insert the incoming value across the temporary 64bit source // Make sure to insert at + 1 offsets // We need to cover 32bits plus the amount that could rotate in if (Size == 8) { // 8-bit optimal cascade // Cascade: 0 // Data: -> [7:0] // CF: -> [8:8] // Cascade: 1 // Data: -> [16:9] // CF: -> [17:17] // Cascade: 2 // Data: -> [25:18] // CF: -> [26:26] // Cascade: 3 // Data: -> [34:27] // CF: -> [35:35] // Cascade: 4 // Data: -> [43:36] // CF: -> [44:44] // Insert CF, Destination already at [7:0] Tmp = _Bfi(OpSize::i64Bit, 1, 8, Dest, CF); // First Cascade, copies 9 bits from itself. Tmp = _Bfi(OpSize::i64Bit, 9, 9, Tmp, Tmp); // Second cascade, copies 18 bits from itself. Tmp = _Bfi(OpSize::i64Bit, 18, 18, Tmp, Tmp); // Final cascade, copies 9 bits again from itself. Tmp = _Bfi(OpSize::i64Bit, 9, 36, Tmp, Tmp); } else { // 16-bit optimal cascade // Cascade: 0 // Data: -> [15:0] // CF: -> [16:16] // Cascade: 1 // Data: -> [32:17] // CF: -> [33:33] // Cascade: 2 // Data: -> [49:34] // CF: -> [50:50] // Insert CF, Destination already at [15:0] Tmp = _Bfi(OpSize::i64Bit, 1, 16, Dest, CF); // First Cascade, copies 17 bits from itself. Tmp = _Bfi(OpSize::i64Bit, 17, 17, Tmp, Tmp); // Final Cascade, copies 17 bits from itself again. Tmp = _Bfi(OpSize::i64Bit, 17, 34, Tmp, Tmp); } // Entire bitfield has been setup. Just extract the 8 or 16bits we need. // 64-bit shift used because we want to rotate in our cascaded upper bits // rather than zeroes. Ref Res = _Lshr(OpSize::i64Bit, Tmp, Src.Ref()); StoreResultGPR(Op, Res); // Our new CF will be bit (Shift - 1) of the source. 32-bit Lshr masks the // same as x86, but if we constant fold we must mask ourselves. if (Src.IsConstant) { SetCFDirect(Tmp, (Src.C & 0x1f) - 1, true); } else { auto NewCF = _Lshr(OpSize::i32Bit, Tmp, Sub(OpSize::i32Bit, Src.Ref(), 1)); SetCFDirect(NewCF, 0, true); } // OF is the top two MSBs XOR'd together // Only when Shift == 1, it is undefined otherwise if (!Src.IsConstant || Src.C == 1) { auto NewOF = _XorShift(OpSize::i32Bit, Res, Res, ShiftType::LSR, 1); SetRFLAG(NewOF, Size - 2, true); } }); } void OpDispatchBuilder::RCLOp1Bit(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags); const auto Size = GetSrcBitSize(Op); const auto OpSize = Size == 64 ? OpSize::i64Bit : OpSize::i32Bit; auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); // Rotate left and insert CF in to lowest bit // TODO: Use `adc Res, xzr, Dest, lsl 1` to save an instruction Ref Res = _Orlshl(OpSize, CF, Dest, 1); // Our new CF will be the top bit of the source SetCFDirect(Dest, Size - 1, true); // OF is the top two MSBs XOR'd together // Top two MSBs is CF and top bit of result SetRFLAG(_Xor(OpSize, Res, Dest), Size - 1, true); StoreResultGPR(Op, Res); } void OpDispatchBuilder::RCLOp(OpcodeArgs, bool UseRCX) { const auto Size = GetSrcBitSize(Op); if (Size == 8 || Size == 16) { RCLSmallerOp(Op, UseRCX); return; } const auto Mask = (Size == 64) ? 0x3F : 0x1F; // Calculate flags early. CalculateDeferredFlags(); const auto OpSize = OpSizeFromSrc(Op); if (!UseRCX) { Ref Src = LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true}); uint64_t Const; if (IsValueConstant(WrapNode(Src), &Const)) { Const &= Mask; if (!Const) { ZeroShiftResult(Op); return; } // Res = Src << Shift Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); Ref Res = _Lshl(OpSize, Dest, Src); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); // Res |= (Src << (Size - Shift + 1)); if (Const > 1) { Res = _Orlshr(OpSize, Res, Dest, Size + 1 - Const); } // Our new CF will be bit (Shift - 1) of the source SetCFDirect(Dest, Size - Const, true); // Since Shift != 0 we can inject the CF Res = _Orlshl(OpSize, Res, CF, Const - 1); // OF is the top two MSBs XOR'd together // Only when Shift == 1, it is undefined otherwise if (Const == 1) { auto NewOF = _Xor(OpSize, Res, Dest); SetRFLAG(NewOF, Size - 1, true); } StoreResultGPR(Op, Res); return; } } // x86 masks the shift by 0x3F or 0x1F depending on size of op auto Src = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); Ref SrcMasked = _And(OpSize, Src, _InlineConstant(Mask)); Calculate_ShiftVariable( Op, SrcMasked, [this, Op, Size, OpSize]() { // Rematerialized to avoid crossblock liveness auto Src = LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true); // Res = Src << Shift Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); Ref Res = _Lshl(OpSize, Dest, Src); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); // Res |= (Dest >> (Size - Shift + 1)), expressed as // Res | ((Dest >> (-Shift)) >> 1), since Size - Shift = -Shift mod // Size. The shift aborbs the masking. auto NegSrc = _Neg(OpSize, Src); Res = _Orlshr(OpSize, Res, _Lshr(OpSize, Dest, NegSrc), 1); // Our new CF will be bit (Shift - 1) of the source auto NewCF = _Lshr(OpSize, Dest, NegSrc); SetCFDirect(NewCF, 0, true); // Since Shift != 0 we can inject the CF. Shift absorbs the masking. Ref CFShl = Sub(OpSize, Src, 1); auto TmpCF = _Lshl(OpSize, CF, CFShl); Res = _Or(OpSize, Res, TmpCF); // OF is the top two MSBs XOR'd together // Only when Shift == 1, it is undefined otherwise // // Note that NewCF has garbage in the upper bits, but we ignore them here // and mask as part of the set after. auto NewOF = _XorShift(OpSize, Res, NewCF, ShiftType::LSL, Size - 1); SetRFLAG(NewOF, Size - 1, true); StoreResultGPR(Op, Res); }, OpSizeFromSrc(Op) == OpSize::i32Bit ? std::make_optional(&OpDispatchBuilder::ZeroShiftResult) : std::nullopt); } void OpDispatchBuilder::RCLSmallerOp(OpcodeArgs, bool UseRCX) { CalculateDeferredFlags(); const auto Size = GetSrcBitSize(Op); // x86 masks the shift by 0x3F or 0x1F depending on size of op auto GetShift = [this, Op, UseRCX]() { if (UseRCX) { auto Src = ARef(LoadGPRRegister(X86State::REG_RCX, OpSize::iInvalid, 0, true)); return Src.And(0x1F); } else { auto Src = ARef(LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true})); return Src.And(0x1F); } }; auto Src = GetShift(); // CF only changes if we actually shifted. OF undefined if we didn't shift. // The result is unchanged if we didn't shift. So branch over the whole thing. Calculate_ShiftVariable(Op, Src.Ref(), [this, Op, Size, GetShift]() { // Rematerialized to avoid crossblock liveness auto Src = GetShift(); Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); Ref Tmp = Constant(0); for (size_t i = 0; i < (32 + Size + 1); i += (Size + 1)) { // Insert incoming value Tmp = _Bfi(OpSize::i64Bit, Size, 63 - i - Size, Tmp, Dest); // Insert CF Tmp = _Bfi(OpSize::i64Bit, 1, 63 - i, Tmp, CF); } // Insert incoming value Tmp = _Bfi(OpSize::i64Bit, Size, 0, Tmp, Dest); // The data is now set up like this // [Data][CF]:[Data][CF]:[Data][CF]:[Data][CF] // Shift 1 more bit that expected to get our result // Shifting to the right will now behave like a rotate to the left // Which we emulate with a _Ror Ref Res = _Ror(OpSize::i64Bit, Tmp, Src.Neg().Ref()); StoreResultGPR(Op, Res); // Our new CF is now at the bit position that we are shifting // Either 0 if CF hasn't changed (CF is living in bit 0) // or higher auto NewCF = _Ror(OpSize::i64Bit, Tmp, Src.Presub(63).Ref()); SetCFDirect(NewCF, 0, true); // OF is the XOR of the NewCF and the MSB of the result // Only defined for 1-bit rotates. if (!Src.IsConstant || Src.C == 1) { auto NewOF = _XorShift(OpSize::i64Bit, NewCF, Res, ShiftType::LSR, Size - 1); SetRFLAG(NewOF, 0, true); } }); } void OpDispatchBuilder::BTOp(OpcodeArgs, uint32_t SrcIndex, BTAction Action) { Ref Value; ArithRef Src; bool IsNonconstant = Op->Src[SrcIndex].IsGPR(); const uint32_t Size = GetDstBitSize(Op); const uint32_t Mask = Size - 1; if (IsNonconstant) { // Because we mask explicitly with And/Bfe/Sbfe after, we can allow garbage here. Src = ARef(LoadSourceGPR(Op, Op->Src[SrcIndex], Op->Flags, {.AllowUpperGarbage = true})); } else { // Can only be an immediate // Masked by operand size Src = ARef(Op->Src[SrcIndex].Literal() & Mask); } if (Op->Dest.IsGPR()) { // When the destination is a GPR, we don't care about garbage in the upper bits. // Load the full register. auto Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, GetGPROpSize(), Op->Flags); Value = Dest; // Get the bit selection from the src. We need to mask for 8/16-bit, but // rely on the implicit masking of Lshr for native sizes. unsigned LshrSize = std::max(IR::OpSizeToSize(OpSize::i32Bit), Size / 8); auto BitSelect = (Size == (LshrSize * 8)) ? Src : Src.And(Mask); auto LshrOpSize = IR::SizeToOpSize(LshrSize); // AMD: OF/SF/ZF/AF/PF undefined. // Intel: OF/SF/AF/PF undefined. ZF must be preserved. // We choose to preserve ZF/OF/SF since we just use an rmif // to insert into CF directly. We could optimize perhaps. // // Set CF before the action to save a move, except for complements where we // can reuse the invert. if (Action != BTAction::BTComplement) { if (IsNonconstant) { Value = _Lshr(IR::SizeToOpSize(LshrSize), Value, BitSelect.Ref()); } SetRFLAG(Value, X86State::RFLAG_CF_RAW_LOC, Src.IsConstant ? Src.C : 0, true); CFInverted = false; } switch (Action) { case BTAction::BTNone: { /* Nothing to do */ break; } case BTAction::BTClear: { Dest = _Andn(LshrOpSize, Dest, BitSelect.MaskBit(LshrOpSize).Ref()); StoreResultGPR(Op, Dest); break; } case BTAction::BTSet: { Dest = _Or(LshrOpSize, Dest, BitSelect.MaskBit(LshrOpSize).Ref()); StoreResultGPR(Op, Dest); break; } case BTAction::BTComplement: { Dest = _Xor(LshrOpSize, Dest, BitSelect.MaskBit(LshrOpSize).Ref()); if (IsNonconstant) { Value = _Lshr(LshrOpSize, Dest, BitSelect.Ref()); } else { Value = Dest; } SetRFLAG(Value, X86State::RFLAG_CF_RAW_LOC, Src.IsConstant ? Src.C : 0, true); CFInverted = true; StoreResultGPR(Op, Dest); break; } } } else { // Load the address to the memory location Ref Dest = MakeSegmentAddress(Op, Op->Dest); // Get the bit selection from the src auto BitSelect = Src.Bfe(0, 3); // Address is provided as bits we want BYTE offsets // Extract Signed offset Src = Src.Sbfe(3, Size - 3); // Get the address offset by shifting out the size of the op (To shift out the bit selection) // Then use that to index in to the memory location by size of op AddressMode Address = {.Base = Dest, .Index = Src.Ref(), .AddrSize = OpSize::i64Bit}; switch (Action) { case BTAction::BTNone: { Value = _LoadMemGPRAutoTSO(OpSize::i8Bit, Address, OpSize::i8Bit); break; } case BTAction::BTClear: { Ref BitMask = BitSelect.MaskBit(OpSize::i64Bit).Ref(); if (DestIsLockedMem(Op)) { HandledLock = true; Value = _AtomicFetchCLR(OpSize::i8Bit, BitMask, LoadEffectiveAddress(this, Address, GetGPROpSize(), true)); } else { Value = _LoadMemGPRAutoTSO(OpSize::i8Bit, Address, OpSize::i8Bit); auto Modified = _Andn(OpSize::i64Bit, Value, BitMask); _StoreMemGPRAutoTSO(OpSize::i8Bit, Address, Modified, OpSize::i8Bit); } break; } case BTAction::BTSet: { Ref BitMask = BitSelect.MaskBit(OpSize::i64Bit).Ref(); if (DestIsLockedMem(Op)) { HandledLock = true; Value = _AtomicFetchOr(OpSize::i8Bit, BitMask, LoadEffectiveAddress(this, Address, GetGPROpSize(), true)); } else { Value = _LoadMemGPRAutoTSO(OpSize::i8Bit, Address, OpSize::i8Bit); auto Modified = _Or(OpSize::i64Bit, Value, BitMask); _StoreMemGPRAutoTSO(OpSize::i8Bit, Address, Modified, OpSize::i8Bit); } break; } case BTAction::BTComplement: { Ref BitMask = BitSelect.MaskBit(OpSize::i64Bit).Ref(); if (DestIsLockedMem(Op)) { HandledLock = true; Value = _AtomicFetchXor(OpSize::i8Bit, BitMask, LoadEffectiveAddress(this, Address, GetGPROpSize(), true)); } else { Value = _LoadMemGPRAutoTSO(OpSize::i8Bit, Address, OpSize::i8Bit); auto Modified = _Xor(OpSize::i64Bit, Value, BitMask); _StoreMemGPRAutoTSO(OpSize::i8Bit, Address, Modified, OpSize::i8Bit); } break; } } // Now shift in to the correct bit location if (!BitSelect.IsDefinitelyZero()) { Value = _Lshr(std::max(OpSize::i32Bit, GetOpSize(Value)), Value, BitSelect.Ref()); } // AMD: OF/SF/ZF/AF/PF undefined. // Intel: OF/SF/AF/PF undefined. ZF must be preserved. // We choose to preserve ZF/OF/SF since we just use an rmif // to insert into CF directly. We could optimize perhaps. SetCFDirect(Value, 0, true); } } void OpDispatchBuilder::IMUL1SrcOp(OpcodeArgs) { /* We're just going to sign-extend the non-garbage anyway.. */ Ref Src1 = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); Ref Src2 = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); const auto Size = OpSizeFromSrc(Op); const auto SizeBits = IR::OpSizeAsBits(Size); Ref Dest {}; Ref ResultHigh {}; switch (Size) { case OpSize::i8Bit: case OpSize::i16Bit: { Src1 = _Sbfe(OpSize::i64Bit, SizeBits, 0, Src1); Src2 = _Sbfe(OpSize::i64Bit, SizeBits, 0, Src2); Dest = _Mul(OpSize::i64Bit, Src1, Src2); ResultHigh = _Sbfe(OpSize::i64Bit, SizeBits, SizeBits, Dest); break; } case OpSize::i32Bit: { ResultHigh = _SMull(Src1, Src2); ResultHigh = _Sbfe(OpSize::i64Bit, SizeBits, SizeBits, ResultHigh); // Flipped order to save a move Dest = _Mul(OpSize::i32Bit, Src1, Src2); break; } case OpSize::i64Bit: { ResultHigh = _MulH(OpSize::i64Bit, Src1, Src2); // Flipped order to save a move Dest = _Mul(OpSize::i64Bit, Src1, Src2); break; } default: FEX_UNREACHABLE; } StoreResultGPR(Op, Dest); CalculateFlags_MUL(Size, Dest, ResultHigh); } void OpDispatchBuilder::IMUL2SrcOp(OpcodeArgs) { Ref Src1 = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); Ref Src2 = LoadSourceGPR(Op, Op->Src[1], Op->Flags, {.AllowUpperGarbage = true}); const auto Size = OpSizeFromSrc(Op); const auto SizeBits = IR::OpSizeAsBits(Size); Ref Dest {}; Ref ResultHigh {}; switch (Size) { case OpSize::i8Bit: case OpSize::i16Bit: { Src1 = _Sbfe(OpSize::i64Bit, SizeBits, 0, Src1); Src2 = ARef(Src2).Sbfe(0, SizeBits).Ref(); Dest = _Mul(OpSize::i64Bit, Src1, Src2); ResultHigh = _Sbfe(OpSize::i64Bit, SizeBits, SizeBits, Dest); break; } case OpSize::i32Bit: { ResultHigh = _SMull(Src1, Src2); ResultHigh = _Sbfe(OpSize::i64Bit, SizeBits, SizeBits, ResultHigh); // Flipped order to save a move Dest = _Mul(OpSize::i32Bit, Src1, Src2); break; } case OpSize::i64Bit: { ResultHigh = _MulH(OpSize::i64Bit, Src1, Src2); // Flipped order to save a move Dest = _Mul(OpSize::i64Bit, Src1, Src2); break; } default: FEX_UNREACHABLE; } StoreResultGPR(Op, Dest); CalculateFlags_MUL(Size, Dest, ResultHigh); } void OpDispatchBuilder::IMULOp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); const auto SizeBits = IR::OpSizeAsBits(Size); Ref Src1 = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); Ref Src2 = LoadGPRRegister(X86State::REG_RAX); if (Size != OpSize::i64Bit) { Src1 = _Sbfe(OpSize::i64Bit, SizeBits, 0, Src1); Src2 = _Sbfe(OpSize::i64Bit, SizeBits, 0, Src2); } // 64-bit special cased to save a move Ref Result {}; if (Size < OpSize::i64Bit) { Result = _Mul(OpSize::i64Bit, Src1, Src2); } Ref ResultHigh {}; if (Size == OpSize::i8Bit) { // Result is stored in AX StoreGPRRegister(X86State::REG_RAX, Result, OpSize::i16Bit); ResultHigh = _Sbfe(OpSize::i64Bit, 8, 8, Result); } else if (Size == OpSize::i16Bit) { // 16bits stored in AX // 16bits stored in DX StoreGPRRegister(X86State::REG_RAX, Result, Size); ResultHigh = _Sbfe(OpSize::i64Bit, 16, 16, Result); StoreGPRRegister(X86State::REG_RDX, ResultHigh, Size); } else if (Size == OpSize::i32Bit) { // 32bits stored in EAX // 32bits stored in EDX // Make sure they get Zext correctly auto LocalResult = _Bfe(OpSize::i64Bit, 32, 0, Result); auto LocalResultHigh = _Bfe(OpSize::i64Bit, 32, 32, Result); ResultHigh = _Sbfe(OpSize::i64Bit, 32, 32, Result); Result = _Sbfe(OpSize::i64Bit, 32, 0, Result); StoreGPRRegister(X86State::REG_RAX, LocalResult); StoreGPRRegister(X86State::REG_RDX, LocalResultHigh); } else if (Size == OpSize::i64Bit) { if (!Is64BitMode) { LogMan::Msg::EFmt("Doesn't exist in 32bit mode"); DecodeFailure = true; return; } // 64bits stored in RAX // 64bits stored in RDX ResultHigh = _MulH(OpSize::i64Bit, Src1, Src2); Result = _Mul(OpSize::i64Bit, Src1, Src2); StoreGPRRegister(X86State::REG_RAX, Result); StoreGPRRegister(X86State::REG_RDX, ResultHigh); } CalculateFlags_MUL(Size, Result, ResultHigh); } void OpDispatchBuilder::MULOp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); const auto SizeBits = IR::OpSizeAsBits(Size); Ref Src1 = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); Ref Src2 = LoadGPRRegister(X86State::REG_RAX); Ref Result {}; if (Size != OpSize::i64Bit) { Src1 = _Bfe(OpSize::i64Bit, SizeBits, 0, Src1); Src2 = _Bfe(OpSize::i64Bit, SizeBits, 0, Src2); Result = _UMul(OpSize::i64Bit, Src1, Src2); } Ref ResultHigh {}; if (Size == OpSize::i8Bit) { // Result is stored in AX StoreGPRRegister(X86State::REG_RAX, Result, OpSize::i16Bit); ResultHigh = _Bfe(OpSize::i64Bit, 8, 8, Result); } else if (Size == OpSize::i16Bit) { // 16bits stored in AX // 16bits stored in DX StoreGPRRegister(X86State::REG_RAX, Result, Size); ResultHigh = _Bfe(OpSize::i64Bit, 16, 16, Result); StoreGPRRegister(X86State::REG_RDX, ResultHigh, Size); } else if (Size == OpSize::i32Bit) { // 32bits stored in EAX // 32bits stored in EDX Ref ResultLow = _Bfe(OpSize::i64Bit, 32, 0, Result); ResultHigh = _Bfe(OpSize::i64Bit, 32, 32, Result); StoreGPRRegister(X86State::REG_RAX, ResultLow); StoreGPRRegister(X86State::REG_RDX, ResultHigh); } else if (Size == OpSize::i64Bit) { if (!Is64BitMode) { LogMan::Msg::EFmt("Doesn't exist in 32bit mode"); DecodeFailure = true; return; } // 64bits stored in RAX // 64bits stored in RDX // // Calculate high first to allow better RA. ResultHigh = _UMulH(OpSize::i64Bit, Src1, Src2); Result = _UMul(OpSize::i64Bit, Src1, Src2); StoreGPRRegister(X86State::REG_RAX, Result); StoreGPRRegister(X86State::REG_RDX, ResultHigh); } CalculateFlags_UMUL(ResultHigh); } void OpDispatchBuilder::NOTOp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); const auto SizeBits = IR::OpSizeAsBits(Size); LOGMAN_THROW_A_FMT(Size >= IR::OpSize::i8Bit && Size <= IR::OpSize::i64Bit, "Invalid size"); Ref MaskConst {}; if (Size == OpSize::i64Bit) { MaskConst = Constant(~0ULL); } else { MaskConst = Constant((1ULL << SizeBits) - 1); } if (DestIsLockedMem(Op)) { HandledLock = true; Ref DestMem = MakeSegmentAddress(Op, Op->Dest); // Result unused _AtomicFetchXor(Size, MaskConst, DestMem); } else if (!Op->Dest.IsGPR()) { // GPR version plays fast and loose with sizes, be safe for memory tho. Ref Src = LoadSourceGPR(Op, Op->Dest, Op->Flags); Src = _Xor(OpSize::i64Bit, Src, MaskConst); StoreResultGPR(Op, Src); } else { // Specially handle high bits so we can invert in place with the correct // mask and a larger type. auto Dest = Op->Dest; if (Dest.Data.GPR.HighBits) { LOGMAN_THROW_A_FMT(Size == OpSize::i8Bit, "Only 8-bit GPRs get high bits"); MaskConst = Constant(0xFF00); Dest.Data.GPR.HighBits = false; } // Always load full size, we explicitly want the upper bits to get the // insert behaviour for free/implicitly. const auto GPRSize = GetGPROpSize(); Ref Src = LoadSourceGPR_WithOpSize(Op, Dest, GPRSize, Op->Flags); // For 8/16-bit, use 64-bit invert so we invert in place, while getting // insert behaviour. For 32-bit, use 32-bit invert to zero the upper bits. const auto EffectiveSize = Size == OpSize::i32Bit ? OpSize::i32Bit : GPRSize; // If we're inverting the whole thing, use Not instead of Xor to save a constant. if (Size >= OpSize::i32Bit) { Src = _Not(EffectiveSize, Src); } else { Src = _Xor(EffectiveSize, Src, MaskConst); } // Always store 64-bit, the Not/Xor correctly handle the upper bits and this // way we can delete the store. StoreResultGPR_WithOpSize(Op, Dest, Src, GPRSize); } } void OpDispatchBuilder::XADDOp(OpcodeArgs) { Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.LoadData = false}); Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags); Ref Result; if (Op->Dest.IsGPR()) { // If this is a GPR then we can just do an Add Result = CalculateFlags_ADD(OpSizeFromSrc(Op), Dest, Src); // Previous value in dest gets stored in src StoreResultGPR(Op, Op->Src[0], Dest); // Calculated value gets stored in dst (order is important if dst is same as src) StoreResultGPR(Op, Result); } else { HandledLock = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK; Dest = AppendSegmentOffset(Dest, Op->Flags); auto Before = _AtomicFetchAdd(OpSizeFromSrc(Op), Src, Dest); CalculateFlags_ADD(OpSizeFromSrc(Op), Before, Src); StoreResultGPR(Op, Op->Src[0], Before); } } void OpDispatchBuilder::PopcountOp(OpcodeArgs) { Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = CTX->HostFeatures.SupportsCSSC || GetSrcSize(Op) >= 4}); Src = _Popcount(OpSizeFromSrc(Op), Src); StoreResultGPR(Op, Src); // We need to set ZF while clearing the rest of NZCV. The result of a popcount // is in the range [0, 63]. In particular, it is always positive. So a // combined NZ test will correctly zero SF/CF/OF while setting ZF. SetNZ_ZeroCV(OpSize::i32Bit, Src); ZeroPF_AF(); } Ref OpDispatchBuilder::CalculateAFForDecimal(Ref A) { auto Nibble = _And(OpSize::i64Bit, A, Constant(0xF)); auto Greater = Select01(OpSize::i64Bit, CondClass::UGT, Nibble, Constant(9)); return _Or(OpSize::i64Bit, LoadAF(), Greater); } void OpDispatchBuilder::DAAOp(OpcodeArgs) { CalculateDeferredFlags(); auto AL = LoadGPRRegister(X86State::REG_RAX, OpSize::i8Bit); auto CFInv = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC, true); auto AF = CalculateAFForDecimal(AL); // CF |= (AL > 0x99); CFInv = _And(OpSize::i64Bit, CFInv, Select01(OpSize::i64Bit, CondClass::ULE, AL, Constant(0x99))); // AL = AF ? (AL + 0x6) : AL; AL = _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::NEQ, AF, Constant(0), Add(OpSize::i64Bit, AL, 0x6), AL); // AL = CF ? (AL + 0x60) : AL; AL = _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::EQ, CFInv, Constant(0), Add(OpSize::i64Bit, AL, 0x60), AL); // SF, ZF, PF set according to result. CF set per above. OF undefined. StoreGPRRegister(X86State::REG_RAX, AL, OpSize::i8Bit); SetNZ_ZeroCV(OpSize::i8Bit, AL); SetCFInverted(CFInv); CalculatePF(AL); SetAFAndFixup(AF); } void OpDispatchBuilder::DASOp(OpcodeArgs) { CalculateDeferredFlags(); auto AL = LoadGPRRegister(X86State::REG_RAX, OpSize::i8Bit); auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_RAW_LOC); auto AF = CalculateAFForDecimal(AL); // CF |= (AL > 0x99); CF = _Or(OpSize::i64Bit, CF, Select01(OpSize::i64Bit, CondClass::UGT, AL, Constant(0x99))); // NewCF = CF | (AF && (Borrow from AL - 6)) auto NewCF = _Or(OpSize::i32Bit, CF, _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::ULT, AL, Constant(6), AF, CF)); // AL = AF ? (AL - 0x6) : AL; AL = _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::NEQ, AF, Constant(0), Sub(OpSize::i64Bit, AL, 0x6), AL); // AL = CF ? (AL - 0x60) : AL; AL = _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::NEQ, CF, Constant(0), Sub(OpSize::i64Bit, AL, 0x60), AL); // SF, ZF, PF set according to result. CF set per above. OF undefined. StoreGPRRegister(X86State::REG_RAX, AL, OpSize::i8Bit); SetNZ_ZeroCV(OpSize::i8Bit, AL); SetCFDirect(NewCF); CalculatePF(AL); SetAFAndFixup(AF); } void OpDispatchBuilder::AAAOp(OpcodeArgs) { auto A = LoadGPRRegister(X86State::REG_RAX); auto AF = CalculateAFForDecimal(A); // CF = AF, OF/SF/ZF/PF undefined SetCFDirect_InvalidateNZV(AF); SetAFAndFixup(AF); CalculateDeferredFlags(); // AX = CF ? (AX + 0x106) : 0 A = NZCVSelect(OpSize::i32Bit, CondClass::UGE /* CF = 1 */, Add(OpSize::i32Bit, A, 0x106), A); // AL = AL & 0x0F A = _And(OpSize::i32Bit, A, Constant(0xFF0F)); StoreGPRRegister(X86State::REG_RAX, A, OpSize::i16Bit); } void OpDispatchBuilder::AASOp(OpcodeArgs) { auto A = LoadGPRRegister(X86State::REG_RAX); auto AF = CalculateAFForDecimal(A); // CF = AF, OF/SF/ZF/PF undefined SetCFDirect_InvalidateNZV(AF); SetAFAndFixup(AF); CalculateDeferredFlags(); // AX = CF ? (AX - 0x106) : 0 A = NZCVSelect(OpSize::i32Bit, CondClass::UGE /* CF = 1 */, Sub(OpSize::i32Bit, A, 0x106), A); // AL = AL & 0x0F A = _And(OpSize::i32Bit, A, Constant(0xFF0F)); StoreGPRRegister(X86State::REG_RAX, A, OpSize::i16Bit); } void OpDispatchBuilder::AAMOp(OpcodeArgs) { auto AL = LoadGPRRegister(X86State::REG_RAX, OpSize::i8Bit); auto Imm8 = Constant(Op->Src[0].Literal() & 0xFF); Ref Quotient = _AllocateGPR(true); Ref Remainder = _AllocateGPR(true); _UDiv(OpSize::i64Bit, AL, Invalid(), Imm8, Quotient, Remainder); auto Res = _AddShift(OpSize::i64Bit, Remainder, Quotient, ShiftType::LSL, 8); StoreGPRRegister(X86State::REG_RAX, Res, OpSize::i16Bit); SetNZ_ZeroCV(OpSize::i8Bit, Res); CalculatePF(Res); InvalidateAF(); } void OpDispatchBuilder::AADOp(OpcodeArgs) { auto A = LoadGPRRegister(X86State::REG_RAX); auto AH = _Lshr(OpSize::i32Bit, A, Constant(8)); auto Imm8 = Constant(Op->Src[0].Literal() & 0xFF); auto NewAL = Add(OpSize::i64Bit, A, _Mul(OpSize::i64Bit, AH, Imm8)); auto Result = _And(OpSize::i64Bit, NewAL, Constant(0xFF)); StoreGPRRegister(X86State::REG_RAX, Result, OpSize::i16Bit); SetNZ_ZeroCV(OpSize::i8Bit, Result); CalculatePF(Result); InvalidateAF(); } void OpDispatchBuilder::XLATOp(OpcodeArgs) { Ref Src = MakeSegmentAddress(X86State::REG_RBX, Op->Flags, X86Tables::DecodeFlags::FLAG_DS_PREFIX); Ref Offset = LoadGPRRegister(X86State::REG_RAX, OpSize::i8Bit); AddressMode A = {.Base = Src, .Index = Offset, .AddrSize = OpSize::i64Bit}; auto Res = _LoadMemGPRAutoTSO(OpSize::i8Bit, A, OpSize::i8Bit); StoreGPRRegister(X86State::REG_RAX, Res, OpSize::i8Bit); } void OpDispatchBuilder::ReadSegmentReg(OpcodeArgs, OpDispatchBuilder::Segment Seg) { // 64-bit only // Doesn't hit the segment register optimization const auto Size = OpSizeFromSrc(Op); Ref Src {}; if (Seg == Segment::FS) { Src = _LoadContextGPR(Size, offsetof(FEXCore::Core::CPUState, fs_cached)); } else { Src = _LoadContextGPR(Size, offsetof(FEXCore::Core::CPUState, gs_cached)); } StoreResultGPR(Op, Src); } void OpDispatchBuilder::WriteSegmentReg(OpcodeArgs, OpDispatchBuilder::Segment Seg) { // Documentation claims that the 32-bit version of this instruction inserts in to the lower 32-bits of the segment // This is incorrect and it instead zero extends the 32-bit value to 64-bit const auto Size = OpSizeFromDst(Op); Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags); if (Seg == Segment::FS) { _StoreContextGPR(Size, Src, offsetof(FEXCore::Core::CPUState, fs_cached)); } else { _StoreContextGPR(Size, Src, offsetof(FEXCore::Core::CPUState, gs_cached)); } } void OpDispatchBuilder::EnterOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); const auto OperandSize = (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_OPERAND_SIZE) ? OpSize::i16Bit : GPRSize; const uint64_t Value = Op->Src[0].Literal(); const uint16_t AllocSpace = Value & 0xFFFF; const uint8_t Level = (Value >> 16) & 0x1F; const auto PushValue = [&](IR::OpSize Size, Ref Src) -> Ref { auto OldSP = LoadGPRRegister(X86State::REG_RSP); auto NewSP = _Push(GPRSize, Size, Src, OldSP); // Store the new stack pointer StoreGPRRegister(X86State::REG_RSP, NewSP); return NewSP; }; auto OldBP = LoadGPRRegister(X86State::REG_RBP); auto NewSP = PushValue(OperandSize, OldBP); auto temp_RBP = NewSP; if (Level > 0) { for (uint8_t i = 1; i < Level; ++i) { auto MemLoc = Sub(GPRSize, OldBP, i * IR::OpSizeToSize(OperandSize)); auto Mem = _LoadMemGPR(OperandSize, MemLoc, OperandSize); NewSP = PushValue(OperandSize, Mem); } NewSP = PushValue(OperandSize, temp_RBP); } NewSP = Sub(GPRSize, NewSP, AllocSpace); StoreGPRRegister(X86State::REG_RSP, NewSP); StoreGPRRegister(X86State::REG_RBP, temp_RBP); } void OpDispatchBuilder::SGDTOp(OpcodeArgs) { auto DestAddress = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.LoadData = false}); // Store an emulated value in the format of: // uint16_t Limit; // {uint32_t,uint64_t} Base; // // Limit is always 0 // Base is always in kernel space at: 0xFFFFFFFFFFFE0000ULL // // Operand size prefix is ignored on this instruction, size purely depends on operating mode. uint64_t GDTAddress = 0xFFFFFFFFFFFE0000ULL; auto GDTStoreSize = OpSize::i64Bit; if (!Is64BitMode) { // Mask off upper bits if 32-bit result. GDTAddress &= ~0U; GDTStoreSize = OpSize::i32Bit; } _StoreMemGPRAutoTSO(OpSize::i16Bit, DestAddress, Constant(0)); _StoreMemGPRAutoTSO(GDTStoreSize, AddressMode {.Base = DestAddress, .Offset = 2, .AddrSize = OpSize::i64Bit}, Constant(GDTAddress)); } void OpDispatchBuilder::SIDTOp(OpcodeArgs) { auto DestAddress = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.LoadData = false}); // See SGDTOp, matches Linux in reported values uint64_t IDTAddress = 0xFFFFFE0000000000ULL; auto IDTStoreSize = OpSize::i64Bit; if (!Is64BitMode) { // Mask off upper bits if 32-bit result. IDTAddress &= ~0U; IDTStoreSize = OpSize::i32Bit; } _StoreMemGPRAutoTSO(OpSize::i16Bit, DestAddress, Constant(0xfff)); _StoreMemGPRAutoTSO(IDTStoreSize, AddressMode {.Base = DestAddress, .Offset = 2, .AddrSize = OpSize::i64Bit}, Constant(IDTAddress)); } void OpDispatchBuilder::SMSWOp(OpcodeArgs) { const bool IsMemDst = DestIsMem(Op); IR::OpSize DstSize {OpSize::iInvalid}; Ref Const = Constant((1U << 31) | ///< PG - Paging (0U << 30) | ///< CD - Cache Disable (0U << 29) | ///< NW - Not Writethrough (Legacy, now ignored) ///< [28:19] - Reserved (1U << 18) | ///< AM - Alignment Mask ///< 17 - Reserved (1U << 16) | ///< WP - Write Protect ///< [15:6] - Reserved (1U << 5) | ///< NE - Numeric Error (1U << 4) | ///< ET - Extension Type (Legacy, now reserved and 1) (0U << 3) | ///< TS - Task Switched (0U << 2) | ///< EM - Emulation (1U << 1) | ///< MP - Monitor Coprocessor (1U << 0)); ///< PE - Protection Enabled const auto OpAddr = X86Tables::DecodeFlags::GetOpAddr(Op->Flags, 0); if (Is64BitMode) { DstSize = OpAddr == X86Tables::DecodeFlags::FLAG_OPERAND_SIZE_LAST ? OpSize::i16Bit : OpAddr == X86Tables::DecodeFlags::FLAG_WIDENING_SIZE_LAST ? OpSize::i64Bit : OpSize::i32Bit; if (!IsMemDst && DstSize == OpSize::i32Bit) { // Special-case version of `smsw ebx`. This instruction does an insert in to the lower 32-bits on 64-bit hosts. // Override and insert. auto Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, GetGPROpSize(), Op->Flags); Const = _Bfi(OpSize::i64Bit, 32, 0, Dest, Const); DstSize = OpSize::i64Bit; } } else { DstSize = OpAddr == X86Tables::DecodeFlags::FLAG_OPERAND_SIZE_LAST ? OpSize::i16Bit : OpSize::i32Bit; } if (IsMemDst) { // Memory destinatino always writes only 16-bits. DstSize = OpSize::i16Bit; } StoreResultGPR_WithOpSize(Op, Op->Dest, Const, DstSize); } OpDispatchBuilder::CycleCounterPair OpDispatchBuilder::CycleCounter(bool SelfSynchronizingLoads) { Ref CounterLow {}; Ref CounterHigh {}; auto Counter = _CycleCounter(SelfSynchronizingLoads); if (CTX->Config.TSCScale) { CounterLow = _Lshl(OpSize::i32Bit, Counter, Constant(CTX->Config.TSCScale)); CounterHigh = _Lshr(OpSize::i64Bit, Counter, Constant(32 - CTX->Config.TSCScale)); } else { CounterLow = _Bfe(OpSize::i64Bit, 32, 0, Counter); CounterHigh = _Bfe(OpSize::i64Bit, 32, 32, Counter); } return { .CounterLow = CounterLow, .CounterHigh = CounterHigh, }; } void OpDispatchBuilder::RDTSCOp(OpcodeArgs) { auto Counter = CycleCounter(false); StoreGPRRegister(X86State::REG_RAX, Counter.CounterLow); StoreGPRRegister(X86State::REG_RDX, Counter.CounterHigh); } void OpDispatchBuilder::INCOp(OpcodeArgs) { Ref Dest; Ref Result; const auto Size = GetSrcBitSize(Op); const bool IsLocked = DestIsLockedMem(Op); if (IsLocked) { HandledLock = true; Ref DestAddress = MakeSegmentAddress(Op, Op->Dest); Dest = _AtomicFetchAdd(OpSizeFromSrc(Op), Constant(1), DestAddress); } else { Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = Size >= 32}); } CalculateDeferredFlags(); if (Size < 32 && CTX->HostFeatures.SupportsFlagM) { // Addition producing upper garbage Result = Add(OpSize::i32Bit, Dest, 1); CalculatePF(Result); CalculateAF(Dest, Constant(1)); // Correctly set NZ flags, preserving C HandleNZCV_RMW(); _SetSmallNZV(OpSizeFromSrc(Op), Result); // Fix up V flag. INC overflows only when incrementing a positive and // getting a negative. So compare the sign bits to calculate V. _RmifNZCV(_Andn(OpSize::i32Bit, Result, Dest), Size - 1, 1); } else { Result = CalculateFlags_ADD(OpSizeFromSrc(Op), Dest, Constant(1), false); } if (!IsLocked) { StoreResultGPR(Op, Result); } } void OpDispatchBuilder::DECOp(OpcodeArgs) { Ref Dest; Ref Result; const auto Size = GetSrcBitSize(Op); const bool IsLocked = DestIsLockedMem(Op); if (IsLocked) { HandledLock = true; Ref DestAddress = MakeSegmentAddress(Op, Op->Dest); // Use Add instead of Sub to avoid a NEG Dest = _AtomicFetchAdd(OpSizeFromSrc(Op), Constant(Size == 64 ? -1 : ((1ULL << Size) - 1)), DestAddress); } else { Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = Size >= 32}); } CalculateDeferredFlags(); if (Size < 32 && CTX->HostFeatures.SupportsFlagM) { // Subtraction producing upper garbage Result = Sub(OpSize::i32Bit, Dest, 1); CalculatePF(Result); CalculateAF(Dest, Constant(1)); // Correctly set NZ flags, preserving C HandleNZCV_RMW(); _SetSmallNZV(OpSizeFromSrc(Op), Result); // Fix up V flag. DEC overflows only when decrementing a negative and // getting a positive. So compare the sign bits to calculate V. _RmifNZCV(_Andn(OpSize::i32Bit, Dest, Result), Size - 1, 1); } else { Result = CalculateFlags_SUB(OpSizeFromSrc(Op), Dest, Constant(1), false); } if (!IsLocked) { StoreResultGPR(Op, Result); } } void OpDispatchBuilder::STOSOp(OpcodeArgs) { if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) { LogMan::Msg::EFmt("STOSOp: Can't handle address size override (OP: 0x{:04X}, Flags: 0x{:08X})", Op->OP, Op->Flags); DecodeFailure = true; return; } const auto Size = OpSizeFromSrc(Op); const bool Repeat = (Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX)) != 0; if (!Repeat) { // Src is used only for a store of the same size so allow garbage Ref Src = LoadGPRRegister(X86State::REG_RAX, Size, 0, true); // Only ES prefix Ref Dest = MakeSegmentAddress(X86State::REG_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); // Store to memory where RDI points if (CTX->IsMemcpyAtomicTSOEnabled()) { _StoreMemGPRAutoTSO(Size, Dest, Src, Size); } else { _StoreMem(RegClass::GPR, Size, Src, Dest, Invalid(), OpSize::i8Bit, MemOffsetType::SXTX, 1); } // Offset the pointer Ref TailDest = LoadGPRRegister(X86State::REG_RDI); StoreGPRRegister(X86State::REG_RDI, OffsetByDir(TailDest, IR::OpSizeToSize(Size))); } else { // FEX doesn't support partial faulting REP instructions. // Converting this to a `MemSet` IR op optimizes this quite significantly in our codegen. // If FEX is to gain support for faulting REP instructions, then this implementation needs to change significantly. Ref Src = LoadGPRRegister(X86State::REG_RAX, Size); Ref Dest = LoadGPRRegister(X86State::REG_RDI); // Only ES prefix auto Segment = GetSegment(0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); Ref Counter = LoadGPRRegister(X86State::REG_RCX); auto Result = _MemSet(CTX->IsAtomicTSOEnabled(), Size, Segment ?: InvalidNode, Dest, Src, Counter, LoadDir(1)); StoreGPRRegister(X86State::REG_RCX, Constant(0)); StoreGPRRegister(X86State::REG_RDI, Result); } } void OpDispatchBuilder::MOVSOp(OpcodeArgs) { if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) { LogMan::Msg::EFmt("MOVSOp: Can't handle address size override (OP: 0x{:04X}, Flags: 0x{:08X})", Op->OP, Op->Flags); DecodeFailure = true; return; } // RA now can handle these to be here, to avoid DF accesses const auto Size = OpSizeFromSrc(Op); if (Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX)) { auto SrcAddr = LoadGPRRegister(X86State::REG_RSI); auto DstAddr = LoadGPRRegister(X86State::REG_RDI); auto Counter = LoadGPRRegister(X86State::REG_RCX); auto DstSegment = GetSegment(0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); auto SrcSegment = GetSegment(Op->Flags, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX); if (DstSegment) { DstAddr = Add(OpSize::i64Bit, DstAddr, DstSegment); } if (SrcSegment) { SrcAddr = Add(OpSize::i64Bit, SrcAddr, SrcSegment); } Ref Result_Src = _AllocateGPR(false); Ref Result_Dst = _AllocateGPR(false); _MemCpy(CTX->IsAtomicTSOEnabled(), Size, DstAddr, SrcAddr, Counter, LoadDir(1), Result_Dst, Result_Src); if (DstSegment) { Result_Dst = Sub(OpSize::i64Bit, Result_Dst, DstSegment); } if (SrcSegment) { Result_Src = Sub(OpSize::i64Bit, Result_Src, SrcSegment); } StoreGPRRegister(X86State::REG_RCX, Constant(0)); StoreGPRRegister(X86State::REG_RDI, Result_Dst); StoreGPRRegister(X86State::REG_RSI, Result_Src); } else { Ref RSI = MakeSegmentAddress(X86State::REG_RSI, Op->Flags, X86Tables::DecodeFlags::FLAG_DS_PREFIX); Ref RDI = MakeSegmentAddress(X86State::REG_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); if (CTX->IsMemcpyAtomicTSOEnabled()) { auto Src = _LoadMemGPRAutoTSO(Size, RSI, Size); // Store to memory where RDI points _StoreMemGPRAutoTSO(Size, RDI, Src, Size); } else { auto Src = _LoadMem(RegClass::GPR, Size, RSI, Invalid(), OpSize::i8Bit, MemOffsetType::SXTX, 1); _StoreMem(RegClass::GPR, Size, Src, RDI, Invalid(), OpSize::i8Bit, MemOffsetType::SXTX, 1); } RSI = OffsetByDir(RSI, IR::OpSizeToSize(Size)); RDI = OffsetByDir(RDI, IR::OpSizeToSize(Size)); StoreGPRRegister(X86State::REG_RSI, RSI); StoreGPRRegister(X86State::REG_RDI, RDI); } } IR::OpSize OpDispatchBuilder::GetStringOpSize(X86Tables::DecodedOp Op) const { LOGMAN_THROW_A_FMT(Is64BitMode || !(Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE), "Invalid modifier on 32bit address"); return !Is64BitMode || (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) ? OpSize::i32Bit : OpSize::i64Bit; } void OpDispatchBuilder::CMPSOp(OpcodeArgs) { if (!Is64BitMode && (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE)) { LogMan::Msg::EFmt("CMPSOp: Address size override (0x67) not supported in 32-bit mode (OP: 0x{:04X}).", Op->OP); DecodeFailure = true; return; } const auto Size = OpSizeFromSrc(Op); OpSize AddrSize = GetStringOpSize(Op); bool Repeat = Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX); if (!Repeat) { Ref Src_RSI = LoadGPRRegister(X86State::REG_RSI, AddrSize); Ref Src_RDI = LoadGPRRegister(X86State::REG_RDI, AddrSize); Ref Dest_RSI = AppendSegmentOffset(Src_RSI, Op->Flags, X86Tables::DecodeFlags::FLAG_DS_PREFIX); Ref Dest_RDI = AppendSegmentOffset(Src_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); auto Src1 = _LoadMemGPRAutoTSO(Size, Dest_RDI, Size); auto Src2 = _LoadMemGPRAutoTSO(Size, Dest_RSI, Size); CalculateFlags_SUB(OpSizeFromSrc(Op), Src2, Src1); Dest_RDI = OffsetByDir(Src_RDI, IR::OpSizeToSize(Size)); if (Is64BitMode && AddrSize == OpSize::i32Bit) { Dest_RDI = _Bfe(OpSize::i64Bit, 32, 0, Dest_RDI); StoreGPRRegister(X86State::REG_RDI, Dest_RDI); } else { StoreGPRRegister(X86State::REG_RDI, Dest_RDI, AddrSize); } Dest_RSI = OffsetByDir(Src_RSI, IR::OpSizeToSize(Size)); if (Is64BitMode && AddrSize == OpSize::i32Bit) { Dest_RSI = _Bfe(OpSize::i64Bit, 32, 0, Dest_RSI); StoreGPRRegister(X86State::REG_RSI, Dest_RSI); } else { StoreGPRRegister(X86State::REG_RSI, Dest_RSI, AddrSize); } } else { // Calculate flags early. CalculateDeferredFlags(); bool REPE = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX; // If rcx = 0, skip the whole loop. Ref Counter = LoadGPRRegister(X86State::REG_RCX); auto OuterJump = CondJump(Counter, CondClass::EQ); auto BeforeLoop = CreateNewCodeBlockAfter(GetCurrentBlock()); SetFalseJumpTarget(OuterJump, BeforeLoop); SetCurrentCodeBlock(BeforeLoop); StartNewBlock(); ForeachDirection([this, Op, Size, AddrSize, REPE](int32_t PtrDir) { IRPair InnerJump; auto JumpIntoLoop = Jump(); // Setup for the loop auto LoopHeader = CreateNewCodeBlockAfter(GetCurrentBlock()); SetCurrentCodeBlock(LoopHeader); StartNewBlock(); SetJumpTarget(JumpIntoLoop, LoopHeader); // Working loop { Ref Src_RSI = LoadGPRRegister(X86State::REG_RSI, AddrSize); Ref Src_RDI = LoadGPRRegister(X86State::REG_RDI, AddrSize); Ref Dest_RSI = AppendSegmentOffset(Src_RSI, Op->Flags, X86Tables::DecodeFlags::FLAG_DS_PREFIX); Ref Dest_RDI = AppendSegmentOffset(Src_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); auto Src1 = _LoadMemGPRAutoTSO(Size, Dest_RDI, Size); auto Src2 = _LoadMemGPR(Size, Dest_RSI, Size); // We'll calculate PF/AF after the loop, so use them as temporaries here. StoreRegister(Core::CPUState::PF_AS_GREG, false, Src1); StoreRegister(Core::CPUState::AF_AS_GREG, false, Src2); Ref TailCounter = LoadGPRRegister(X86State::REG_RCX); // Decrement counter TailCounter = SubWithFlags(OpSize::i64Bit, TailCounter, 1); // Store the counter since we don't have phis StoreGPRRegister(X86State::REG_RCX, TailCounter); Dest_RDI = Add(AddrSize, Src_RDI, PtrDir * static_cast(IR::OpSizeToSize(Size))); if (Is64BitMode && AddrSize == OpSize::i32Bit) { Dest_RDI = _Bfe(OpSize::i64Bit, 32, 0, Dest_RDI); StoreGPRRegister(X86State::REG_RDI, Dest_RDI); } else { StoreGPRRegister(X86State::REG_RDI, Dest_RDI, AddrSize); } Dest_RSI = Add(AddrSize, Src_RSI, PtrDir * static_cast(IR::OpSizeToSize(Size))); if (Is64BitMode && AddrSize == OpSize::i32Bit) { Dest_RSI = _Bfe(OpSize::i64Bit, 32, 0, Dest_RSI); StoreGPRRegister(X86State::REG_RSI, Dest_RSI); } else { StoreGPRRegister(X86State::REG_RSI, Dest_RSI, AddrSize); } // If TailCounter != 0, compare sources. // If TailCounter == 0, set ZF iff that would break. _CondSubNZCV(OpSize::i64Bit, Src2, Src1, CondClass::NEQ, REPE ? 0 : (1 << 2) /* Z */); CachedNZCV = nullptr; NZCVDirty = false; InnerJump = CondJumpNZCV(REPE ? CondClass::EQ : CondClass::NEQ); // Jump back to the start if we have more work to do SetTrueJumpTarget(InnerJump, LoopHeader); } // Make sure to start a new block after ending this one auto LoopEnd = CreateNewCodeBlockAfter(GetCurrentBlock()); SetFalseJumpTarget(InnerJump, LoopEnd); SetCurrentCodeBlock(LoopEnd); StartNewBlock(); }); // Make sure to start a new block after ending this one { // Grab the sources from the last iteration so we can set flags. auto Src1 = LoadGPR(Core::CPUState::PF_AS_GREG); auto Src2 = LoadGPR(Core::CPUState::AF_AS_GREG); CalculateFlags_SUB(OpSizeFromSrc(Op), Src2, Src1); } auto Jump_ = Jump(); auto Exit = CreateNewCodeBlockAfter(GetCurrentBlock()); SetJumpTarget(Jump_, Exit); SetTrueJumpTarget(OuterJump, Exit); SetCurrentCodeBlock(Exit); StartNewBlock(); } } void OpDispatchBuilder::LODSOp(OpcodeArgs) { if (!Is64BitMode && (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE)) { LogMan::Msg::EFmt("LODSOp: Address size override (0x67) not supported in 32-bit mode (OP: 0x{:04X}).", Op->OP); DecodeFailure = true; return; } const auto Size = OpSizeFromSrc(Op); OpSize AddrSize = GetStringOpSize(Op); const bool Repeat = (Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX)) != 0; if (!Repeat) { Ref Src_RSI = LoadGPRRegister(X86State::REG_RSI, AddrSize); Ref Dest_RSI = AppendSegmentOffset(Src_RSI, 0, X86Tables::DecodeFlags::FLAG_DS_PREFIX, true); auto Src = _LoadMemGPRAutoTSO(Size, Dest_RSI, Size); StoreGPRResultWithZExtSemantics(X86State::REG_RAX, Src, Size); // Offset the pointer Ref TailDest_RSI = OffsetByDir(Src_RSI, IR::OpSizeToSize(Size)); if (Is64BitMode && AddrSize == OpSize::i32Bit) { TailDest_RSI = _Bfe(OpSize::i64Bit, 32, 0, TailDest_RSI); StoreGPRRegister(X86State::REG_RSI, TailDest_RSI); } else { StoreGPRRegister(X86State::REG_RSI, TailDest_RSI, AddrSize); } } else { // Calculate flags early. because end of block CalculateDeferredFlags(); ForeachDirection([this, Size, AddrSize](int32_t PtrDir) { // XXX: Theoretically LODS could be optimized to // RSI += {-}(RCX * Size) // RAX = [RSI - Size] // But this might violate the case of an application scanning pages for read permission and catching the fault // May or may not matter auto JumpStart = Jump(); // Make sure to start a new block after ending this one auto LoopStart = CreateNewCodeBlockAfter(GetCurrentBlock()); SetJumpTarget(JumpStart, LoopStart); SetCurrentCodeBlock(LoopStart); StartNewBlock(); Ref Counter = LoadGPRRegister(X86State::REG_RCX); // Can we end the block? // We leave if RCX = 0 auto CondJump_ = CondJump(Counter, CondClass::EQ); auto LoopTail = CreateNewCodeBlockAfter(LoopStart); SetFalseJumpTarget(CondJump_, LoopTail); SetCurrentCodeBlock(LoopTail); StartNewBlock(); // Working loop { Ref Src_RSI = LoadGPRRegister(X86State::REG_RSI, AddrSize); Ref Dest_RSI = AppendSegmentOffset(Src_RSI, 0, X86Tables::DecodeFlags::FLAG_DS_PREFIX, true); auto Src = _LoadMemGPRAutoTSO(Size, Dest_RSI, Size); StoreGPRResultWithZExtSemantics(X86State::REG_RAX, Src, Size); Ref TailCounter = LoadGPRRegister(X86State::REG_RCX); Ref TailDest_RSI = LoadGPRRegister(X86State::REG_RSI); // Decrement counter TailCounter = Sub(OpSize::i64Bit, TailCounter, 1); // Store the counter since we don't have phis StoreGPRRegister(X86State::REG_RCX, TailCounter); // Offset the pointer TailDest_RSI = Add(AddrSize, TailDest_RSI, PtrDir * static_cast(IR::OpSizeToSize(Size))); if (Is64BitMode && AddrSize == OpSize::i32Bit) { TailDest_RSI = _Bfe(OpSize::i64Bit, 32, 0, TailDest_RSI); StoreGPRRegister(X86State::REG_RSI, TailDest_RSI); } else { StoreGPRRegister(X86State::REG_RSI, TailDest_RSI, AddrSize); } // Jump back to the start, we have more work to do Jump(LoopStart); } // Make sure to start a new block after ending this one auto LoopEnd = CreateNewCodeBlockAfter(LoopTail); SetTrueJumpTarget(CondJump_, LoopEnd); SetCurrentCodeBlock(LoopEnd); StartNewBlock(); }); } } void OpDispatchBuilder::SCASOp(OpcodeArgs) { if (!Is64BitMode && (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE)) { LogMan::Msg::EFmt("SCASOp: Address size override (0x67) not supported in 32-bit mode (OP: 0x{:04X}).", Op->OP); DecodeFailure = true; return; } const auto Size = OpSizeFromSrc(Op); OpSize AddrSize = GetStringOpSize(Op); const bool Repeat = (Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX)) != 0; if (!Repeat) { Ref Src_RDI = LoadGPRRegister(X86State::REG_RDI, AddrSize); Ref Dest_RDI = AppendSegmentOffset(Src_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); auto Src1 = LoadGPRRegister(X86State::REG_RAX, Size, 0, true); auto Src2 = _LoadMemGPRAutoTSO(Size, Dest_RDI, Size); CalculateFlags_SUB(OpSizeFromSrc(Op), Src1, Src2); Ref TailDest_RDI = OffsetByDir(Src_RDI, IR::OpSizeToSize(Size)); if (Is64BitMode && AddrSize == OpSize::i32Bit) { TailDest_RDI = _Bfe(OpSize::i64Bit, 32, 0, TailDest_RDI); StoreGPRRegister(X86State::REG_RDI, TailDest_RDI); } else { StoreGPRRegister(X86State::REG_RDI, TailDest_RDI, AddrSize); } } else { // Calculate flags early. because end of block CalculateDeferredFlags(); ForeachDirection([this, Op, Size, AddrSize](int32_t Dir) { bool REPE = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX; auto JumpStart = Jump(); // Make sure to start a new block after ending this one auto LoopStart = CreateNewCodeBlockAfter(GetCurrentBlock()); SetJumpTarget(JumpStart, LoopStart); SetCurrentCodeBlock(LoopStart); StartNewBlock(); Ref Counter = LoadGPRRegister(X86State::REG_RCX); // Can we end the block? // We leave if RCX = 0 auto CondJump_ = CondJump(Counter, CondClass::EQ); IRPair InternalCondJump; auto LoopTail = CreateNewCodeBlockAfter(LoopStart); SetFalseJumpTarget(CondJump_, LoopTail); SetCurrentCodeBlock(LoopTail); StartNewBlock(); // Working loop { Ref Src_RDI = LoadGPRRegister(X86State::REG_RDI, AddrSize); Ref Dest_RDI = AppendSegmentOffset(Src_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); auto Src1 = LoadGPRRegister(X86State::REG_RAX, Size, 0, true); auto Src2 = _LoadMemGPRAutoTSO(Size, Dest_RDI, Size); CalculateFlags_SUB(OpSizeFromSrc(Op), Src1, Src2); // Calculate flags early. CalculateDeferredFlags(); Ref TailCounter = LoadGPRRegister(X86State::REG_RCX); Ref Src_RDI_Tail = LoadGPRRegister(X86State::REG_RDI, AddrSize); // Decrement counter TailCounter = Sub(OpSize::i64Bit, TailCounter, 1); // Store the counter since we don't have phis StoreGPRRegister(X86State::REG_RCX, TailCounter); Ref TailDest_RDI = Add(AddrSize, Src_RDI_Tail, Dir * static_cast(IR::OpSizeToSize(Size))); if (Is64BitMode && AddrSize == OpSize::i32Bit) { TailDest_RDI = _Bfe(OpSize::i64Bit, 32, 0, TailDest_RDI); StoreGPRRegister(X86State::REG_RDI, TailDest_RDI); } else { StoreGPRRegister(X86State::REG_RDI, TailDest_RDI, AddrSize); } CalculateDeferredFlags(); InternalCondJump = CondJumpNZCV(REPE ? CondClass::EQ : CondClass::NEQ); // Jump back to the start if we have more work to do SetTrueJumpTarget(InternalCondJump, LoopStart); } // Make sure to start a new block after ending this one auto LoopEnd = CreateNewCodeBlockAfter(LoopTail); SetTrueJumpTarget(CondJump_, LoopEnd); SetFalseJumpTarget(InternalCondJump, LoopEnd); SetCurrentCodeBlock(LoopEnd); StartNewBlock(); }); } } void OpDispatchBuilder::BSWAPOp(OpcodeArgs) { Ref Dest; const auto Size = OpSizeFromSrc(Op); if (Size == OpSize::i16Bit) { // BSWAP of 16bit is undef. ZEN+ causes the lower 16bits to get zero'd Dest = Constant(0); } else { Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, GetGPROpSize(), Op->Flags); Dest = _Rev(Size, Dest); } StoreResultGPR(Op, Dest); } void OpDispatchBuilder::PUSHFOp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); Push(Size, GetPackedRFLAG()); } void OpDispatchBuilder::POPFOp(OpcodeArgs) { const auto Size = OpSizeFromSrc(Op); Ref Src = Pop(Size); // Add back our flag constants // Bit 1 is always 1 // Bit 9 is always 1 because we always have interrupts enabled Src = _Or(OpSize::i64Bit, Src, Constant(0x202)); SetPackedRFLAG(false, Src); auto NewRIP = GetRelocatedPC(Op); ExitFunction(NewRIP, BranchHint::CheckTF); BlockSetRIP = true; } void OpDispatchBuilder::NEGOp(OpcodeArgs) { HandledLock = (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK) != 0; const auto Size = OpSizeFromSrc(Op); auto ZeroConst = Constant(0); if (DestIsLockedMem(Op)) { Ref DestMem = MakeSegmentAddress(Op, Op->Dest); Ref Dest = _AtomicFetchNeg(Size, DestMem); CalculateFlags_SUB(Size, ZeroConst, Dest); } else { Ref Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); Ref Result = CalculateFlags_SUB(Size, ZeroConst, Dest); StoreResultGPR(Op, Result); } } void OpDispatchBuilder::DIVOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); auto Size = OpSizeFromSrc(Op); // This loads the divisor. 32-bit/64-bit paths mask inside the JIT, 8/16 do not. Ref Divisor = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = Size >= OpSize::i32Bit}); if (Size == OpSize::i64Bit && !Is64BitMode) { LogMan::Msg::EFmt("Doesn't exist in 32bit mode"); DecodeFailure = true; return; } Ref Quotient = _AllocateGPR(true); Ref Remainder = _AllocateGPR(true); if (Size == OpSize::i8Bit) { Ref Src1 = LoadGPRRegister(X86State::REG_RAX, OpSize::i16Bit); _UDiv(OpSize::i16Bit, Src1, Invalid(), Divisor, Quotient, Remainder); // AX[15:0] = concat auto ResultAX = _Bfi(GPRSize, 8, 8, Quotient, Remainder); StoreGPRRegister(X86State::REG_RAX, ResultAX, OpSize::i16Bit); } else { Ref Src1 = LoadGPRRegister(X86State::REG_RAX); Ref Src2 = LoadGPRRegister(X86State::REG_RDX); _UDiv(Size, Src1, Src2, Divisor, Quotient, Remainder); if (Size == OpSize::i32Bit) { Quotient = _Bfe(OpSize::i32Bit, IR::OpSizeAsBits(Size), 0, Quotient); Remainder = _Bfe(OpSize::i32Bit, IR::OpSizeAsBits(Size), 0, Remainder); Size = OpSize::iInvalid; } StoreGPRRegister(X86State::REG_RAX, Quotient, Size); StoreGPRRegister(X86State::REG_RDX, Remainder, Size); } } void OpDispatchBuilder::IDIVOp(OpcodeArgs) { // This loads the divisor Ref Divisor = LoadSourceGPR(Op, Op->Dest, Op->Flags); const auto GPRSize = GetGPROpSize(); auto Size = OpSizeFromSrc(Op); if (Size == OpSize::i64Bit && !Is64BitMode) { LogMan::Msg::EFmt("Doesn't exist in 32bit mode"); DecodeFailure = true; return; } Ref Quotient = _AllocateGPR(true); Ref Remainder = _AllocateGPR(true); if (Size == OpSize::i8Bit) { Ref Src1 = LoadGPRRegister(X86State::REG_RAX); Src1 = _Sbfe(OpSize::i64Bit, 16, 0, Src1); Divisor = _Sbfe(OpSize::i64Bit, 8, 0, Divisor); _Div(OpSize::i64Bit, Src1, Invalid(), Divisor, Quotient, Remainder); // AX[15:0] = concat auto ResultAX = _Bfi(GPRSize, 8, 8, Quotient, Remainder); StoreGPRRegister(X86State::REG_RAX, ResultAX, OpSize::i16Bit); } else { Ref Src1 = LoadGPRRegister(X86State::REG_RAX); Ref Src2 = LoadGPRRegister(X86State::REG_RDX); _Div(Size, Src1, Src2, Divisor, Quotient, Remainder); if (Size == OpSize::i32Bit) { Quotient = _Bfe(OpSize::i32Bit, IR::OpSizeAsBits(Size), 0, Quotient); Remainder = _Bfe(OpSize::i32Bit, IR::OpSizeAsBits(Size), 0, Remainder); Size = OpSize::iInvalid; } StoreGPRRegister(X86State::REG_RAX, Quotient, Size); StoreGPRRegister(X86State::REG_RDX, Remainder, Size); } } void OpDispatchBuilder::BSFOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); const auto DstSize = OpSizeFromDst(Op) == OpSize::i16Bit ? OpSize::i16Bit : GPRSize; Ref Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, DstSize, Op->Flags, {.AllowUpperGarbage = true}); Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); // Find the LSB of this source auto Result = _FindLSB(OpSizeFromSrc(Op), Src); // OF, SF, AF, PF, CF all undefined // ZF is set to 1 if the source was zero SetZ_InvalidateNCV(OpSizeFromSrc(Op), Src); // If Src was zero then the destination doesn't get modified. // // Although Intel does not guarantee that semantic, AMD does and Intel // hardware satisfies it. We provide the stronger AMD behaviour as // applications might rely on that in the wild. auto SelectOp = NZCVSelect(GPRSize, CondClass::EQ, Dest, Result); StoreResultGPR_WithOpSize(Op, Op->Dest, SelectOp, DstSize); } void OpDispatchBuilder::BSROp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); const auto DstSize = OpSizeFromDst(Op) == OpSize::i16Bit ? OpSize::i16Bit : GPRSize; Ref Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, DstSize, Op->Flags, {.AllowUpperGarbage = true}); Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); // Find the MSB of this source auto Result = _FindMSB(OpSizeFromSrc(Op), Src); // OF, SF, AF, PF, CF all undefined // ZF is set to 1 if the source was zero SetZ_InvalidateNCV(OpSizeFromSrc(Op), Src); // If Src was zero then the destination doesn't get modified auto SelectOp = NZCVSelect(GPRSize, CondClass::EQ, Dest, Result); StoreResultGPR_WithOpSize(Op, Op->Dest, SelectOp, DstSize); } void OpDispatchBuilder::CMPXCHGOp(OpcodeArgs) { // CMPXCHG ModRM, reg, {RAX} // MemData = *ModRM.dest // if (RAX == MemData) // modRM.dest = reg; // ZF = 1 // else // ZF = 0 // RAX = MemData // // CASL Xs, Xt, Xn // MemData = *Xn // if (MemData == Xs) // *Xn = Xt // Xs = MemData const auto GPRSize = GetGPROpSize(); auto Size = OpSizeFromSrc(Op); if (Op->Dest.IsGPR()) { // This is our source register Ref Src2 = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); Ref Src3 = LoadGPRRegister(X86State::REG_RAX); // If the destination is also the accumulator, we get some algebraic // simplifications. Not sure if this is actually hit but it's in // InstCountCI. bool Trivial = Op->Dest.Data.GPR.GPR == X86State::REG_RAX && !Op->Dest.IsGPRDirect() && !Op->Dest.Data.GPR.HighBits; Ref Src1 {}; Ref Src1Lower {}; if (GPRSize == OpSize::i64Bit && Size == OpSize::i32Bit) { Src1 = LoadSourceGPR_WithOpSize(Op, Op->Dest, GPRSize, Op->Flags, {.AllowUpperGarbage = true}); Src1Lower = Trivial ? Src1 : _Bfe(GPRSize, IR::OpSizeAsBits(Size), 0, Src1); } else { Src1 = LoadSourceGPR_WithOpSize(Op, Op->Dest, Size, Op->Flags, {.AllowUpperGarbage = true}); Src1Lower = Src1; } // Compare RAX with the destination, setting flags accordingly. CalculateFlags_SUB(OpSizeFromSrc(Op), Src3, Src1Lower); CalculateDeferredFlags(); if (!Trivial) { if (GPRSize == OpSize::i64Bit && Size == OpSize::i32Bit) { // This allows us to only hit the ZEXT case on failure Ref RAXResult = NZCVSelect(OpSize::i64Bit, CondClass::EQ, Src3, Src1Lower); StoreGPRRegister(X86State::REG_RAX, RAXResult); } else { StoreGPRRegister(X86State::REG_RAX, Src1Lower, Size); } } // Op1 = RAX == Op1 ? Op2 : Op1 // If they match then set the rm operand to the input // else don't set the rm operand Ref Src2Lower = Src2; if (GPRSize == OpSize::i64Bit && Size == OpSize::i32Bit) { Src2Lower = _Bfe(GPRSize, IR::OpSizeAsBits(Size), 0, Src2); } Ref DestResult = Trivial ? Src2Lower : NZCVSelect(OpSize::i64Bit, CondClass::EQ, Src2Lower, Src1); // Store in to GPR Dest if (GPRSize == OpSize::i64Bit && Size == OpSize::i32Bit) { StoreResultGPR_WithOpSize(Op, Op->Dest, DestResult, GPRSize); } else { StoreResultGPR(Op, DestResult); } } else { Ref Src2 = LoadSourceGPR(Op, Op->Src[0], Op->Flags); HandledLock = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK; auto Src3 = LoadGPRRegister(X86State::REG_RAX); auto Src3Lower = _Bfe(OpSize::i64Bit, OpSizeAsBits(Size), 0, Src3); // If this is a memory location then we want the pointer to it Ref Src1 = MakeSegmentAddress(Op, Op->Dest); // DataSrc = *Src1 // if (DataSrc == Src3) { *Src1 == Src2; } Src2 = DataSrc // This will write to memory! Careful! // Third operand must be a calculated guest memory address Ref CASResult = _CAS(Size, Src3, Src2, Src1); Ref RAXResult = CASResult; CalculateFlags_SUB(OpSizeFromSrc(Op), Src3Lower, CASResult); CalculateDeferredFlags(); if (GPRSize == OpSize::i64Bit && Size == OpSize::i32Bit) { // This allows us to only hit the ZEXT case on failure RAXResult = _NZCVSelect(OpSize::i64Bit, CondClass::EQ, Src3, CASResult); Size = OpSize::i64Bit; } // RAX gets the result of the CAS op StoreGPRRegister(X86State::REG_RAX, RAXResult, Size); } } void OpDispatchBuilder::CMPXCHGPairOp(OpcodeArgs) { // Calculate flags early. CalculateDeferredFlags(); // REX.W used to determine if it is 16byte or 8byte // Unlike CMPXCHG, the destination can only be a memory location const auto Size = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REX_WIDENING ? OpSize::i64Bit : OpSize::i32Bit; HandledLock = (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK) != 0; // If this is a memory location then we want the pointer to it Ref Src1 = MakeSegmentAddress(Op, Op->Dest); // Load the full 64-bit registers, all the users ignore the upper 32-bits for // 32-bit only cmpxchg. This saves some zero extension. Ref Expected_Lower = LoadGPRRegister(X86State::REG_RAX); Ref Expected_Upper = LoadGPRRegister(X86State::REG_RDX); Ref Desired_Lower = LoadGPRRegister(X86State::REG_RBX); Ref Desired_Upper = LoadGPRRegister(X86State::REG_RCX); // ssa0 = Expected // ssa1 = Desired // ssa2 = MemoryLocation // DataSrc = *MemSrc // if (DataSrc == Expected) { *MemSrc == Desired; } Expected = DataSrc // This will write to memory! Careful! // Third operand must be a calculated guest memory address Ref Result_Lower = _AllocateGPR(true); Ref Result_Upper = _AllocateGPRAfter(Result_Lower); _CASPair(Size, Expected_Lower, Expected_Upper, Desired_Lower, Desired_Upper, Src1, Result_Lower, Result_Upper); HandleNZCV_RMW(); _CmpPairZ(Size, Result_Lower, Result_Upper, Expected_Lower, Expected_Upper); CalculateDeferredFlags(); auto UpdateIfNotZF = [this](auto Reg, auto Value) { // Always use 64-bit csel to preserve existing upper bits. If we have a // 32-bit cmpxchg in a 64-bit context, Value will be zeroed in upper bits. StoreGPRRegister(Reg, NZCVSelect(OpSize::i64Bit, CondClass::NEQ, Value, LoadGPRRegister(Reg))); }; UpdateIfNotZF(X86State::REG_RAX, Result_Lower); UpdateIfNotZF(X86State::REG_RDX, Result_Upper); } void OpDispatchBuilder::CreateJumpBlocks(const fextl::vector* Blocks) { Ref PrevCodeBlock {}; for (auto& Target : *Blocks) { auto CodeNode = CreateCodeNode(Target.IsEntryPoint, Target.Entry - Entry); JumpTargets.try_emplace(Target.Entry, JumpTargetInfo {CodeNode, false, Target.IsEntryPoint}); if (PrevCodeBlock) { LinkCodeBlocks(PrevCodeBlock, CodeNode); } PrevCodeBlock = CodeNode; } } void OpDispatchBuilder::BeginFunction(uint64_t RIP, const fextl::vector* Blocks, uint32_t NumInstructions, bool _Is64BitMode, bool MonoBackpatcherBlock) { Entry = RIP; Is64BitMode = _Is64BitMode; LOGMAN_THROW_A_FMT(Is64BitMode == CTX->Config.Is64BitMode, "Expected operating mode to not change at runtime!"); IsMonoBackpatcherBlock = MonoBackpatcherBlock; auto IRHeader = _IRHeader(InvalidNode, RIP, 0, NumInstructions, 0, 0); CreateJumpBlocks(Blocks); auto Block = GetNewJumpBlock(RIP); SetCurrentCodeBlock(Block); IRHeader.first->Blocks = Block->Wrapped(DualListData.ListBegin()); CurrentHeader = IRHeader.first; } void OpDispatchBuilder::Finalize() { // This usually doesn't emit any IR but in the case of hitting the block instruction limit it will FlushRegisterCache(); const auto GPRSize = GetGPROpSize(); // Node 0 is invalid node Ref RealNode = reinterpret_cast(GetNode(1)); const FEXCore::IR::IROp_Header* IROp = RealNode->Op(DualListData.DataBegin()); LOGMAN_THROW_A_FMT(IROp->Op == OP_IRHEADER, "First op in function must be our header"); // Let's walk the jump blocks and see if we have handled every block target for (auto& Handler : JumpTargets) { if (Handler.second.HaveEmitted) { continue; } // We haven't emitted. Dump out to the dispatcher SetCurrentCodeBlock(Handler.second.BlockEntry); ExitFunction(_InlineEntrypointOffset(GPRSize, Handler.first - Entry)); } } uint8_t OpDispatchBuilder::GetDstSize(X86Tables::DecodedOp Op) const { const uint32_t DstSizeFlag = X86Tables::DecodeFlags::GetSizeDstFlags(Op->Flags); LOGMAN_THROW_A_FMT(DstSizeFlag != 0 && DstSizeFlag != X86Tables::DecodeFlags::SIZE_MASK, "Invalid destination size for op"); return 1u << (DstSizeFlag - 1); } uint8_t OpDispatchBuilder::GetSrcSize(X86Tables::DecodedOp Op) const { const uint32_t SrcSizeFlag = X86Tables::DecodeFlags::GetSizeSrcFlags(Op->Flags); LOGMAN_THROW_A_FMT(SrcSizeFlag != 0 && SrcSizeFlag != X86Tables::DecodeFlags::SIZE_MASK, "Invalid destination size for op"); return 1u << (SrcSizeFlag - 1); } uint32_t OpDispatchBuilder::GetSrcBitSize(X86Tables::DecodedOp Op) const { return GetSrcSize(Op) * 8; } uint32_t OpDispatchBuilder::GetDstBitSize(X86Tables::DecodedOp Op) const { return GetDstSize(Op) * 8; } Ref OpDispatchBuilder::GetSegment(uint32_t Flags, uint32_t DefaultPrefix, bool Override) { const auto GPRSize = GetGPROpSize(); uint32_t Prefix = Flags & FEXCore::X86Tables::DecodeFlags::FLAG_SEGMENTS; if (Is64BitMode) { if (Prefix == FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX) { return _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, fs_cached)); } else if (Prefix == FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX) { return _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, gs_cached)); } // If there was any other segment in 64bit then it is ignored } else { if (Prefix == FEXCore::X86Tables::DecodeFlags::FLAG_NO_PREFIX || Override) { // If there was no prefix then use the default one if available // Or the argument only uses a specific prefix (with override set) Prefix = DefaultPrefix; } // With the segment register optimization we store the GDT bases directly in the segment register to remove indexed loads Ref SegmentResult {}; switch (Prefix) { [[likely]] case FEXCore::X86Tables::DecodeFlags::FLAG_NO_PREFIX: return nullptr; case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX: SegmentResult = _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, es_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX: SegmentResult = _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, cs_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX: SegmentResult = _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, ss_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX: SegmentResult = _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, ds_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX: SegmentResult = _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, fs_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX: SegmentResult = _LoadContextGPR(GPRSize, offsetof(FEXCore::Core::CPUState, gs_cached)); break; default: FEX_UNREACHABLE; } CheckLegacySegmentRead(SegmentResult, Prefix); return SegmentResult; } return nullptr; } Ref OpDispatchBuilder::AppendSegmentOffset(Ref Value, uint32_t Flags, uint32_t DefaultPrefix, bool Override) { auto Segment = GetSegment(Flags, DefaultPrefix, Override); if (Segment) { Value = Add(std::max(OpSize::i32Bit, std::max(GetOpSize(Value), GetOpSize(Segment))), Value, Segment); } return Value; } void OpDispatchBuilder::CheckLegacySegmentRead(Ref NewNode, uint32_t SegmentReg) { #ifndef FEX_DISABLE_TELEMETRY if (SegmentReg == FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX || SegmentReg == FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX) { // FS and GS segments aren't considered legacy. return; } if (!(SegmentsNeedReadCheck & SegmentReg)) { // If the block has done multiple reads of a segment register then skip redundant read checks. // Segment write will cause another read check. return; } if (CTX->Config.DisableTelemetry()) { // Telemetry disabled at runtime. return; } FEXCore::Telemetry::TelemetryType TelemIndex {}; switch (SegmentReg) { case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX: TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_USES_32BIT_SEGMENT_ES; SegmentsNeedReadCheck &= ~FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX; break; case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX: TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_USES_32BIT_SEGMENT_CS; SegmentsNeedReadCheck &= ~FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX; break; case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX: TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_USES_32BIT_SEGMENT_SS; SegmentsNeedReadCheck &= ~FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX; break; case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX: TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_USES_32BIT_SEGMENT_DS; SegmentsNeedReadCheck &= ~FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX; break; default: FEX_UNREACHABLE; } // Will set the telemetry value if NewNode is != 0 _TelemetrySetValue(NewNode, TelemIndex); // Telemetry will dirty flags, and user code does not expect LoadSource to clobber flags, fix that up here as this is an edge case. CalculateDeferredFlags(); #endif } void OpDispatchBuilder::CheckLegacySegmentWrite(Ref NewNode, uint32_t SegmentReg) { #ifndef FEX_DISABLE_TELEMETRY if (SegmentReg == FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX || SegmentReg == FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX) { // FS and GS segments aren't considered legacy. return; } if (CTX->Config.DisableTelemetry()) { // Telemetry disabled at runtime. return; } FEXCore::Telemetry::TelemetryType TelemIndex {}; switch (SegmentReg) { case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX: TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_ES; SegmentsNeedReadCheck |= FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX; break; case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX: TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_CS; SegmentsNeedReadCheck |= FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX; break; case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX: TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_SS; SegmentsNeedReadCheck |= FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX; break; case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX: TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_DS; SegmentsNeedReadCheck |= FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX; break; default: FEX_UNREACHABLE; } // Will set the telemetry value if NewNode is != 0 _TelemetrySetValue(NewNode, TelemIndex); // Telemetry will dirty flags, and user code does not expect LoadSource to clobber flags, fix that up here as this is an edge case. CalculateDeferredFlags(); #endif } void OpDispatchBuilder::UpdatePrefixFromSegment(Ref Segment, uint32_t SegmentReg) { // Use BFE to extract the selector index in bits [15,3] of the segment register. // In some cases the upper 16-bits of the 32-bit GPR contain garbage to ignore. auto GDT = _Bfe(OpSize::i32Bit, 1, 2, Segment); // Fun quirk, if we mask the selector then it is premultiplied by 8 which we need to do for accessing anyway. auto SegmentOffset = _And(OpSize::i32Bit, Segment, _Constant(0xfff8)); Ref SegmentBase = _LoadContextGPRIndexed(GDT, OpSize::i64Bit, offsetof(FEXCore::Core::CPUState, segment_arrays[0]), 8); Ref NewSegment = _LoadMemGPR(OpSize::i64Bit, SegmentBase, SegmentOffset, OpSize::i8Bit, MemOffsetType::UXTW, 1); CheckLegacySegmentWrite(NewSegment, SegmentReg); // Extract the 32-bit base from the GDT segment. auto Upper32 = _Lshr(OpSize::i64Bit, NewSegment, _Constant(32)); auto Masked = _And(OpSize::i32Bit, Upper32, _Constant(0xFF00'0000)); Ref Merged = _Orlshr(OpSize::i32Bit, Masked, NewSegment, 16); NewSegment = _Bfi(OpSize::i32Bit, 8, 16, Merged, Upper32); switch (SegmentReg) { case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX: _StoreContextGPR(OpSize::i32Bit, NewSegment, offsetof(FEXCore::Core::CPUState, es_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX: _StoreContextGPR(OpSize::i32Bit, NewSegment, offsetof(FEXCore::Core::CPUState, cs_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX: _StoreContextGPR(OpSize::i32Bit, NewSegment, offsetof(FEXCore::Core::CPUState, ss_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX: _StoreContextGPR(OpSize::i32Bit, NewSegment, offsetof(FEXCore::Core::CPUState, ds_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX: _StoreContextGPR(OpSize::i32Bit, NewSegment, offsetof(FEXCore::Core::CPUState, fs_cached)); break; case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX: _StoreContextGPR(OpSize::i32Bit, NewSegment, offsetof(FEXCore::Core::CPUState, gs_cached)); break; default: break; // Do nothing } } uint64_t OpDispatchBuilder::CalcAddress(const X86Tables::DecodedOp& Op, const X86Tables::DecodedOperand& Operand, bool IsLoad) { if constexpr (!Context::BLOCK_DEBUGGING) { LOGMAN_MSG_A_FMT("Tried to calculate address without block debugging enabled!"); FEX_UNREACHABLE; } const auto GPRSize = GetGPROpSize(); const auto GPRMask = GPRSize == OpSize::i64Bit ? ~0ULL : ~0U; // This makes the assumption that InternalThreadState is synchronized at the point of call! uint64_t Ptr {}; if (Operand.IsLiteral()) { Ptr = Operand.Literal(); if (Operand.Data.Literal.Size != 8 && IsLoad) { // zero extend uint64_t width = Operand.Data.Literal.Size * 8; Ptr &= ((1ULL << width) - 1); } } else if (Operand.IsGPR()) { // Not a memory source. return ~0ULL; } else if (Operand.IsGPRDirect()) { Ptr = Thread->CurrentFrame->State.gregs[Operand.Data.GPR.GPR] & GPRMask; } else if (Operand.IsGPRIndirect() || Operand.IsGPRIndirectRelocation()) { Ptr = Thread->CurrentFrame->State.gregs[Operand.Data.GPR.GPR] & GPRMask; Ptr += static_cast(Operand.Data.GPRIndirect.Displacement); } else if (Operand.IsRIPRelative() || Operand.IsRIPRelativeRelocation()) { // 64-bit is RIP relative, while 32-bit is absolute. if (Is64BitMode) { Ptr = Op->PC + Op->InstSize + static_cast(Operand.Data.RIPLiteral.Value) - Entry; } else { Ptr = Operand.Data.RIPLiteral.Value; } } else if (Operand.IsSIB() || Operand.IsSIBRelocation()) { const bool IsVSIB = IsLoad && ((Op->Flags & X86Tables::DecodeFlags::FLAG_VSIB_BYTE) != 0); if (IsVSIB) { // TODO: Unhandled. return ~0ULL; } if (Operand.Data.SIB.Base != FEXCore::X86State::REG_INVALID) { Ptr = Thread->CurrentFrame->State.gregs[Operand.Data.SIB.Base] & GPRMask; } if (Operand.Data.SIB.Index != FEXCore::X86State::REG_INVALID) { Ptr += (Thread->CurrentFrame->State.gregs[Operand.Data.SIB.Index] * Operand.Data.SIB.Scale) & GPRMask; } Ptr += static_cast(Operand.Data.SIB.Offset); } auto AppendSegment = [&](uint64_t Ptr, uint32_t Flags, uint32_t DefaultPrefix = FEXCore::X86Tables::DecodeFlags::FLAG_NO_PREFIX, bool Override = false) -> uint64_t { uint32_t Prefix = Flags & FEXCore::X86Tables::DecodeFlags::FLAG_SEGMENTS; if (Is64BitMode) { if (Prefix == FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX) { return Ptr + Thread->CurrentFrame->State.fs_cached; } else if (Prefix == FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX) { return Ptr + Thread->CurrentFrame->State.gs_cached; } // If there was any other segment in 64bit then it is ignored } else { if (Prefix == FEXCore::X86Tables::DecodeFlags::FLAG_NO_PREFIX || Override) { // If there was no prefix then use the default one if available // Or the argument only uses a specific prefix (with override set) Prefix = DefaultPrefix; } // With the segment register optimization we store the GDT bases directly in the segment register to remove indexed loads switch (Prefix) { [[likely]] case FEXCore::X86Tables::DecodeFlags::FLAG_NO_PREFIX: return Ptr; case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX: return Ptr + Thread->CurrentFrame->State.es_cached; case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX: return Ptr + Thread->CurrentFrame->State.cs_cached; case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX: return Ptr + Thread->CurrentFrame->State.ss_cached; case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX: return Ptr + Thread->CurrentFrame->State.ds_cached; case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX: return Ptr + Thread->CurrentFrame->State.fs_cached; case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX: return Ptr + Thread->CurrentFrame->State.gs_cached; default: FEX_UNREACHABLE; } } return Ptr; }; return AppendSegment(Ptr, Op->Flags); }; AddressMode OpDispatchBuilder::DecodeAddress(const X86Tables::DecodedOp& Op, const X86Tables::DecodedOperand& Operand, MemoryAccessType AccessType, bool IsLoad) { const auto GPRSize = GetGPROpSize(); AddressMode A {}; A.Segment = GetSegment(Op->Flags); A.AddrSize = (Op->Flags & X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) != 0 ? (GPRSize >> 1) : GPRSize; A.NonTSO = AccessType == MemoryAccessType::NONTSO || AccessType == MemoryAccessType::STREAM; if (Operand.IsLiteral()) { A.Offset = Operand.Literal(); if (Operand.Data.Literal.Size != 8 && IsLoad) { // zero extend uint64_t width = Operand.Data.Literal.Size * 8; A.Offset &= ((1ULL << width) - 1); } } else if (Operand.IsGPR()) { // Not an address, let the caller deal with it A.AddrSize = GPRSize; } else if (Operand.IsGPRDirect()) { A.Base = LoadGPRRegister(Operand.Data.GPR.GPR, GPRSize); A.NonTSO |= IsNonTSOReg(AccessType, Operand.Data.GPR.GPR); } else if (Operand.IsGPRIndirect() || Operand.IsGPRIndirectRelocation()) { A.Base = LoadGPRRegister(Operand.Data.GPRIndirect.GPR, GPRSize); if (Operand.IsGPRIndirectRelocation()) { A.Base = Add(GPRSize, _EntrypointOffset(GPRSize, Operand.Data.GPRIndirect.Displacement), A.Base); } else { if (Operand.Data.GPRIndirect.PatchableDisp) { A.Base = Add(GPRSize, A.Base, _PatchableGuestData(OpSize::i64Bit, static_cast(Operand.Data.GPRIndirect.Displacement), Op->PC + Operand.Data.GPRIndirect.DispOffset, 4)); } else { A.Offset = static_cast(Operand.Data.GPRIndirect.Displacement); } } A.NonTSO |= IsNonTSOReg(AccessType, Operand.Data.GPRIndirect.GPR); } else if (Operand.IsRIPRelative() || Operand.IsRIPRelativeRelocation()) { if (Is64BitMode) { if (Operand.IsRIPRelative() && Operand.Data.RIPLiteral.PatchableDisp) { A.Base = _PatchableGuestRIP(OpSize::i64Bit, Op->PC + Op->InstSize + static_cast(Operand.Data.RIPLiteral.Value), Op->PC + Operand.Data.RIPLiteral.DispOffset, 4); } else { A.Base = GetRelocatedPC(Op, static_cast(Operand.Data.RIPLiteral.Value)); } } else { // 32bit this isn't RIP relative but instead absolute if (Operand.IsRIPRelativeRelocation()) { A.Base = _EntrypointOffset(GPRSize, Operand.Data.RIPLiteral.Value); } else { A.Offset = Operand.Data.RIPLiteral.Value; } } } else if (Operand.IsSIB() || Operand.IsSIBRelocation()) { const bool IsVSIB = IsLoad && ((Op->Flags & X86Tables::DecodeFlags::FLAG_VSIB_BYTE) != 0); if (Operand.Data.SIB.Base != FEXCore::X86State::REG_INVALID) { A.Base = LoadGPRRegister(Operand.Data.SIB.Base, GPRSize); } // NOTE: VSIB cannot have the index * scale portion calculated ahead of time, // since the index in this case is a vector. So, we can't just apply the scale // to it, since this needs to be applied to each element in the index register // after said element has been sign extended. So, we pass this through for the // instruction implementation to handle. // // What we do handle though, is the applying the displacement value to // the base register (if a base register is provided), since this is a // part of the address calculation that can be done ahead of time. if (!IsVSIB && Operand.Data.SIB.Index != FEXCore::X86State::REG_INVALID) { A.Index = LoadGPRRegister(Operand.Data.SIB.Index, GPRSize); A.IndexScale = Operand.Data.SIB.Scale; } if (Operand.IsSIBRelocation()) { auto EPOffset = _EntrypointOffset(GPRSize, Operand.Data.SIB.Offset); if (A.Base) { A.Base = Add(GPRSize, EPOffset, A.Base); } else { A.Base = EPOffset; } } else if (Operand.Data.SIB.PatchableDisp) { Ref PatchedDisp = _PatchableGuestData(OpSize::i64Bit, static_cast(Operand.Data.SIB.Offset), Op->PC + Operand.Data.SIB.DispOffset, 4); if (A.Base) { A.Base = Add(OpSize::i64Bit, A.Base, PatchedDisp); } else { A.Base = PatchedDisp; } } else { A.Offset = static_cast(Operand.Data.SIB.Offset); } A.NonTSO |= IsNonTSOReg(AccessType, Operand.Data.SIB.Base) || IsNonTSOReg(AccessType, Operand.Data.SIB.Index); } else if (Operand.IsLiteralRelocation()) { A.Base = _EntrypointOffset(GPRSize, Operand.Data.LiteralRelocation.EntrypointOffset); } else if (Operand.IsLiteralPatchable()) { A.Base = _PatchableGuestData(OpSize::i64Bit, Operand.Data.LiteralPatchable.Value, Op->PC + Operand.Data.LiteralPatchable.FieldOffset, static_cast(Operand.Data.LiteralPatchable.Width)); } else { LOGMAN_MSG_A_FMT("Unknown Src Type: {}\n", Operand.Type); } return A; } Ref OpDispatchBuilder::LoadSource_WithOpSize(RegClass Class, const X86Tables::DecodedOp& Op, const X86Tables::DecodedOperand& Operand, IR::OpSize OpSize, uint32_t Flags, const LoadSourceOptions& Options) { auto [Align, LoadData, ForceLoad, AccessType, AllowUpperGarbage] = Options; AddressMode A = DecodeAddress(Op, Operand, AccessType, true /* IsLoad */); Ref Result {}; if (Operand.IsGPR()) { const auto gpr = Operand.Data.GPR.GPR; const auto highIndex = Operand.Data.GPR.HighBits ? 1 : 0; if (gpr >= FEXCore::X86State::REG_MM_0) { LOGMAN_THROW_A_FMT(OpSize == OpSize::i64Bit, "full"); if (MMXState != MMXState_MMX) { ChgStateX87_MMX(); } A.Base = LoadContext(OpSize::i64Bit, MM0Index + gpr - FEXCore::X86State::REG_MM_0); } else if (gpr >= FEXCore::X86State::REG_XMM_0) { const auto gprIndex = gpr - X86State::REG_XMM_0; // Load the full register size if it is a XMM register source. A.Base = LoadXMMRegister(gprIndex); // Now extract the subregister if it was a partial load /smaller/ than SSE size // TODO: Instead of doing the VMov implicitly on load, hunt down all use cases that require partial loads and do it after load. // We don't have information here to know if the operation needs zero upper bits or can contain data. if (!AllowUpperGarbage && OpSize < OpSize::i128Bit) { A.Base = _VMov(OpSize, A.Base); } } else { A.Base = LoadGPRRegister(gpr, OpSize, highIndex ? 8 : 0, AllowUpperGarbage); } } const bool ShouldLoad = (IsOperandMem(Operand, true) && LoadData) || ForceLoad; if (ShouldLoad) { if (OpSize == OpSize::f80Bit) { Ref MemSrc = LoadEffectiveAddress(this, A, GetGPROpSize(), true); if (CTX->HostFeatures.SupportsSVE()) { Result = _LoadMemX87SVEOptPredicate(OpSize::i128Bit, OpSize::i16Bit, MemSrc); } else { // For X87 extended doubles, Split the load. auto Res = _LoadMem(Class, OpSize::i64Bit, MemSrc, Align == OpSize::iInvalid ? OpSize : Align); Result = _VLoadVectorElement(OpSize::i128Bit, OpSize::i16Bit, Res, 4, Add(OpSize::i64Bit, MemSrc, 8)); } } else { Result = _LoadMemAutoTSO(Class, OpSize, A, Align == OpSize::iInvalid ? OpSize : Align); } } else { Result = LoadEffectiveAddress(this, A, GetGPROpSize(), false, AllowUpperGarbage); } if constexpr (Context::BLOCK_DEBUGGING) { if (ShouldLoad && CTX->BlockDebuggerTracker.IsSingleStepTarget(Entry)) { uint64_t Ptr = CalcAddress(Op, Operand, true); if (CTX->BlockDebuggerTracker.ContainsReadWatchPoint(Ptr, OpSizeToSize(OpSize))) { // It's up to the developer if they want more advanced debugging logic here. LogMan::Msg::IFmt("Entrypoint 0x{:x} will hit read watch: [0x{:x}, 0x{:x})", Entry, Ptr, Ptr + OpSizeToSize(OpSize)); } } } return Result; } Ref OpDispatchBuilder::LoadGPRRegister(uint32_t GPR, IR::OpSize Size, uint8_t Offset, bool AllowUpperGarbage) { const auto GPRSize = GetGPROpSize(); if (Size == OpSize::iInvalid) { Size = GPRSize; } Ref Reg = LoadGPR(GPR); if ((!AllowUpperGarbage && (Size != GPRSize)) || Offset != 0) { // Extract the subregister if requested. const auto OpSize = std::max(OpSize::i32Bit, Size); if (AllowUpperGarbage) { Reg = _Lshr(OpSize, Reg, Constant(Offset)); } else { Reg = _Bfe(OpSize, IR::OpSizeAsBits(Size), Offset, Reg); } } return Reg; } void OpDispatchBuilder::StoreGPRRegister(uint32_t GPR, const Ref Src, IR::OpSize Size, uint8_t Offset) { const auto GPRSize = GetGPROpSize(); if (Size == OpSize::iInvalid) { Size = GPRSize; } Ref Reg = Src; if (Size != GPRSize || Offset != 0) { // Need to do an insert if not automatic size or zero offset. Reg = ARef(Reg).BfiInto(LoadGPRRegister(GPR), Offset, IR::OpSizeAsBits(Size)); } StoreRegister(GPR, false, Reg); } void OpDispatchBuilder::StoreXMMRegister(uint32_t XMM, const Ref Src) { StoreRegister(XMM, true, Src); } Ref OpDispatchBuilder::LoadSource(RegClass Class, const X86Tables::DecodedOp& Op, const X86Tables::DecodedOperand& Operand, uint32_t Flags, const LoadSourceOptions& Options) { const auto OpSize = OpSizeFromSrc(Op); return LoadSource_WithOpSize(Class, Op, Operand, OpSize, Flags, Options); } void OpDispatchBuilder::StoreResult_WithOpSize(RegClass Class, FEXCore::X86Tables::DecodedOp Op, const X86Tables::DecodedOperand& Operand, Ref Src, IR::OpSize OpSize, IR::OpSize Align, MemoryAccessType AccessType) { if (Operand.IsGPR()) { // 8Bit and 16bit destination types store their result without effecting the upper bits // 32bit ops ZEXT the result to 64bit const auto GPRSize = GetGPROpSize(); const auto gpr = Operand.Data.GPR.GPR; if (gpr >= FEXCore::X86State::REG_MM_0) { LOGMAN_THROW_A_FMT(OpSize == OpSize::i64Bit, "full"); LOGMAN_THROW_A_FMT(Class == RegClass::FPR, "MMX is floaty"); if (MMXState != MMXState_MMX) { ChgStateX87_MMX(); } uint8_t Index = MM0Index + gpr - FEXCore::X86State::REG_MM_0; StoreContext(Index, Src); RegCache.Partial |= (1ull << (uint64_t)Index); } else if (gpr >= FEXCore::X86State::REG_XMM_0) { const auto gprIndex = gpr - X86State::REG_XMM_0; const auto VectorSize = GetGuestVectorLength(); auto Result = Src; if (OpSize != VectorSize) { // Partial writes can come from FPRs. // TODO: Fix the instructions doing partial writes rather than dealing with it here. LOGMAN_THROW_A_FMT(Class != RegClass::GPR, "Partial writes from GPR not allowed. Instruction: {}", Op->TableInfo->Name); // XMM-size is handled in implementations. if (VectorSize != OpSize::i256Bit || OpSize != OpSize::i128Bit) { auto SrcVector = LoadXMMRegister(gprIndex); Result = _VInsElement(VectorSize, OpSize, 0, 0, SrcVector, Src); } } StoreXMMRegister(gprIndex, Result); } else { if (GPRSize == OpSize::i64Bit && OpSize == OpSize::i32Bit) { // If the Source IR op is 64 bits, we need to zext the upper bits // For all other sizes, the upper bits are guaranteed to already be zero Ref Value = GetOpSize(Src) == OpSize::i64Bit ? ARef(Src).Bfe(0, 32).Ref() : Src; StoreGPRRegister(gpr, Value, GPRSize); LOGMAN_THROW_A_FMT(!Operand.Data.GPR.HighBits, "Can't handle 32bit store to high 8bit register"); } else { LOGMAN_THROW_A_FMT(!(GPRSize == OpSize::i32Bit && OpSize > OpSize::i32Bit), "Oops had a {} GPR load", OpSize); if (GPRSize != OpSize) { // if the GPR isn't the full size then we need to insert. // eg: // mov al, 2 ; Move in to lower 8-bits. // mov ah, 2 ; Move in to upper 8-bits of 16-bit reg. // mov ax, 2 ; Move in to lower 16-bits of reg. StoreGPRRegister(gpr, Src, OpSize, Operand.Data.GPR.HighBits * 8); } else { StoreGPRRegister(gpr, Src, std::min(GPRSize, OpSize)); } } } return; } AddressMode A = DecodeAddress(Op, Operand, AccessType, false /* IsLoad */); if (OpSize == OpSize::f80Bit) { Ref MemStoreDst = LoadEffectiveAddress(this, A, GetGPROpSize(), true); if (CTX->HostFeatures.SupportsSVE()) { _StoreMemX87SVEOptPredicate(OpSize::i128Bit, OpSize::i16Bit, Src, MemStoreDst); } else { // For X87 extended doubles, split before storing _StoreMemFPR(OpSize::i64Bit, MemStoreDst, Src, Align); auto Upper = _VExtractToGPR(OpSize::i128Bit, OpSize::i64Bit, Src, 1); _StoreMemGPR(OpSize::i16Bit, Upper, MemStoreDst, Constant(8), std::min(Align, OpSize::i64Bit), MemOffsetType::SXTX, 1); } } else { _StoreMemAutoTSO(Class, OpSize, A, Src, Align == OpSize::iInvalid ? OpSize : Align); } if constexpr (Context::BLOCK_DEBUGGING) { if (CTX->BlockDebuggerTracker.IsSingleStepTarget(Entry)) { uint64_t Ptr = CalcAddress(Op, Operand, false); if (CTX->BlockDebuggerTracker.ContainsWriteWatchPoint(Ptr, OpSizeToSize(OpSize))) { // It's up to the developer if they want more advanced debugging logic here. LogMan::Msg::IFmt("Entrypoint 0x{:x} will hit write watch: [0x{:x}, 0x{:x})", Entry, Ptr, Ptr + OpSizeToSize(OpSize)); } } } } void OpDispatchBuilder::StoreResult(RegClass Class, X86Tables::DecodedOp Op, const X86Tables::DecodedOperand& Operand, Ref Src, IR::OpSize Align, MemoryAccessType AccessType) { StoreResult_WithOpSize(Class, Op, Operand, Src, OpSizeFromDst(Op), Align, AccessType); } void OpDispatchBuilder::StoreResult(RegClass Class, X86Tables::DecodedOp Op, Ref Src, IR::OpSize Align, MemoryAccessType AccessType) { StoreResult(Class, Op, Op->Dest, Src, Align, AccessType); } OpDispatchBuilder::OpDispatchBuilder(FEXCore::Context::ContextImpl* ctx, FEXCore::Core::InternalThreadState* Thread) : IREmitter {ctx->OpDispatcherAllocator, ctx->HostFeatures.SupportsTSOImm9 != 0} , CTX {ctx} , Thread {Thread} { if (CTX->HostFeatures.SupportsAVX && CTX->HostFeatures.SupportsSVE256) { SaveAVXStateFunc = &OpDispatchBuilder::SaveAVXState; RestoreAVXStateFunc = &OpDispatchBuilder::RestoreAVXState; DefaultAVXStateFunc = &OpDispatchBuilder::DefaultAVXState; } else if (CTX->HostFeatures.SupportsAVX) { SaveAVXStateFunc = &OpDispatchBuilder::AVX128_SaveAVXState; RestoreAVXStateFunc = &OpDispatchBuilder::AVX128_RestoreAVXState; DefaultAVXStateFunc = &OpDispatchBuilder::AVX128_DefaultAVXState; } } void OpDispatchBuilder::ResetWorkingList() { IREmitter::ReownOrClaimBuffer(); JumpTargets.clear(); BlockSetRIP = false; DecodeFailure = false; ShouldDump = false; CurrentCodeBlock = nullptr; RegCache.Written = 0; RegCache.Cached = 0; } void OpDispatchBuilder::UnhandledOp(OpcodeArgs) { DecodeFailure = true; } void OpDispatchBuilder::MOVGPROp(OpcodeArgs, uint32_t SrcIndex) { // StoreResult will store with the same size as the input, so we allow upper // garbage on the input. The zero extension would be pointless. Ref Src = LoadSourceGPR(Op, Op->Src[SrcIndex], Op->Flags, {.Align = OpSize::i8Bit, .AllowUpperGarbage = true}); StoreResultGPR(Op, Src, OpSize::i8Bit); } void OpDispatchBuilder::MOVGPRNTOp(OpcodeArgs) { Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.Align = OpSize::i8Bit}); StoreResultGPR(Op, Src, OpSize::i8Bit, MemoryAccessType::STREAM); } void OpDispatchBuilder::ALUOp(OpcodeArgs, FEXCore::IR::IROps ALUIROp, FEXCore::IR::IROps AtomicFetchOp, unsigned SrcIdx, bool DestRAX) { // On x86, the canonical way to zero a register is XOR with itself. Detect and // emit optimal arm64 assembly. if (!DestIsLockedMem(Op) && ALUIROp == FEXCore::IR::IROps::OP_XOR && Op->Dest.IsGPR() && Op->Src[SrcIdx].IsGPR() && Op->Dest.Data.GPR == Op->Src[SrcIdx].Data.GPR) { // Set flags for zero result with inverted carry. We subtract an arbitrary // register from itself to get the zero, since `subs wzr, #0` is not // encodable. This is optimal and works regardless of the opsize. auto Zero = LoadGPR(Op->Dest.Data.GPR.GPR); HandleNZ00Write(); InvalidateAF(); CalculatePF(SubWithFlags(OpSize::i32Bit, Zero, Zero)); CFInverted = true; FlushRegisterCache(); // Move 0 into the register StoreResultGPR(Op, Constant(0)); return; } auto Size = OpSizeFromDst(Op); auto ResultSize = Size; auto RoundedSize = Size; if (ALUIROp != FEXCore::IR::IROps::OP_ANDWITHFLAGS) { RoundedSize = std::max(OpSize::i32Bit, RoundedSize); } // X86 basic ALU ops just do the operation between the destination and a single source Ref Src = LoadSourceGPR(Op, Op->Src[SrcIdx], Op->Flags, {.AllowUpperGarbage = true}); // Try to eliminate the masking after 8/16-bit operations with constants, by // promoting to a full size operation that preserves the upper bits. uint64_t Const; bool IsConst = IsValueConstant(WrapNode(Src), &Const); if (Size < OpSize::i32Bit && !DestIsLockedMem(Op) && ((Op->Dest.IsGPR() && !Op->Dest.Data.GPR.HighBits) || DestRAX) && IsConst && (ALUIROp == IR::IROps::OP_XOR || ALUIROp == IR::IROps::OP_OR || ALUIROp == IR::IROps::OP_ANDWITHFLAGS)) { RoundedSize = ResultSize = GetGPROpSize(); LOGMAN_THROW_A_FMT(Const < (1ull << IR::OpSizeAsBits(Size)), "does not clobber"); // For AND, we can play the same trick but we instead need the upper bits of // the constant to be all-1s instead of all-0s to preserve. We also can't // use andwithflags in this case, since we've promoted to 64-bit so the // negate flag would be wrong, but using the regular logical operation path // instead still ends up a net win for uops. // // In the common case where the constant is of the form (1 << x) - 1, the // adjusted constant here will inline into the arm64 and instruction, so if // flags are not needed, we save an instruction overall. if (ALUIROp == IR::IROps::OP_ANDWITHFLAGS) { Src = Constant(Const | ~((1ull << IR::OpSizeAsBits(Size)) - 1)); ALUIROp = IR::IROps::OP_AND; } } Ref Result {}; Ref Dest {}; if (DestIsLockedMem(Op)) { HandledLock = true; Ref DestMem = MakeSegmentAddress(Op, Op->Dest); DeriveOp(FetchOp, AtomicFetchOp, _AtomicFetchAdd(Size, Src, DestMem)); Dest = FetchOp; } else if (DestRAX) { Dest = LoadGPRRegister(X86State::REG_RAX, OpSizeFromSrc(Op), 0, true); } else { Dest = LoadSourceGPR(Op, Op->Dest, Op->Flags, {.AllowUpperGarbage = true}); } const auto OpSize = RoundedSize; uint64_t Mask = Size == OpSize::i64Bit ? ~0ull : ((1ull << IR::OpSizeAsBits(Size)) - 1); if (IsConst && Const == Mask && !DestIsLockedMem(Op) && ALUIROp == IR::IROps::OP_XOR && Size >= OpSize::i32Bit) { Result = _Not(OpSize, Dest); } else if (IsConst && Const == Mask && !DestIsLockedMem(Op) && ALUIROp == IR::IROps::OP_AND) { Result = Dest; } else { DeriveOp(ALUOp, ALUIROp, _AndWithFlags(OpSize, Dest, Src)); Result = ALUOp; } // Flags set switch (ALUIROp) { case FEXCore::IR::IROps::OP_ADD: Result = CalculateFlags_ADD(Size, Dest, Src); break; case FEXCore::IR::IROps::OP_SUB: Result = CalculateFlags_SUB(Size, Dest, Src); break; case FEXCore::IR::IROps::OP_XOR: case FEXCore::IR::IROps::OP_AND: case FEXCore::IR::IROps::OP_OR: { CalculateFlags_Logical(Size, Result); break; } case FEXCore::IR::IROps::OP_ANDWITHFLAGS: { HandleNZ00Write(); CalculatePF(Result); InvalidateAF(); break; } default: break; } if (DestRAX) { StoreGPRResultWithZExtSemantics(X86State::REG_RAX, Result, ResultSize); } else if (!DestIsLockedMem(Op)) { StoreResultGPR_WithOpSize(Op, Op->Dest, Result, ResultSize, OpSize::iInvalid, MemoryAccessType::DEFAULT); } } void OpDispatchBuilder::LSLOp(OpcodeArgs) { // Emulate by always returning failure, this deviates from both Linux and Windows but // shouldn't be depended on by anything. SetRFLAG(Constant(0)); } void OpDispatchBuilder::INTOp(OpcodeArgs) { IR::BreakDefinition Reason; bool SetRIPToNext = false; switch (Op->OP) { case 0xCD: { // INT imm8 uint8_t Literal = Op->Src[0].Literal(); if (CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Linux) { constexpr uint8_t SYSCALL_LITERAL = 0x80; if (Literal == SYSCALL_LITERAL) { if (Is64BitMode) [[unlikely]] { LogMan::Msg::EFmt("[Unsupported] Trying to execute 32-bit syscall from a 64-bit process."); UnhandledOp(Op); return; } // Syscall on linux SyscallOp(Op, false); return; } } else if (CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Wow64 || CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec) { constexpr uint8_t SYSCALL_LITERAL = 0x2E; if (Literal == SYSCALL_LITERAL) { // Can be used for both 64-bit and 32-bit syscalls on windows SyscallOp(Op, false); return; } if (CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec) { // This is used when QueryPerformanceCounter is called on recent Windows versions, it causes CNTVCT to be written into RAX. constexpr uint8_t GET_CNTVCT_LITERAL = 0x81; if (Literal == GET_CNTVCT_LITERAL) { StoreGPRRegister(X86State::REG_RAX, _CycleCounter(false)); return; } } } Reason.ErrorRegister = Literal << 3 | (0b010); Reason.Signal = Core::FAULT_SIGSEGV; // GP is raised when task-gate isn't setup to be valid Reason.TrapNumber = X86State::X86_TRAPNO_GP; Reason.si_code = 0x80; break; } case 0xCE: // INTO Reason.ErrorRegister = 0; Reason.Signal = Core::FAULT_SIGSEGV; Reason.TrapNumber = X86State::X86_TRAPNO_OF; Reason.si_code = 0x80; break; case 0xF1: // INT1 Reason.ErrorRegister = 0; Reason.Signal = Core::FAULT_SIGTRAP; Reason.TrapNumber = X86State::X86_TRAPNO_DB; Reason.si_code = 1; SetRIPToNext = true; break; case 0xF4: { // HLT Reason.ErrorRegister = 0; Reason.Signal = Core::FAULT_SIGSEGV; Reason.TrapNumber = X86State::X86_TRAPNO_GP; Reason.si_code = 0x80; break; } case 0x0B: // UD2 Reason.ErrorRegister = 0; Reason.Signal = Core::FAULT_SIGILL; Reason.TrapNumber = X86State::X86_TRAPNO_UD; Reason.si_code = 2; break; case 0xCC: // INT3 Reason.ErrorRegister = 0; Reason.Signal = Core::FAULT_SIGTRAP; Reason.TrapNumber = X86State::X86_TRAPNO_BP; Reason.si_code = 0x80; SetRIPToNext = true; break; default: FEX_UNREACHABLE; } // Calculate flags early. FlushRegisterCache(); const auto GPRSize = GetGPROpSize(); if (SetRIPToNext) { BlockSetRIP = SetRIPToNext; // We want to set RIP to the next instruction after INT3/INT1 auto NewRIP = GetRelocatedPC(Op); _StoreContextGPR(GPRSize, NewRIP, offsetof(FEXCore::Core::CPUState, rip)); } else if (Op->OP != 0xCE) { auto NewRIP = GetRelocatedPC(Op, -Op->InstSize); _StoreContextGPR(GPRSize, NewRIP, offsetof(FEXCore::Core::CPUState, rip)); } if (Op->OP == 0xCE) { // Conditional to only break if Overflow == 1 CalculateDeferredFlags(); // If condition doesn't hold then keep going // CondClass::FNU means OF == 0 auto CondJump_ = CondJumpNZCV(CondClass::FNU); auto FalseBlock = CreateNewCodeBlockAfter(GetCurrentBlock()); SetFalseJumpTarget(CondJump_, FalseBlock); SetCurrentCodeBlock(FalseBlock); StartNewBlock(); auto NewRIP = GetRelocatedPC(Op); _StoreContextGPR(GPRSize, NewRIP, offsetof(FEXCore::Core::CPUState, rip)); Break(Reason); // Make sure to start a new block after ending this one auto JumpTarget = CreateNewCodeBlockAfter(FalseBlock); SetTrueJumpTarget(CondJump_, JumpTarget); SetCurrentCodeBlock(JumpTarget); StartNewBlock(); } else { BlockSetRIP = true; Break(Reason); } } void OpDispatchBuilder::TZCNT(OpcodeArgs) { // _FindTrailingZeroes ignores upper garbage so we don't need to mask Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); Src = _FindTrailingZeroes(OpSizeFromSrc(Op), Src); StoreResultGPR(Op, Src); CalculateFlags_ZCNT(OpSizeFromSrc(Op), Src); } void OpDispatchBuilder::LZCNT(OpcodeArgs) { // _CountLeadingZeroes clears upper garbage so we don't need to mask Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.AllowUpperGarbage = true}); auto Res = _CountLeadingZeroes(OpSizeFromSrc(Op), Src); StoreResultGPR(Op, Res); CalculateFlags_ZCNT(OpSizeFromSrc(Op), Res); } void OpDispatchBuilder::MOVBEOp(OpcodeArgs) { const auto GPRSize = GetGPROpSize(); const auto SrcSize = OpSizeFromSrc(Op); Ref Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.Align = OpSize::i8Bit}); if (DestIsMem(Op) || SrcSize != OpSize::i16Bit) { Src = _Rev(SrcSize, Src); StoreResultGPR(Op, Op->Dest, Src); } else { Src = _Rev(std::max(OpSize::i32Bit, SrcSize), Src); // 16-bit does an insert. // Rev of 16-bit value as 32-bit replaces the result in the upper 16-bits of the result. // bfxil the 16-bit result in to the GPR. Ref Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, GPRSize, Op->Flags); auto Result = _Bfxil(GPRSize, 16, 16, Dest, Src); StoreResultGPR_WithOpSize(Op, Op->Dest, Result, GPRSize); } } void OpDispatchBuilder::CLWBOrTPause(OpcodeArgs) { if (DestIsMem(Op)) { Ref DestMem = MakeSegmentAddress(Op, Op->Dest); _CacheLineClean(DestMem); } else { if (!CTX->HostFeatures.SupportsWFXT) { UnimplementedOp(Op); } else { auto RAX = LoadGPRRegister(X86State::REG_RAX); auto RDX = LoadGPRRegister(X86State::REG_RDX); // Incoming source register is unused. _WFET(RDX, RAX); // OF, SF, ZF, AF, PF, CF all zero. // CF is used if the OS deadline is set, which we don't do anything with. ZeroPF_AF(); ZeroNZCV(); } } } void OpDispatchBuilder::CLFLUSHOPT(OpcodeArgs) { Ref DestMem = MakeSegmentAddress(Op, Op->Dest); _CacheLineClear(DestMem, false); } void OpDispatchBuilder::LoadFenceOrXRSTOR(OpcodeArgs) { // 0xE8 signifies LFENCE if (Op->ModRM == 0xE8) { _Fence(FenceType::Load); } else { XRstorOpImpl(Op); } } void OpDispatchBuilder::MemFenceOrXSAVEOPT(OpcodeArgs) { if (Op->ModRM == 0xF0) { // 0xF0 is MFENCE _Fence(FenceType::LoadStore); } else { XSaveOp(Op); } } void OpDispatchBuilder::StoreFenceOrCLFlush(OpcodeArgs) { if (Op->ModRM == 0xF8) { // 0xF8 is SFENCE _Fence(FenceType::Store); } else { // This is a CLFlush Ref DestMem = MakeSegmentAddress(Op, Op->Dest); _CacheLineClear(DestMem, true); } } void OpDispatchBuilder::UMonitorOrCLRSSBSY(OpcodeArgs) { if (DestIsMem(Op) || !CTX->HostFeatures.SupportsWFXT) { // CLRSSBSY UnimplementedOp(Op); } else { // Explicit NOP implementation of umonitor. } } void OpDispatchBuilder::UMWaitOp(OpcodeArgs) { if (DestIsMem(Op) || !CTX->HostFeatures.SupportsWFXT) { UnimplementedOp(Op); } else { // Explicit NOP implementation of umwait. // Still zero flags. // // OF, SF, ZF, AF, PF, CF all zero. ZeroPF_AF(); ZeroNZCV(); } } void OpDispatchBuilder::CLZeroOp(OpcodeArgs) { if (!CTX->HostFeatures.SupportsCLZERO) { UnimplementedOp(Op); return; } _CacheLineZero(LoadGPRRegister(X86State::REG_RAX)); } void OpDispatchBuilder::Prefetch(OpcodeArgs, bool ForStore, bool Stream, uint8_t Level) { if (Op->Src[0].IsGPR()) { // NOP instance. return; } Ref DestMem = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.LoadData = false}); _Prefetch(ForStore, Stream, Level, DestMem, Invalid(), MemOffsetType::SXTX, 1); } void OpDispatchBuilder::RDTSCPOp(OpcodeArgs) { // RDTSCP is slightly different than RDTSC // IA32_TSC_AUX is returned in RCX // All previous loads are globally visible // - Explicitly does not wait for stores to be globally visible // - Explicitly use an MFENCE before this instruction if you want this behaviour // This instruction is not an execution fence, so subsequent instructions can execute after this // - Explicitly use an LFENCE after RDTSCP if you want to block this behaviour if (CTX->HostFeatures.HostType != FEXCore::HostFeatures::HostTypeEnum::Linux && !CTX->HostFeatures.SupportsCPUIndexInTPIDRRO) { // RDTSCP is unsupported on Win32 platforms if TPIDRRO isn't supported. UnimplementedOp(Op); return; } auto Counter = CycleCounter(true); auto ID = _ProcessorID(); StoreGPRRegister(X86State::REG_RAX, Counter.CounterLow); StoreGPRRegister(X86State::REG_RCX, ID); StoreGPRRegister(X86State::REG_RDX, Counter.CounterHigh); } void OpDispatchBuilder::RDPIDOp(OpcodeArgs) { if (CTX->HostFeatures.HostType != FEXCore::HostFeatures::HostTypeEnum::Linux && !CTX->HostFeatures.SupportsCPUIndexInTPIDRRO) { // RDTSCP is unsupported on Win32 platforms if TPIDRRO isn't supported. UnimplementedOp(Op); return; } StoreResultGPR(Op, _ProcessorID()); } void OpDispatchBuilder::CRC32(OpcodeArgs) { if (!CTX->HostFeatures.SupportsCRC) { UnimplementedOp(Op); return; } const auto GPRSize = GetGPROpSize(); const auto SrcSize = OpSizeFromSrc(Op); // Destination GPR size is always 4 or 8 bytes depending on widening const auto DstSize = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REX_WIDENING ? OpSize::i64Bit : OpSize::i32Bit; Ref Dest = LoadSourceGPR_WithOpSize(Op, Op->Dest, GPRSize, Op->Flags); // Incoming memory is 8, 16, 32, or 64 Ref Src {}; if (Op->Src[0].IsGPR()) { Src = LoadSourceGPR_WithOpSize(Op, Op->Src[0], SrcSize, Op->Flags, {.AllowUpperGarbage = true}); } else { Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.Align = OpSize::i8Bit}); } auto Result = _CRC32(Dest, Src, SrcSize); StoreResultGPR_WithOpSize(Op, Op->Dest, Result, DstSize); } void OpDispatchBuilder::RDRANDOp(OpcodeArgs, bool Reseed) { if (!CTX->HostFeatures.SupportsRAND && !CTX->SoftwareRNGEnabled()) { UnimplementedOp(Op); return; } StoreResultGPR(Op, _RDRAND(Reseed)); // If the rng number is valid then NZCV is 0b0000, otherwise NZCV is 0b0100 auto CF_inv = GetRFLAG(X86State::RFLAG_ZF_RAW_LOC); // OF, SF, ZF, AF, PF all zero. CF indicates if valid. ZeroPF_AF(); if (!CTX->HostFeatures.SupportsFlagM) { ZeroNZCV(); SetCFInverted(CF_inv); } else { // Accelerated path. Invalid is 0 or 1, so set NZCV with a single rmif. HandleNZCVWrite(); _RmifNZCV(CF_inv, (64 - 1) /* rotate bit 0 into bit 1 = C */, 0xf); CFInverted = true; } } void OpDispatchBuilder::BreakOp(OpcodeArgs, FEXCore::IR::BreakDefinition BreakDefinition) { const auto GPRSize = GetGPROpSize(); // We don't actually support this instruction // Multiblock may hit it though _StoreContextGPR(GPRSize, GetRelocatedPC(Op, -Op->InstSize), offsetof(FEXCore::Core::CPUState, rip)); Break(BreakDefinition); if (Multiblock) { auto NextBlock = CreateNewCodeBlockAfter(GetCurrentBlock()); SetCurrentCodeBlock(NextBlock); StartNewBlock(); } else { BlockSetRIP = true; } } void OpDispatchBuilder::UnimplementedOp(OpcodeArgs) { BreakOp(Op, FEXCore::IR::BreakDefinition { .ErrorRegister = 0, .Signal = SIGILL, .TrapNumber = X86State::X86_TRAPNO_UD, .si_code = 2, ///< ILL_ILLOPN }); } void OpDispatchBuilder::PermissionRestrictedOp(OpcodeArgs) { BreakOp(Op, FEXCore::IR::BreakDefinition { .ErrorRegister = 0, .Signal = SIGSEGV, .TrapNumber = X86State::X86_TRAPNO_GP, .si_code = 0x80, }); } void OpDispatchBuilder::InvalidOp(OpcodeArgs) { BreakOp(Op, FEXCore::IR::BreakDefinition { .ErrorRegister = 0, .Signal = SIGILL, .TrapNumber = 0, .si_code = 0, }); } void OpDispatchBuilder::NoExecOp(OpcodeArgs) { BreakOp(Op, FEXCore::IR::BreakDefinition { .ErrorRegister = X86State::X86_PF_PROT | X86State::X86_PF_USER | X86State::X86_PF_INSTR, .Signal = Core::FAULT_SIGSEGV, .TrapNumber = X86State::X86_TRAPNO_PF, .si_code = 2, // SEGV_ACCERR }); } #undef OpcodeArgs } // namespace FEXCore::IR