| //=- AArch64ConditionOptimizer.cpp - Remove useless comparisons for AArch64 -=// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // |
| // This pass tries to make consecutive comparisons of values use the same |
| // operands to allow the CSE pass to remove duplicate instructions. It adjusts |
| // comparisons with immediate values by converting between inclusive and |
| // exclusive forms (GE <-> GT, LE <-> LT) and correcting immediate values to |
| // make them equal. |
| // |
| // The pass handles: |
| // * Cross-block: SUBS/ADDS followed by conditional branches |
| // * Intra-block: Select-family conditional instructions |
| // |
| // |
| // Consider the following example in C: |
| // |
| // if ((a < 5 && ...) || (a > 5 && ...)) { |
| // ~~~~~ ~~~~~ |
| // ^ ^ |
| // x y |
| // |
| // Here both "x" and "y" expressions compare "a" with "5". When "x" evaluates |
| // to "false", "y" can just check flags set by the first comparison. As a |
| // result of the canonicalization employed by |
| // SelectionDAGBuilder::visitSwitchCase, DAGCombine, and other target-specific |
| // code, assembly ends up in the form that is not CSE friendly: |
| // |
| // ... |
| // cmp w8, #4 |
| // b.gt .LBB0_3 |
| // ... |
| // .LBB0_3: |
| // cmp w8, #6 |
| // b.lt .LBB0_6 |
| // ... |
| // |
| // Same assembly after the pass: |
| // |
| // ... |
| // cmp w8, #5 |
| // b.ge .LBB0_3 |
| // ... |
| // .LBB0_3: |
| // cmp w8, #5 // <-- CSE pass removes this instruction |
| // b.le .LBB0_6 |
| // ... |
| // |
| // See optimizeCrossBlock() and optimizeIntraBlock() for implementation details. |
| // |
| // TODO: maybe handle TBNZ/TBZ the same way as CMP when used instead for "a < 0" |
| // TODO: For cross-block: |
| // - allow second branching to be anything if it doesn't require adjusting |
| // |
| // Cross-block optimizeCrossBlock() handles four head/true-successor |
| // combinations: |
| // Bcc (head) + Bcc (true) -- original case |
| // Select (head) + Bcc (true) -- head ends with CSEL/CSET/etc. |
| // Bcc (head) + Select (true) -- true-successor ends with CSEL/CSET/etc. |
| // Select (head) + Select (true) -- both blocks end with a select |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "AArch64.h" |
| #include "AArch64Subtarget.h" |
| #include "MCTargetDesc/AArch64AddressingModes.h" |
| #include "Utils/AArch64BaseInfo.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/DepthFirstIterator.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/Statistic.h" |
| #include "llvm/CodeGen/MachineBasicBlock.h" |
| #include "llvm/CodeGen/MachineDominators.h" |
| #include "llvm/CodeGen/MachineFunction.h" |
| #include "llvm/CodeGen/MachineFunctionPass.h" |
| #include "llvm/CodeGen/MachineInstr.h" |
| #include "llvm/CodeGen/MachineOperand.h" |
| #include "llvm/CodeGen/MachineRegisterInfo.h" |
| #include "llvm/CodeGen/TargetInstrInfo.h" |
| #include "llvm/CodeGen/TargetRegisterInfo.h" |
| #include "llvm/CodeGen/TargetSubtargetInfo.h" |
| #include "llvm/InitializePasses.h" |
| #include "llvm/Pass.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <cassert> |
| #include <cstdlib> |
| |
| using namespace llvm; |
| |
| #define DEBUG_TYPE "aarch64-condopt" |
| |
| STATISTIC(NumConditionsAdjusted, "Number of conditions adjusted"); |
| |
| namespace { |
| |
| /// Bundles the parameters needed to adjust a comparison instruction. |
| struct CmpInfo { |
| int Imm; |
| unsigned Opc; |
| AArch64CC::CondCode CC; |
| }; |
| |
| class AArch64ConditionOptimizerImpl { |
| /// Represents a comparison instruction paired with its consuming |
| /// conditional instruction |
| struct CmpCondPair { |
| MachineInstr *CmpMI; |
| MachineInstr *CondMI; |
| AArch64CC::CondCode CC; |
| |
| int getImm() const { return CmpMI->getOperand(2).getImm(); } |
| unsigned getOpc() const { return CmpMI->getOpcode(); } |
| }; |
| |
| const AArch64InstrInfo *TII; |
| const TargetRegisterInfo *TRI; |
| MachineDominatorTree *DomTree; |
| const MachineRegisterInfo *MRI; |
| |
| public: |
| bool run(MachineFunction &MF, MachineDominatorTree &MDT); |
| |
| private: |
| bool canAdjustCmp(MachineInstr &CmpMI); |
| bool registersMatch(MachineInstr *FirstMI, MachineInstr *SecondMI); |
| bool nzcvLivesOut(MachineBasicBlock *MBB); |
| MachineInstr *getBccTerminator(MachineBasicBlock *MBB); |
| MachineInstr *findAdjustableCmp(MachineInstr *CondMI); |
| CmpInfo getAdjustedCmpInfo(MachineInstr *CmpMI, AArch64CC::CondCode Cmp); |
| void updateCmpInstr(MachineInstr *CmpMI, int NewImm, unsigned NewOpc); |
| void updateCondInstr(MachineInstr *CondMI, AArch64CC::CondCode NewCC); |
| void applyCmpAdjustment(CmpCondPair &Pair, const CmpInfo &Info); |
| bool commitPendingPair(std::optional<CmpCondPair> &PendingPair, |
| SmallDenseMap<Register, CmpCondPair> &PairsByReg); |
| bool tryOptimizePair(CmpCondPair &First, CmpCondPair &Second); |
| bool optimizeIntraBlock(MachineBasicBlock &MBB); |
| bool optimizeCrossBlock(MachineBasicBlock &HBB); |
| std::pair<MachineInstr *, AArch64CC::CondCode> |
| findCondConsumer(MachineBasicBlock *MBB); |
| }; |
| |
| class AArch64ConditionOptimizerLegacy : public MachineFunctionPass { |
| public: |
| static char ID; |
| AArch64ConditionOptimizerLegacy() : MachineFunctionPass(ID) {} |
| |
| void getAnalysisUsage(AnalysisUsage &AU) const override; |
| bool runOnMachineFunction(MachineFunction &MF) override; |
| |
| StringRef getPassName() const override { |
| return "AArch64 Condition Optimizer"; |
| } |
| }; |
| |
| } // end anonymous namespace |
| |
| char AArch64ConditionOptimizerLegacy::ID = 0; |
| |
| INITIALIZE_PASS_BEGIN(AArch64ConditionOptimizerLegacy, "aarch64-condopt", |
| "AArch64 CondOpt Pass", false, false) |
| INITIALIZE_PASS_DEPENDENCY(MachineDominatorTreeWrapperPass) |
| INITIALIZE_PASS_END(AArch64ConditionOptimizerLegacy, "aarch64-condopt", |
| "AArch64 CondOpt Pass", false, false) |
| |
| FunctionPass *llvm::createAArch64ConditionOptimizerLegacyPass() { |
| return new AArch64ConditionOptimizerLegacy(); |
| } |
| |
| void AArch64ConditionOptimizerLegacy::getAnalysisUsage( |
| AnalysisUsage &AU) const { |
| AU.addRequired<MachineDominatorTreeWrapperPass>(); |
| AU.addPreserved<MachineDominatorTreeWrapperPass>(); |
| MachineFunctionPass::getAnalysisUsage(AU); |
| } |
| |
| // Verify that the MI's immediate is adjustable and it only sets flags (pure |
| // cmp) |
| bool AArch64ConditionOptimizerImpl::canAdjustCmp(MachineInstr &CmpMI) { |
| unsigned ShiftAmt = AArch64_AM::getShiftValue(CmpMI.getOperand(3).getImm()); |
| if (!CmpMI.getOperand(2).isImm()) { |
| LLVM_DEBUG(dbgs() << "Immediate of cmp is symbolic, " << CmpMI << '\n'); |
| return false; |
| } else if (CmpMI.getOperand(2).getImm() << ShiftAmt >= 0xfff) { |
| LLVM_DEBUG(dbgs() << "Immediate of cmp may be out of range, " << CmpMI |
| << '\n'); |
| return false; |
| } else if (!MRI->use_nodbg_empty(CmpMI.getOperand(0).getReg())) { |
| LLVM_DEBUG(dbgs() << "Destination of cmp is not dead, " << CmpMI << '\n'); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Ensure both compare MIs use the same register, tracing through copies. |
| bool AArch64ConditionOptimizerImpl::registersMatch(MachineInstr *FirstMI, |
| MachineInstr *SecondMI) { |
| Register FirstReg = FirstMI->getOperand(1).getReg(); |
| Register SecondReg = SecondMI->getOperand(1).getReg(); |
| Register FirstCmpReg = |
| FirstReg.isVirtual() ? TRI->lookThruCopyLike(FirstReg, MRI) : FirstReg; |
| Register SecondCmpReg = |
| SecondReg.isVirtual() ? TRI->lookThruCopyLike(SecondReg, MRI) : SecondReg; |
| if (FirstCmpReg != SecondCmpReg) { |
| LLVM_DEBUG(dbgs() << "CMPs compare different registers\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Check if NZCV lives out to any successor block. |
| bool AArch64ConditionOptimizerImpl::nzcvLivesOut(MachineBasicBlock *MBB) { |
| for (auto *SuccBB : MBB->successors()) { |
| if (SuccBB->isLiveIn(AArch64::NZCV)) { |
| LLVM_DEBUG(dbgs() << "NZCV live into successor " |
| << printMBBReference(*SuccBB) << " from " |
| << printMBBReference(*MBB) << '\n'); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Returns true if the opcode is a comparison instruction (CMP/CMN). |
| static bool isCmpInstruction(unsigned Opc) { |
| switch (Opc) { |
| // cmp is an alias for SUBS with a dead destination register. |
| case AArch64::SUBSWri: |
| case AArch64::SUBSXri: |
| // cmp is an alias for ADDS with a dead destination register. |
| case AArch64::ADDSWri: |
| case AArch64::ADDSXri: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // Returns the Bcc terminator if present, otherwise nullptr. |
| MachineInstr * |
| AArch64ConditionOptimizerImpl::getBccTerminator(MachineBasicBlock *MBB) { |
| MachineBasicBlock::iterator Term = MBB->getFirstTerminator(); |
| if (Term == MBB->end()) { |
| LLVM_DEBUG(dbgs() << "No terminator in " << printMBBReference(*MBB) |
| << '\n'); |
| return nullptr; |
| } |
| |
| if (Term->getOpcode() != AArch64::Bcc) { |
| LLVM_DEBUG(dbgs() << "Non-Bcc terminator in " << printMBBReference(*MBB) |
| << ": " << *Term); |
| return nullptr; |
| } |
| |
| return &*Term; |
| } |
| |
| // Find the CMP instruction controlling the given conditional instruction and |
| // ensure it can be adjusted for CSE optimization. Searches backward from |
| // CondMI, ensuring no NZCV interference. Returns nullptr if no suitable CMP |
| // is found or if adjustments are not safe. |
| MachineInstr * |
| AArch64ConditionOptimizerImpl::findAdjustableCmp(MachineInstr *CondMI) { |
| assert(CondMI && "CondMI cannot be null"); |
| MachineBasicBlock *MBB = CondMI->getParent(); |
| |
| // Search backward from the conditional to find the instruction controlling |
| // it. |
| for (MachineBasicBlock::iterator B = MBB->begin(), |
| It = MachineBasicBlock::iterator(CondMI); |
| It != B;) { |
| It = prev_nodbg(It, B); |
| MachineInstr &I = *It; |
| assert(!I.isTerminator() && "Spurious terminator"); |
| // Ensure there is no use of NZCV between CMP and conditional. |
| if (I.readsRegister(AArch64::NZCV, /*TRI=*/nullptr)) |
| return nullptr; |
| |
| if (isCmpInstruction(I.getOpcode())) { |
| if (!canAdjustCmp(I)) { |
| return nullptr; |
| } |
| return &I; |
| } |
| |
| if (I.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr)) |
| return nullptr; |
| } |
| LLVM_DEBUG(dbgs() << "Flags not defined in " << printMBBReference(*MBB) |
| << '\n'); |
| return nullptr; |
| } |
| |
| // Changes opcode adds <-> subs considering register operand width. |
| static int getComplementOpc(int Opc) { |
| switch (Opc) { |
| case AArch64::ADDSWri: return AArch64::SUBSWri; |
| case AArch64::ADDSXri: return AArch64::SUBSXri; |
| case AArch64::SUBSWri: return AArch64::ADDSWri; |
| case AArch64::SUBSXri: return AArch64::ADDSXri; |
| default: |
| llvm_unreachable("Unexpected opcode"); |
| } |
| } |
| |
| // Changes form of comparison inclusive <-> exclusive. |
| static AArch64CC::CondCode getAdjustedCmp(AArch64CC::CondCode Cmp) { |
| switch (Cmp) { |
| case AArch64CC::GT: |
| return AArch64CC::GE; |
| case AArch64CC::GE: |
| return AArch64CC::GT; |
| case AArch64CC::LT: |
| return AArch64CC::LE; |
| case AArch64CC::LE: |
| return AArch64CC::LT; |
| case AArch64CC::HI: |
| return AArch64CC::HS; |
| case AArch64CC::HS: |
| return AArch64CC::HI; |
| case AArch64CC::LO: |
| return AArch64CC::LS; |
| case AArch64CC::LS: |
| return AArch64CC::LO; |
| default: |
| llvm_unreachable("Unexpected condition code"); |
| } |
| } |
| |
| // Returns the adjusted immediate, opcode, and condition code for switching |
| // between inclusive/exclusive forms (GT <-> GE, LT <-> LE). |
| CmpInfo |
| AArch64ConditionOptimizerImpl::getAdjustedCmpInfo(MachineInstr *CmpMI, |
| AArch64CC::CondCode Cmp) { |
| unsigned Opc = CmpMI->getOpcode(); |
| |
| bool IsSigned = Cmp == AArch64CC::GT || Cmp == AArch64CC::GE || |
| Cmp == AArch64CC::LT || Cmp == AArch64CC::LE; |
| |
| // CMN (compare with negative immediate) is an alias to ADDS (as |
| // "operand - negative" == "operand + positive") |
| bool Negative = (Opc == AArch64::ADDSWri || Opc == AArch64::ADDSXri); |
| |
| int Correction = (Cmp == AArch64CC::GT || Cmp == AArch64CC::HI) ? 1 : -1; |
| // Negate Correction value for comparison with negative immediate (CMN). |
| if (Negative) { |
| Correction = -Correction; |
| } |
| |
| const int OldImm = (int)CmpMI->getOperand(2).getImm(); |
| const int NewImm = std::abs(OldImm + Correction); |
| |
| // Bail out on cmn 0 (ADDS with immediate 0). It is a valid instruction but |
| // doesn't set flags in a way we can safely transform, so skip optimization. |
| if (OldImm == 0 && Negative) |
| return {OldImm, Opc, Cmp}; |
| |
| if ((OldImm == 1 && Negative && Correction == -1) || |
| (OldImm == 0 && Correction == -1)) { |
| // If we change opcodes for unsigned comparisons, this means we did an |
| // unsigned wrap (e.g., 0 wrapping to 0xFFFFFFFF), so return the old cmp. |
| // Note: For signed comparisons, opcode changes (cmn 1 ↔ cmp 0) are valid. |
| if (!IsSigned) |
| return {OldImm, Opc, Cmp}; |
| Opc = getComplementOpc(Opc); |
| } |
| |
| return {NewImm, Opc, getAdjustedCmp(Cmp)}; |
| } |
| |
| // Modifies a comparison instruction's immediate and opcode. |
| void AArch64ConditionOptimizerImpl::updateCmpInstr(MachineInstr *CmpMI, |
| int NewImm, |
| unsigned NewOpc) { |
| CmpMI->getOperand(2).setImm(NewImm); |
| CmpMI->setDesc(TII->get(NewOpc)); |
| } |
| |
| // Modifies the condition code of a conditional instruction. |
| void AArch64ConditionOptimizerImpl::updateCondInstr(MachineInstr *CondMI, |
| AArch64CC::CondCode NewCC) { |
| int CCOpIdx = |
| AArch64InstrInfo::findCondCodeUseOperandIdxForBranchOrSelect(*CondMI); |
| assert(CCOpIdx >= 0 && "Unsupported conditional instruction"); |
| CondMI->getOperand(CCOpIdx).setImm(NewCC); |
| ++NumConditionsAdjusted; |
| } |
| |
| // Applies a comparison adjustment to a cmp/cond instruction pair. |
| void AArch64ConditionOptimizerImpl::applyCmpAdjustment(CmpCondPair &Pair, |
| const CmpInfo &Info) { |
| updateCmpInstr(Pair.CmpMI, Info.Imm, Info.Opc); |
| updateCondInstr(Pair.CondMI, Info.CC); |
| Pair.CC = Info.CC; |
| } |
| |
| // Extracts the condition code from the result of analyzeBranch. |
| // Returns the CondCode or Invalid if the format is not a simple br.cond. |
| static AArch64CC::CondCode parseCondCode(ArrayRef<MachineOperand> Cond) { |
| assert(!Cond.empty() && "Expected non-empty condition from analyzeBranch"); |
| // A normal br.cond simply has the condition code. |
| if (Cond[0].getImm() != -1) { |
| assert(Cond.size() == 1 && "Unknown Cond array format"); |
| return (AArch64CC::CondCode)(int)Cond[0].getImm(); |
| } |
| return AArch64CC::CondCode::Invalid; |
| } |
| |
| static bool isGreaterThan(AArch64CC::CondCode Cmp) { |
| return Cmp == AArch64CC::GT || Cmp == AArch64CC::HI; |
| } |
| |
| static bool isLessThan(AArch64CC::CondCode Cmp) { |
| return Cmp == AArch64CC::LT || Cmp == AArch64CC::LO; |
| } |
| |
| bool AArch64ConditionOptimizerImpl::tryOptimizePair(CmpCondPair &First, |
| CmpCondPair &Second) { |
| if (!((isGreaterThan(First.CC) || isLessThan(First.CC)) && |
| (isGreaterThan(Second.CC) || isLessThan(Second.CC)))) |
| return false; |
| |
| int FirstImmTrueValue = First.getImm(); |
| int SecondImmTrueValue = Second.getImm(); |
| |
| // Normalize immediate of CMN (ADDS) instructions |
| if (First.getOpc() == AArch64::ADDSWri || First.getOpc() == AArch64::ADDSXri) |
| FirstImmTrueValue = -FirstImmTrueValue; |
| if (Second.getOpc() == AArch64::ADDSWri || |
| Second.getOpc() == AArch64::ADDSXri) |
| SecondImmTrueValue = -SecondImmTrueValue; |
| |
| CmpInfo FirstAdj = getAdjustedCmpInfo(First.CmpMI, First.CC); |
| CmpInfo SecondAdj = getAdjustedCmpInfo(Second.CmpMI, Second.CC); |
| |
| if (((isGreaterThan(First.CC) && isLessThan(Second.CC)) || |
| (isLessThan(First.CC) && isGreaterThan(Second.CC))) && |
| std::abs(SecondImmTrueValue - FirstImmTrueValue) == 2) { |
| // This branch transforms machine instructions that correspond to |
| // |
| // 1) (a > {SecondImm} && ...) || (a < {FirstImm} && ...) |
| // 2) (a < {SecondImm} && ...) || (a > {FirstImm} && ...) |
| // |
| // into |
| // |
| // 1) (a >= {NewImm} && ...) || (a <= {NewImm} && ...) |
| // 2) (a <= {NewImm} && ...) || (a >= {NewImm} && ...) |
| |
| // Verify both adjustments converge to identical comparisons (same |
| // immediate and opcode). This ensures CSE can eliminate the duplicate. |
| if (FirstAdj.Imm != SecondAdj.Imm || FirstAdj.Opc != SecondAdj.Opc) |
| return false; |
| |
| LLVM_DEBUG(dbgs() << "Optimized (opposite): " |
| << AArch64CC::getCondCodeName(First.CC) << " #" |
| << First.getImm() << ", " |
| << AArch64CC::getCondCodeName(Second.CC) << " #" |
| << Second.getImm() << " -> " |
| << AArch64CC::getCondCodeName(FirstAdj.CC) << " #" |
| << FirstAdj.Imm << ", " |
| << AArch64CC::getCondCodeName(SecondAdj.CC) << " #" |
| << SecondAdj.Imm << '\n'); |
| applyCmpAdjustment(First, FirstAdj); |
| applyCmpAdjustment(Second, SecondAdj); |
| return true; |
| |
| } else if (((isGreaterThan(First.CC) && isGreaterThan(Second.CC)) || |
| (isLessThan(First.CC) && isLessThan(Second.CC))) && |
| std::abs(SecondImmTrueValue - FirstImmTrueValue) == 1) { |
| // This branch transforms machine instructions that correspond to |
| // |
| // 1) (a > {SecondImm} && ...) || (a > {FirstImm} && ...) |
| // 2) (a < {SecondImm} && ...) || (a < {FirstImm} && ...) |
| // |
| // into |
| // |
| // 1) (a <= {NewImm} && ...) || (a > {NewImm} && ...) |
| // 2) (a < {NewImm} && ...) || (a >= {NewImm} && ...) |
| |
| // GT -> GE transformation increases immediate value, so picking the |
| // smaller one; LT -> LE decreases immediate value so invert the choice. |
| bool AdjustFirst = (FirstImmTrueValue < SecondImmTrueValue); |
| if (isLessThan(First.CC)) |
| AdjustFirst = !AdjustFirst; |
| |
| CmpCondPair &Target = AdjustFirst ? Second : First; |
| CmpCondPair &ToChange = AdjustFirst ? First : Second; |
| CmpInfo &Adj = AdjustFirst ? FirstAdj : SecondAdj; |
| |
| // Verify the adjustment converges to the target's comparison (same |
| // immediate and opcode). This ensures CSE can eliminate the duplicate. |
| if (Adj.Imm != Target.getImm() || Adj.Opc != Target.getOpc()) |
| return false; |
| |
| LLVM_DEBUG(dbgs() << "Optimized (same-direction): " |
| << AArch64CC::getCondCodeName(ToChange.CC) << " #" |
| << ToChange.getImm() << " -> " |
| << AArch64CC::getCondCodeName(Adj.CC) << " #" << Adj.Imm |
| << '\n'); |
| applyCmpAdjustment(ToChange, Adj); |
| return true; |
| } |
| |
| // Other transformation cases almost never occur due to generation of < or > |
| // comparisons instead of <= and >=. |
| return false; |
| } |
| |
| bool AArch64ConditionOptimizerImpl::commitPendingPair( |
| std::optional<CmpCondPair> &PendingPair, |
| SmallDenseMap<Register, CmpCondPair> &PairsByReg) { |
| if (!PendingPair) |
| return false; |
| |
| Register Reg = PendingPair->CmpMI->getOperand(1).getReg(); |
| Register Key = Reg.isVirtual() ? TRI->lookThruCopyLike(Reg, MRI) : Reg; |
| |
| auto MatchingPair = PairsByReg.find(Key); |
| bool Changed = MatchingPair != PairsByReg.end() && |
| tryOptimizePair(MatchingPair->second, *PendingPair); |
| |
| PairsByReg[Key] = *PendingPair; |
| PendingPair = std::nullopt; |
| return Changed; |
| } |
| |
| // This function transforms cmps and their consuming conditionals (CmpCondPairs) |
| // 1. Same direction: when both conditions are the same (e.g. GT/GT or LT/LT) |
| // and immediates differ by 1 |
| // 2. Opposite direction: when both conditions are adjustable to a common middle |
| // (e.g., GT/LT) and immediates differ by 2. |
| // The compare instructions are made to match to enable CSE. |
| // All cmp/cond pairs within a basic block are examined |
| // |
| // Example transformation: |
| // cmp w8, #10 |
| // csinc w9, w0, w1, gt ; w9 = (w8 > 10) ? w0 : w1+1 |
| // cmp w8, #9 |
| // csinc w10, w0, w1, gt ; w10 = (w8 > 9) ? w0 : w1+1 |
| // |
| // Into: |
| // cmp w8, #10 |
| // csinc w9, w0, w1, gt ; w9 = (w8 > 10) ? w0 : w1+1 |
| // cmp w8, #10 ; <- CSE can remove the redundant cmp |
| // csinc w10, w0, w1, ge ; w10 = (w8 >= 10) ? w0 : w1+1 |
| // |
| bool AArch64ConditionOptimizerImpl::optimizeIntraBlock(MachineBasicBlock &MBB) { |
| SmallDenseMap<Register, CmpCondPair> PairsByReg; |
| std::optional<CmpCondPair> PendingPair; |
| MachineInstr *ActiveCmp = nullptr; |
| bool Changed = false; |
| |
| for (MachineInstr &MI : MBB) { |
| if (MI.isDebugInstr()) |
| continue; |
| |
| if (isCmpInstruction(MI.getOpcode()) && canAdjustCmp(MI)) { |
| Changed |= commitPendingPair(PendingPair, PairsByReg); |
| ActiveCmp = &MI; |
| continue; |
| } |
| |
| if (MI.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr)) { |
| // Non-CMP clobber: commit any pending pair and reset all state, since |
| // unknown flag state at this point invalidates all prior pairs |
| Changed |= commitPendingPair(PendingPair, PairsByReg); |
| ActiveCmp = nullptr; |
| PairsByReg.clear(); |
| continue; |
| } |
| |
| if (AArch64InstrInfo::findCondCodeUseOperandIdxForBranchOrSelect(MI) >= 0 && |
| !MI.isBranch()) { |
| if (PendingPair) { |
| // A second conditional consuming the same CMP would invalidate any |
| // optimization: modifying the CMP would silently change what both |
| // consumers compare against. Mark the CMP spent. |
| PendingPair = std::nullopt; |
| ActiveCmp = nullptr; |
| } else if (ActiveCmp) { |
| int CCOpIdx = |
| AArch64InstrInfo::findCondCodeUseOperandIdxForBranchOrSelect(MI); |
| assert(CCOpIdx >= 0 && "Unsupported conditional instruction"); |
| AArch64CC::CondCode CC = |
| (AArch64CC::CondCode)(int)MI.getOperand(CCOpIdx).getImm(); |
| PendingPair = CmpCondPair{ActiveCmp, &MI, CC}; |
| } |
| continue; |
| } |
| |
| if (MI.readsRegister(AArch64::NZCV, /*TRI=*/nullptr)) { |
| ActiveCmp = nullptr; |
| PendingPair = std::nullopt; |
| continue; |
| } |
| } |
| |
| // Only commit the final pending pair if NZCV doesn't live out: a cross-block |
| // consumer would be affected by any CMP adjustment we make. |
| if (!nzcvLivesOut(&MBB)) |
| Changed |= commitPendingPair(PendingPair, PairsByReg); |
| |
| return Changed; |
| } |
| |
| // Finds the last valid conditional consumer in MBB and returns it together |
| // with its condition code. Handles two cases: |
| // |
| // 1. Bcc terminator: if the block ends with a Bcc, analyzeBranch extracts |
| // the condition code directly from the branch operands. |
| // |
| // 2. Select-family instruction (CSET/CSEL/CSINC/CSINV/CSNEG): scans |
| // backward past terminators to find the sole non-branch NZCV consumer, |
| // verifying there is no interfering NZCV read or write between it and |
| // the CMP that produces the flags. |
| // |
| // Returns {nullptr, Invalid} if no suitable consumer is found or if any |
| // safety check fails. |
| std::pair<MachineInstr *, AArch64CC::CondCode> |
| AArch64ConditionOptimizerImpl::findCondConsumer(MachineBasicBlock *MBB) { |
| // Case 1: block ends with a Bcc terminator. |
| if (MachineInstr *BrMI = getBccTerminator(MBB)) { |
| SmallVector<MachineOperand, 4> CondOperands; |
| MachineBasicBlock *TBBDest = nullptr, *FBBDest = nullptr; |
| if (TII->analyzeBranch(*MBB, TBBDest, FBBDest, CondOperands)) |
| return {nullptr, AArch64CC::Invalid}; |
| AArch64CC::CondCode CC = parseCondCode(CondOperands); |
| if (CC == AArch64CC::Invalid) |
| return {nullptr, AArch64CC::Invalid}; |
| return {BrMI, CC}; |
| } |
| |
| // Case 2: no Bcc terminator — scan backward for a select-family instruction |
| // (CSET/CSEL/CSINC/CSINV/CSNEG) that is the sole NZCV consumer in the block. |
| MachineInstr *Found = nullptr; |
| AArch64CC::CondCode FoundCC = AArch64CC::Invalid; |
| |
| for (MachineInstr &MI : reverse(*MBB)) { |
| // Skip terminators (e.g. an unconditional branch at the end of the block) |
| // and debug instructions, which carry no real semantics. |
| if (MI.isTerminator() || MI.isDebugInstr()) |
| continue; |
| |
| if (!Found) { |
| // We have not yet found the select. Keep scanning backward. |
| |
| // If something writes NZCV before we find a select, the flags at that |
| // point are not from the CMP we are looking for. Stop searching. |
| if (MI.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr)) |
| return {nullptr, AArch64CC::Invalid}; |
| |
| // findCondCodeUseOperandIdxForBranchOrSelect returns the operand index |
| // of the condition code for any branch or select-family instruction, or |
| // -1 if the instruction does not use a condition code. |
| // We exclude branches because getBccTerminator already handles those; |
| // we only want non-branch conditionals: CSET, CSEL, CSINC, CSINV, CSNEG. |
| int CCOpIdx = |
| AArch64InstrInfo::findCondCodeUseOperandIdxForBranchOrSelect(MI); |
| if (CCOpIdx >= 0 && !MI.isBranch()) { |
| Found = &MI; |
| FoundCC = (AArch64CC::CondCode)(int)MI.getOperand(CCOpIdx).getImm(); |
| continue; |
| } |
| |
| // Any other instruction that reads NZCV (but is not a select) means the |
| // flags are consumed by something we do not understand. Stop searching. |
| if (MI.readsRegister(AArch64::NZCV, /*TRI=*/nullptr)) |
| return {nullptr, AArch64CC::Invalid}; |
| |
| } else { |
| // We already found a select. Now verify there is no second NZCV reader |
| // between the found select and the CMP. If there is, the CMP feeds two |
| // consumers and cannot be safely adjusted. |
| if (MI.readsRegister(AArch64::NZCV, /*TRI=*/nullptr)) |
| return {nullptr, AArch64CC::Invalid}; |
| |
| if (MI.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr)) { |
| // A CMP instruction is the flag producer we are looking for; stop |
| // scanning. findAdjustableCmp will locate it from CondMI. |
| if (isCmpInstruction(MI.getOpcode())) |
| break; |
| // Any other NZCV writer means the select is not reading from the CMP |
| // we would find further back. |
| return {nullptr, AArch64CC::Invalid}; |
| } |
| } |
| } |
| return {Found, FoundCC}; |
| } |
| |
| // Optimizes CMP+conditional pairs across two basic blocks in the dominator |
| // tree. The conditional consumer in each block may be a Bcc terminator or a |
| // select-family instruction (CSEL/CSET/CSINC/CSINV/CSNEG). |
| bool AArch64ConditionOptimizerImpl::optimizeCrossBlock(MachineBasicBlock &HBB) { |
| SmallVector<MachineOperand, 4> HeadCondOperands; |
| MachineBasicBlock *TBB = nullptr, *FBB = nullptr; |
| if (TII->analyzeBranch(HBB, TBB, FBB, HeadCondOperands)) { |
| return false; |
| } |
| |
| // Equivalence check is to skip loops. |
| if (!TBB || TBB == &HBB) { |
| return false; |
| } |
| |
| // Find the conditional consumer(Bcc or select-family) and its condition |
| // code in each block. findCondConsumer() handles both cases uniformly. |
| auto [HeadCondMI, HeadCondCode] = findCondConsumer(&HBB); |
| if (!HeadCondMI) |
| return false; |
| |
| auto [TrueCondMI, TrueCondCode] = findCondConsumer(TBB); |
| if (!TrueCondMI) |
| return false; |
| |
| // Since we may modify cmps in these blocks, make sure NZCV does not live out. |
| if (nzcvLivesOut(&HBB) || nzcvLivesOut(TBB)) |
| return false; |
| |
| // Find the CMPs controlling each conditional. |
| MachineInstr *HeadCmpMI = findAdjustableCmp(HeadCondMI); |
| MachineInstr *TrueCmpMI = findAdjustableCmp(TrueCondMI); |
| if (!HeadCmpMI || !TrueCmpMI) |
| return false; |
| |
| if (!registersMatch(HeadCmpMI, TrueCmpMI)) |
| return false; |
| |
| LLVM_DEBUG(dbgs() << "Checking cross-block pair: " |
| << AArch64CC::getCondCodeName(HeadCondCode) << " #" |
| << HeadCmpMI->getOperand(2).getImm() << ", " |
| << AArch64CC::getCondCodeName(TrueCondCode) << " #" |
| << TrueCmpMI->getOperand(2).getImm() << '\n'); |
| |
| CmpCondPair Head{HeadCmpMI, HeadCondMI, HeadCondCode}; |
| CmpCondPair True{TrueCmpMI, TrueCondMI, TrueCondCode}; |
| |
| return tryOptimizePair(Head, True); |
| } |
| |
| bool AArch64ConditionOptimizerLegacy::runOnMachineFunction( |
| MachineFunction &MF) { |
| if (skipFunction(MF.getFunction())) |
| return false; |
| MachineDominatorTree &MDT = |
| getAnalysis<MachineDominatorTreeWrapperPass>().getDomTree(); |
| return AArch64ConditionOptimizerImpl().run(MF, MDT); |
| } |
| |
| bool AArch64ConditionOptimizerImpl::run(MachineFunction &MF, |
| MachineDominatorTree &MDT) { |
| LLVM_DEBUG(dbgs() << "********** AArch64 Conditional Compares **********\n" |
| << "********** Function: " << MF.getName() << '\n'); |
| |
| TII = static_cast<const AArch64InstrInfo *>(MF.getSubtarget().getInstrInfo()); |
| TRI = MF.getSubtarget().getRegisterInfo(); |
| DomTree = &MDT; |
| MRI = &MF.getRegInfo(); |
| |
| bool Changed = false; |
| |
| // Visit blocks in dominator tree pre-order. The pre-order enables multiple |
| // cmp-conversions from the same head block. |
| // Note that updateDomTree() modifies the children of the DomTree node |
| // currently being visited. The df_iterator supports that; it doesn't look at |
| // child_begin() / child_end() until after a node has been visited. |
| for (MachineDomTreeNode *I : depth_first(DomTree)) { |
| MachineBasicBlock *HBB = I->getBlock(); |
| Changed |= optimizeIntraBlock(*HBB); |
| Changed |= optimizeCrossBlock(*HBB); |
| } |
| |
| return Changed; |
| } |
| |
| PreservedAnalyses |
| AArch64ConditionOptimizerPass::run(MachineFunction &MF, |
| MachineFunctionAnalysisManager &MFAM) { |
| auto &MDT = MFAM.getResult<MachineDominatorTreeAnalysis>(MF); |
| bool Changed = AArch64ConditionOptimizerImpl().run(MF, MDT); |
| if (!Changed) |
| return PreservedAnalyses::all(); |
| PreservedAnalyses PA = getMachineFunctionPassPreservedAnalyses(); |
| PA.preserveSet<CFGAnalyses>(); |
| return PA; |
| } |