// SPDX-License-Identifier: MIT /* $info$ tags: ir|opts desc: ConstProp, ZExt elim, const pooling, fcmp reduction, const inlining $end_info$ */ // aarch64 heuristics #include "aarch64/assembler-aarch64.h" #include "aarch64/cpu-aarch64.h" #include "aarch64/disasm-aarch64.h" #include "aarch64/assembler-aarch64.h" #include "Interface/IR/IREmitter.h" #include "Interface/IR/PassManager.h" #include "Interface/Core/CPUID.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace FEXCore::IR { uint64_t getMask(IROp_Header* Op) { uint64_t NumBits = Op->Size * 8; return (~0ULL) >> (64 - NumBits); } // Returns true if the number bits from [0:width) contain the same bit. // Ensuring that the consecutive bits in the range are entirely 0 or 1. static bool HasConsecutiveBits(uint64_t imm, unsigned width) { if (width == 0) { return true; } // Credit to https://github.com/dougallj for this implementation. return ((imm ^ (imm >> 1)) & ((1ULL << (width - 1)) - 1)) == 0; } // aarch64 heuristics static bool IsImmLogical(uint64_t imm, unsigned width) { if (width < 32) { width = 32; } return vixl::aarch64::Assembler::IsImmLogical(imm, width); } static bool IsImmAddSub(uint64_t imm) { return vixl::aarch64::Assembler::IsImmAddSub(imm); } static bool IsBfeAlreadyDone(IREmitter* IREmit, OrderedNodeWrapper src, uint64_t Width) { auto IROp = IREmit->GetOpHeader(src); if (IROp->Op == OP_BFE) { auto Op = IROp->C(); if (Width >= Op->Width) { return true; } } return false; } class ConstProp final : public FEXCore::IR::Pass { public: explicit ConstProp(bool SupportsTSOImm9, const FEXCore::CPUIDEmu* CPUID) : SupportsTSOImm9 {SupportsTSOImm9} , CPUID {CPUID} {} void Run(IREmitter* IREmit) override; private: void HandleConstantPools(IREmitter* IREmit, const IRListView& CurrentIR); void ConstantPropagation(IREmitter* IREmit, const IRListView& CurrentIR, Ref CodeNode, IROp_Header* IROp); void ConstantInlining(IREmitter* IREmit, const IRListView& CurrentIR); fextl::unordered_map ConstPool; // Pool inline constant generation. These are typically very small and pool efficiently. fextl::robin_map InlineConstantGen; Ref CreateInlineConstant(IREmitter* IREmit, uint64_t Constant) { const auto it = InlineConstantGen.find(Constant); if (it != InlineConstantGen.end()) { return it->second; } auto Result = InlineConstantGen.insert_or_assign(Constant, IREmit->_InlineConstant(Constant)); return Result.first->second; } bool SupportsTSOImm9 {}; const FEXCore::CPUIDEmu* CPUID; void InlineMemImmediate(IREmitter* IREmit, const IRListView& IR, Ref CodeNode, IROp_Header* IROp, OrderedNodeWrapper Offset, MemOffsetType OffsetType, const size_t Offset_Index, uint8_t& OffsetScale, bool TSO) { uint64_t Imm {}; if (OffsetType != MEM_OFFSET_SXTX || !IREmit->IsValueConstant(Offset, &Imm)) { return; } // The immediate may be scaled in the IR, we need to correct for that. Imm *= OffsetScale; // Signed immediate unscaled 9-bit range for both regular and LRCPC2 ops. bool IsSIMM9 = ((int64_t)Imm >= -256) && ((int64_t)Imm <= 255); IsSIMM9 &= (SupportsTSOImm9 || !TSO); // Extended offsets for regular loadstore only. bool IsExtended = (Imm & (IROp->Size - 1)) == 0 && Imm / IROp->Size <= 4095; IsExtended &= !TSO; if (IsSIMM9 || IsExtended) { IREmit->SetWriteCursor(IR.GetNode(Offset)); IREmit->ReplaceNodeArgument(CodeNode, Offset_Index, CreateInlineConstant(IREmit, Imm)); OffsetScale = 1; } } }; // Constants are pooled per block. void ConstProp::HandleConstantPools(IREmitter* IREmit, const IRListView& CurrentIR) { for (auto [BlockNode, BlockIROp] : CurrentIR.GetBlocks()) { for (auto [CodeNode, IROp] : CurrentIR.GetCode(BlockNode)) { if (IROp->Op == OP_CONSTANT) { auto Op = IROp->C(); auto it = ConstPool.find(Op->Constant); if (it != ConstPool.end()) { auto CodeIter = CurrentIR.at(CodeNode); IREmit->ReplaceUsesWithAfter(CodeNode, it->second, CodeIter); } else { ConstPool[Op->Constant] = CodeNode; } } } ConstPool.clear(); } } // constprop + some more per instruction logic void ConstProp::ConstantPropagation(IREmitter* IREmit, const IRListView& CurrentIR, Ref CodeNode, IROp_Header* IROp) { switch (IROp->Op) { case OP_ADD: case OP_SUB: case OP_ADDWITHFLAGS: case OP_SUBWITHFLAGS: { auto Op = IROp->C(); uint64_t Constant1 {}; uint64_t Constant2 {}; bool IsConstant1 = IREmit->IsValueConstant(IROp->Args[0], &Constant1); bool IsConstant2 = IREmit->IsValueConstant(IROp->Args[1], &Constant2); if (IsConstant1 && IsConstant2 && IROp->Op == OP_ADD) { uint64_t NewConstant = (Constant1 + Constant2) & getMask(IROp); IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (IsConstant1 && IsConstant2 && IROp->Op == OP_SUB) { uint64_t NewConstant = (Constant1 - Constant2) & getMask(IROp); IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (IsConstant2 && !IsImmAddSub(Constant2) && IsImmAddSub(-Constant2)) { // If the second argument is constant, the immediate is not ImmAddSub, but when negated is. // So, negate the operation to negate (and inline) the constant. if (IROp->Op == OP_ADD) { IROp->Op = OP_SUB; } else if (IROp->Op == OP_SUB) { IROp->Op = OP_ADD; } else if (IROp->Op == OP_ADDWITHFLAGS) { IROp->Op = OP_SUBWITHFLAGS; } else if (IROp->Op == OP_SUBWITHFLAGS) { IROp->Op = OP_ADDWITHFLAGS; } IREmit->SetWriteCursorBefore(CodeNode); // Negate the constant. auto NegConstant = IREmit->_Constant(-Constant2); // Replace the second source with the negated constant. IREmit->ReplaceNodeArgument(CodeNode, Op->Src2_Index, NegConstant); } break; } case OP_SUBSHIFT: { auto Op = IROp->C(); uint64_t Constant1, Constant2; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1) && IREmit->IsValueConstant(IROp->Args[1], &Constant2) && Op->Shift == IR::ShiftType::LSL) { // Optimize the LSL case when we know both sources are constant. // This is a pattern that shows up with direction flag calculations if DF was set just before the operation. uint64_t NewConstant = (Constant1 - (Constant2 << Op->ShiftAmount)) & getMask(IROp); IREmit->ReplaceWithConstant(CodeNode, NewConstant); } break; } case OP_AND: { uint64_t Constant1 {}; uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1) && IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { uint64_t NewConstant = (Constant1 & Constant2) & getMask(IROp); IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (Constant2 == 1) { // happens from flag calcs auto val = IREmit->GetOpHeader(IROp->Args[0]); uint64_t Constant3; if (val->Op == OP_SELECT && IREmit->IsValueConstant(val->Args[2], &Constant2) && IREmit->IsValueConstant(val->Args[3], &Constant3) && Constant2 == 1 && Constant3 == 0) { IREmit->ReplaceAllUsesWith(CodeNode, CurrentIR.GetNode(IROp->Args[0])); } } else if (IROp->Args[0].ID() == IROp->Args[1].ID()) { // AND with same value results in original value IREmit->ReplaceAllUsesWith(CodeNode, CurrentIR.GetNode(IROp->Args[0])); } break; } case OP_OR: { uint64_t Constant1 {}; uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1) && IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { uint64_t NewConstant = Constant1 | Constant2; IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (IROp->Args[0].ID() == IROp->Args[1].ID()) { // OR with same value results in original value IREmit->ReplaceAllUsesWith(CodeNode, CurrentIR.GetNode(IROp->Args[0])); } break; } case OP_XOR: { uint64_t Constant1 {}; uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1) && IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { uint64_t NewConstant = Constant1 ^ Constant2; IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (IROp->Args[0].ID() == IROp->Args[1].ID()) { // XOR with same value results to zero IREmit->SetWriteCursor(CodeNode); IREmit->ReplaceAllUsesWith(CodeNode, IREmit->_Constant(0)); } else { // XOR with zero results in the nonzero source for (unsigned i = 0; i < 2; ++i) { if (!IREmit->IsValueConstant(IROp->Args[i], &Constant1)) { continue; } if (Constant1 != 0) { continue; } IREmit->SetWriteCursor(CodeNode); Ref Arg = CurrentIR.GetNode(IROp->Args[1 - i]); IREmit->ReplaceAllUsesWith(CodeNode, Arg); break; } } break; } case OP_NEG: { uint64_t Constant {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant)) { uint64_t NewConstant = -Constant; IREmit->ReplaceWithConstant(CodeNode, NewConstant); } break; } case OP_LSHL: { uint64_t Constant1 {}; uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1) && IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { // Shifts mask the shift amount by 63 or 31 depending on operating size; uint64_t ShiftMask = IROp->Size == 8 ? 63 : 31; uint64_t NewConstant = (Constant1 << (Constant2 & ShiftMask)) & getMask(IROp); IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (IREmit->IsValueConstant(IROp->Args[1], &Constant2) && Constant2 == 0) { IREmit->SetWriteCursor(CodeNode); Ref Arg = CurrentIR.GetNode(IROp->Args[0]); IREmit->ReplaceAllUsesWith(CodeNode, Arg); } break; } case OP_LSHR: { uint64_t Constant1 {}; uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1) && IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { // Shifts mask the shift amount by 63 or 31 depending on operating size; // The source is masked, which will produce a correctly masked // destination. Masking the destination without the source instead will // right-shift garbage into the upper bits instead of zeroes. Constant1 &= getMask(IROp); Constant2 &= (IROp->Size == 8 ? 63 : 31); uint64_t NewConstant = (Constant1 >> Constant2); IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (IREmit->IsValueConstant(IROp->Args[1], &Constant2) && Constant2 == 0) { IREmit->SetWriteCursor(CodeNode); Ref Arg = CurrentIR.GetNode(IROp->Args[0]); IREmit->ReplaceAllUsesWith(CodeNode, Arg); } break; } case OP_BFE: { auto Op = IROp->C(); uint64_t Constant; // Is this value already BFE'd? if (IsBfeAlreadyDone(IREmit, Op->Src, Op->Width)) { IREmit->ReplaceAllUsesWith(CodeNode, CurrentIR.GetNode(Op->Src)); break; } // Is this value already ZEXT'd? if (Op->lsb == 0) { // LoadMem, LoadMemTSO & LoadContext ZExt auto source = Op->Src; auto sourceHeader = IREmit->GetOpHeader(source); if (Op->Width >= (sourceHeader->Size * 8) && (sourceHeader->Op == OP_LOADMEM || sourceHeader->Op == OP_LOADMEMTSO || sourceHeader->Op == OP_LOADCONTEXT)) { // Load mem / load ctx zexts, no need to vmem IREmit->ReplaceAllUsesWith(CodeNode, CurrentIR.GetNode(source)); break; } } if (IROp->Size <= 8 && IREmit->IsValueConstant(Op->Src, &Constant)) { uint64_t SourceMask = Op->Width == 64 ? ~0ULL : ((1ULL << Op->Width) - 1); SourceMask <<= Op->lsb; uint64_t NewConstant = (Constant & SourceMask) >> Op->lsb; IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (IROp->Size == CurrentIR.GetOp(IROp->Args[0])->Size && Op->Width == (IROp->Size * 8) && Op->lsb == 0) { // A BFE that extracts all bits results in original value // XXX - This is broken for now - see https://github.com/FEX-Emu/FEX/issues/351 // IREmit->ReplaceAllUsesWith(CodeNode, CurrentIR.GetNode(IROp->Args[0])); } else if (Op->Width == 1 && Op->lsb == 0) { // common from flag codegen auto val = IREmit->GetOpHeader(IROp->Args[0]); uint64_t Constant2 {}; uint64_t Constant3 {}; if (val->Op == OP_SELECT && IREmit->IsValueConstant(val->Args[2], &Constant2) && IREmit->IsValueConstant(val->Args[3], &Constant3) && Constant2 == 1 && Constant3 == 0) { IREmit->ReplaceAllUsesWith(CodeNode, CurrentIR.GetNode(IROp->Args[0])); } } break; } case OP_SBFE: { auto Op = IROp->C(); uint64_t Constant; if (IREmit->IsValueConstant(Op->Src, &Constant)) { // SBFE of a constant can be converted to a constant. uint64_t SourceMask = Op->Width == 64 ? ~0ULL : ((1ULL << Op->Width) - 1); uint64_t DestSizeInBits = IROp->Size * 8; uint64_t DestMask = DestSizeInBits == 64 ? ~0ULL : ((1ULL << DestSizeInBits) - 1); SourceMask <<= Op->lsb; int64_t NewConstant = (Constant & SourceMask) >> Op->lsb; NewConstant <<= 64 - Op->Width; NewConstant >>= 64 - Op->Width; NewConstant &= DestMask; IREmit->ReplaceWithConstant(CodeNode, NewConstant); } break; } case OP_BFI: { auto Op = IROp->C(); uint64_t ConstantDest {}; uint64_t ConstantSrc {}; bool DestIsConstant = IREmit->IsValueConstant(IROp->Args[0], &ConstantDest); bool SrcIsConstant = IREmit->IsValueConstant(IROp->Args[1], &ConstantSrc); if (DestIsConstant && SrcIsConstant) { uint64_t SourceMask = Op->Width == 64 ? ~0ULL : ((1ULL << Op->Width) - 1); uint64_t NewConstant = ConstantDest & ~(SourceMask << Op->lsb); NewConstant |= (ConstantSrc & SourceMask) << Op->lsb; IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (SrcIsConstant && HasConsecutiveBits(ConstantSrc, Op->Width)) { // We are trying to insert constant, if it is a bitfield of only set bits then we can orr or and it. IREmit->SetWriteCursor(CodeNode); uint64_t SourceMask = Op->Width == 64 ? ~0ULL : ((1ULL << Op->Width) - 1); uint64_t NewConstant = SourceMask << Op->lsb; if (ConstantSrc & 1) { auto orr = IREmit->_Or(IR::SizeToOpSize(IROp->Size), CurrentIR.GetNode(IROp->Args[0]), IREmit->_Constant(NewConstant)); IREmit->ReplaceAllUsesWith(CodeNode, orr); } else { // We are wanting to clear the bitfield. auto andn = IREmit->_Andn(IR::SizeToOpSize(IROp->Size), CurrentIR.GetNode(IROp->Args[0]), IREmit->_Constant(NewConstant)); IREmit->ReplaceAllUsesWith(CodeNode, andn); } } break; } case OP_MUL: { uint64_t Constant1 {}; uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1) && IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { uint64_t NewConstant = (Constant1 * Constant2) & getMask(IROp); IREmit->ReplaceWithConstant(CodeNode, NewConstant); } else if (IREmit->IsValueConstant(IROp->Args[1], &Constant2) && std::popcount(Constant2) == 1) { if (IROp->Size == 4 || IROp->Size == 8) { uint64_t amt = std::countr_zero(Constant2); IREmit->SetWriteCursor(CodeNode); auto shift = IREmit->_Lshl(IR::SizeToOpSize(IROp->Size), CurrentIR.GetNode(IROp->Args[0]), IREmit->_Constant(amt)); IREmit->ReplaceAllUsesWith(CodeNode, shift); } } break; } case OP_VMOV: { // elim from load mem auto source = IROp->Args[0]; auto sourceHeader = IREmit->GetOpHeader(source); if (IROp->Size >= sourceHeader->Size && (sourceHeader->Op == OP_LOADMEM || sourceHeader->Op == OP_LOADMEMTSO || sourceHeader->Op == OP_LOADCONTEXT)) { // Load mem / load ctx zexts, no need to vmem IREmit->ReplaceAllUsesWith(CodeNode, CurrentIR.GetNode(source)); } break; } case OP_SYSCALL: { auto Op = IROp->CW(); // Is the first argument a constant? uint64_t Constant; if (IREmit->IsValueConstant(Op->SyscallID, &Constant)) { auto SyscallDef = Manager->SyscallHandler->GetSyscallABI(Constant); auto SyscallFlags = Manager->SyscallHandler->GetSyscallFlags(Constant); // Update the syscall flags Op->Flags = SyscallFlags; // XXX: Once we have the ability to do real function calls then we can call directly in to the syscall handler if (SyscallDef.NumArgs < FEXCore::HLE::SyscallArguments::MAX_ARGS) { // If the number of args are less than what the IR op supports then we can remove arg usage // We need +1 since we are still passing in syscall number here for (uint8_t Arg = (SyscallDef.NumArgs + 1); Arg < FEXCore::HLE::SyscallArguments::MAX_ARGS; ++Arg) { IREmit->ReplaceNodeArgument(CodeNode, Arg, IREmit->Invalid()); } // Replace syscall with inline passthrough syscall if we can if (SyscallDef.HostSyscallNumber != -1) { IREmit->SetWriteCursor(CodeNode); // Skip Args[0] since that is the syscallid auto InlineSyscall = IREmit->_InlineSyscall(CurrentIR.GetNode(IROp->Args[1]), CurrentIR.GetNode(IROp->Args[2]), CurrentIR.GetNode(IROp->Args[3]), CurrentIR.GetNode(IROp->Args[4]), CurrentIR.GetNode(IROp->Args[5]), CurrentIR.GetNode(IROp->Args[6]), SyscallDef.HostSyscallNumber, Op->Flags); // Replace all syscall uses with this inline one IREmit->ReplaceAllUsesWith(CodeNode, InlineSyscall); // We must remove here since DCE can't remove a IROp with sideeffects IREmit->Remove(CodeNode); } } } break; } case OP_CPUID: { auto Op = IROp->CW(); uint64_t ConstantFunction {}, ConstantLeaf {}; bool IsConstantFunction = IREmit->IsValueConstant(Op->Function, &ConstantFunction); bool IsConstantLeaf = IREmit->IsValueConstant(Op->Leaf, &ConstantLeaf); // If the CPUID function is constant then we can try and optimize. if (IsConstantFunction) { // && ConstantFunction != 1) { // Check if it supports constant data reporting for this function. const auto SupportsConstant = CPUID->DoesFunctionReportConstantData(ConstantFunction); if (SupportsConstant.SupportsConstantFunction == CPUIDEmu::SupportsConstant::CONSTANT) { // If the CPUID needs a constant leaf to be optimized then this can't work if we didn't const-prop the leaf register. if (!(SupportsConstant.NeedsLeaf == CPUIDEmu::NeedsLeafConstant::NEEDSLEAFCONSTANT && !IsConstantLeaf)) { // Calculate the constant data and replace all uses. // DCE will remove the CPUID IR operation. const auto ConstantCPUIDResult = CPUID->RunFunction(ConstantFunction, ConstantLeaf); uint64_t ResultsLower = (static_cast(ConstantCPUIDResult.ebx) << 32) | ConstantCPUIDResult.eax; uint64_t ResultsUpper = (static_cast(ConstantCPUIDResult.edx) << 32) | ConstantCPUIDResult.ecx; IREmit->SetWriteCursor(CodeNode); auto ElementPair = IREmit->_CreateElementPair(IR::OpSize::i128Bit, IREmit->_Constant(ResultsLower), IREmit->_Constant(ResultsUpper)); // Replace all CPUID uses with this inline one IREmit->ReplaceAllUsesWith(CodeNode, ElementPair); } } } break; } case OP_XGETBV: { auto Op = IROp->CW(); uint64_t ConstantFunction {}; if (IREmit->IsValueConstant(Op->Function, &ConstantFunction) && CPUID->DoesXCRFunctionReportConstantData(ConstantFunction)) { const auto ConstantXCRResult = CPUID->RunXCRFunction(ConstantFunction); IREmit->SetWriteCursor(CodeNode); auto ElementPair = IREmit->_CreateElementPair(IR::OpSize::i64Bit, IREmit->_Constant(ConstantXCRResult.eax), IREmit->_Constant(ConstantXCRResult.edx)); // Replace all xgetbv uses with this inline one IREmit->ReplaceAllUsesWith(CodeNode, ElementPair); } break; } case OP_LDIV: case OP_LREM: { auto Op = IROp->C(); auto UpperIROp = IREmit->GetOpHeader(Op->Upper); // Check upper Op to see if it came from a sign-extension if (UpperIROp->Op != OP_SBFE) { break; } auto Sbfe = UpperIROp->C(); if (Sbfe->Width != 1 || Sbfe->lsb != 63 || Sbfe->Header.Args[0] != Op->Lower) { break; } // If it does then it we only need a 64bit SDIV IREmit->SetWriteCursor(CodeNode); Ref Lower = CurrentIR.GetNode(Op->Lower); Ref Divisor = CurrentIR.GetNode(Op->Divisor); Ref SDivOp {}; if (IROp->Op == OP_LDIV) { SDivOp = IREmit->_Div(OpSize::i64Bit, Lower, Divisor); } else { SDivOp = IREmit->_Rem(OpSize::i64Bit, Lower, Divisor); } IREmit->ReplaceAllUsesWith(CodeNode, SDivOp); break; } case OP_LUDIV: case OP_LUREM: { auto Op = IROp->C(); // Check upper Op to see if it came from a zeroing op // If it does then it we only need a 64bit UDIV uint64_t Value; if (!IREmit->IsValueConstant(Op->Upper, &Value) || Value != 0) { break; } IREmit->SetWriteCursor(CodeNode); Ref Lower = CurrentIR.GetNode(Op->Lower); Ref Divisor = CurrentIR.GetNode(Op->Divisor); Ref UDivOp {}; if (IROp->Op == OP_LUDIV) { UDivOp = IREmit->_UDiv(OpSize::i64Bit, Lower, Divisor); } else { UDivOp = IREmit->_URem(OpSize::i64Bit, Lower, Divisor); } IREmit->ReplaceAllUsesWith(CodeNode, UDivOp); break; } default: break; } } void ConstProp::ConstantInlining(IREmitter* IREmit, const IRListView& CurrentIR) { InlineConstantGen.clear(); for (auto [CodeNode, IROp] : CurrentIR.GetAllCode()) { switch (IROp->Op) { case OP_LSHR: case OP_ASHR: case OP_ROR: case OP_LSHL: { uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[1])); // this shouldn't be here, but rather on the emitter themselves or the constprop transformation? if (IROp->Size <= 4) { Constant2 &= 31; } else { Constant2 &= 63; } IREmit->ReplaceNodeArgument(CodeNode, 1, CreateInlineConstant(IREmit, Constant2)); } break; } case OP_ADD: case OP_SUB: case OP_ADDNZCV: case OP_SUBNZCV: case OP_ADDWITHFLAGS: case OP_SUBWITHFLAGS: { uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { // We don't allow 8/16-bit operations to have constants, since no // constant would be in bounds after the JIT's 24/16 shift. if (IsImmAddSub(Constant2) && IROp->Size >= 4) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[1])); IREmit->ReplaceNodeArgument(CodeNode, 1, CreateInlineConstant(IREmit, Constant2)); } } else if (IROp->Op == OP_SUBNZCV || IROp->Op == OP_SUBWITHFLAGS || IROp->Op == OP_SUB) { // TODO: Generalize this uint64_t Constant1 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1)) { if (Constant1 == 0) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[0])); IREmit->ReplaceNodeArgument(CodeNode, 0, CreateInlineConstant(IREmit, 0)); } } } break; } case OP_ADC: case OP_ADCWITHFLAGS: { uint64_t Constant1 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1)) { if (Constant1 == 0) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[0])); IREmit->ReplaceNodeArgument(CodeNode, 0, CreateInlineConstant(IREmit, 0)); } } break; } case OP_RMIFNZCV: { uint64_t Constant1 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1)) { if (Constant1 == 0) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[0])); IREmit->ReplaceNodeArgument(CodeNode, 0, CreateInlineConstant(IREmit, 0)); } } break; } case OP_CONDADDNZCV: case OP_CONDSUBNZCV: { uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { if (IsImmAddSub(Constant2)) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[1])); IREmit->ReplaceNodeArgument(CodeNode, 1, CreateInlineConstant(IREmit, Constant2)); } } uint64_t Constant1 {}; if (IREmit->IsValueConstant(IROp->Args[0], &Constant1)) { if (Constant1 == 0) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[0])); IREmit->ReplaceNodeArgument(CodeNode, 0, CreateInlineConstant(IREmit, 0)); } } break; } case OP_TESTNZ: { uint64_t Constant1 {}; if (IREmit->IsValueConstant(IROp->Args[1], &Constant1)) { if (IsImmLogical(Constant1, IROp->Size * 8)) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[1])); IREmit->ReplaceNodeArgument(CodeNode, 1, CreateInlineConstant(IREmit, Constant1)); } } break; } case OP_SELECT: { uint64_t Constant1 {}; if (IREmit->IsValueConstant(IROp->Args[1], &Constant1)) { if (IsImmAddSub(Constant1)) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[1])); IREmit->ReplaceNodeArgument(CodeNode, 1, CreateInlineConstant(IREmit, Constant1)); } } uint64_t AllOnes = IROp->Size == 8 ? 0xffff'ffff'ffff'ffffull : 0xffff'ffffull; uint64_t Constant2 {}; uint64_t Constant3 {}; if (IREmit->IsValueConstant(IROp->Args[2], &Constant2) && IREmit->IsValueConstant(IROp->Args[3], &Constant3) && (Constant2 == 1 || Constant2 == AllOnes) && Constant3 == 0) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[2])); IREmit->ReplaceNodeArgument(CodeNode, 2, CreateInlineConstant(IREmit, Constant2)); IREmit->ReplaceNodeArgument(CodeNode, 3, CreateInlineConstant(IREmit, Constant3)); } break; } case OP_NZCVSELECT: { uint64_t AllOnes = IROp->Size == 8 ? 0xffff'ffff'ffff'ffffull : 0xffff'ffffull; // We always allow source 1 to be zero, but source 0 can only be a // special 1/~0 constant if source 1 is 0. uint64_t Constant0 {}; uint64_t Constant1 {}; if (IREmit->IsValueConstant(IROp->Args[1], &Constant1) && Constant1 == 0) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[1])); IREmit->ReplaceNodeArgument(CodeNode, 1, CreateInlineConstant(IREmit, Constant1)); if (IREmit->IsValueConstant(IROp->Args[0], &Constant0) && (Constant0 == 1 || Constant0 == AllOnes)) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[0])); IREmit->ReplaceNodeArgument(CodeNode, 0, CreateInlineConstant(IREmit, Constant0)); } } break; } case OP_CONDJUMP: { uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { if (IsImmAddSub(Constant2)) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[1])); IREmit->ReplaceNodeArgument(CodeNode, 1, CreateInlineConstant(IREmit, Constant2)); } } break; } case OP_EXITFUNCTION: { auto Op = IROp->C(); uint64_t Constant {}; if (IREmit->IsValueConstant(Op->NewRIP, &Constant)) { IREmit->SetWriteCursor(CurrentIR.GetNode(Op->NewRIP)); IREmit->ReplaceNodeArgument(CodeNode, 0, CreateInlineConstant(IREmit, Constant)); } else { auto NewRIP = IREmit->GetOpHeader(Op->NewRIP); if (NewRIP->Op == OP_ENTRYPOINTOFFSET) { auto EO = NewRIP->C(); IREmit->SetWriteCursor(CurrentIR.GetNode(Op->NewRIP)); IREmit->ReplaceNodeArgument(CodeNode, 0, IREmit->_InlineEntrypointOffset(IR::SizeToOpSize(EO->Header.Size), EO->Offset)); } } break; } case OP_OR: case OP_XOR: case OP_AND: case OP_ANDWITHFLAGS: case OP_ANDN: { uint64_t Constant2 {}; if (IREmit->IsValueConstant(IROp->Args[1], &Constant2)) { if (IsImmLogical(Constant2, IROp->Size * 8)) { IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[1])); IREmit->ReplaceNodeArgument(CodeNode, 1, CreateInlineConstant(IREmit, Constant2)); } } break; } case OP_LOADMEM: { auto Op = IROp->CW(); InlineMemImmediate(IREmit, CurrentIR, CodeNode, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, false); break; } case OP_STOREMEM: { auto Op = IROp->CW(); InlineMemImmediate(IREmit, CurrentIR, CodeNode, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, false); break; } case OP_PREFETCH: { auto Op = IROp->CW(); InlineMemImmediate(IREmit, CurrentIR, CodeNode, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, false); break; } case OP_LOADMEMTSO: { auto Op = IROp->CW(); InlineMemImmediate(IREmit, CurrentIR, CodeNode, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, true); break; } case OP_STOREMEMTSO: { auto Op = IROp->CW(); InlineMemImmediate(IREmit, CurrentIR, CodeNode, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, true); break; } case OP_MEMCPY: { auto Op = IROp->CW(); uint64_t Constant {}; if (IREmit->IsValueConstant(Op->Direction, &Constant)) { IREmit->SetWriteCursor(CurrentIR.GetNode(Op->Direction)); IREmit->ReplaceNodeArgument(CodeNode, Op->Direction_Index, CreateInlineConstant(IREmit, Constant)); } break; } case OP_MEMSET: { auto Op = IROp->CW(); uint64_t Constant {}; if (IREmit->IsValueConstant(Op->Direction, &Constant)) { IREmit->SetWriteCursor(CurrentIR.GetNode(Op->Direction)); IREmit->ReplaceNodeArgument(CodeNode, Op->Direction_Index, CreateInlineConstant(IREmit, Constant)); } break; } default: break; } } } void ConstProp::Run(IREmitter* IREmit) { FEXCORE_PROFILE_SCOPED("PassManager::ConstProp"); auto CurrentIR = IREmit->ViewIR(); HandleConstantPools(IREmit, CurrentIR); for (auto [CodeNode, IROp] : CurrentIR.GetAllCode()) { ConstantPropagation(IREmit, CurrentIR, CodeNode, IROp); } ConstantInlining(IREmit, CurrentIR); } fextl::unique_ptr CreateConstProp(bool SupportsTSOImm9, const FEXCore::CPUIDEmu* CPUID) { return fextl::make_unique(SupportsTSOImm9, CPUID); } } // namespace FEXCore::IR