blob: e4164f091faf6bc2cb021c8e7bdeed4979bc94a1 [file] [edit]
//=- 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;
}