Skip to content

Commit 796a186

Browse files
authored
[AArch64] Extend optimizeCrossBlock() to handle select-family instructions (#208369)
* The cross-block condition optimizer previously only handled blocks ending with a Bcc terminator. This patch extends optimizeCrossBlock() to also recognize CSEL, CSET, CSINC, CSINV, and CSNEG as conditional consumers by introducing findCondConsumer(), which unifies consumer discovery for both block roles: trying a Bcc terminator first and falling back to a reverse scan for the sole NZCV-consuming select-family instruction if no Bcc is present. * This enables CMP adjustment and CSE elimination across all four head/true-successor combinations: Bcc+Bcc (original), Select+Bcc, Bcc+Select, and Select+Select. * Tests are added in aarch64-condopt-cross-block-select.mir covering all three new combinations along with negative cases for NZCV liveness and mismatched registers. Assisted by: Claude for mir test cases
1 parent d37fa5a commit 796a186

2 files changed

Lines changed: 675 additions & 21 deletions

File tree

llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp

Lines changed: 108 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,15 @@
5555
//
5656
// TODO: maybe handle TBNZ/TBZ the same way as CMP when used instead for "a < 0"
5757
// TODO: For cross-block:
58-
// - handle other conditional instructions (e.g. CSET)
5958
// - allow second branching to be anything if it doesn't require adjusting
6059
//
60+
// Cross-block optimizeCrossBlock() handles four head/true-successor
61+
// combinations:
62+
// Bcc (head) + Bcc (true) -- original case
63+
// Select (head) + Bcc (true) -- head ends with CSEL/CSET/etc.
64+
// Bcc (head) + Select (true) -- true-successor ends with CSEL/CSET/etc.
65+
// Select (head) + Select (true) -- both blocks end with a select
66+
//
6167
//===----------------------------------------------------------------------===//
6268

6369
#include "AArch64.h"
@@ -136,6 +142,8 @@ class AArch64ConditionOptimizerImpl {
136142
bool tryOptimizePair(CmpCondPair &First, CmpCondPair &Second);
137143
bool optimizeIntraBlock(MachineBasicBlock &MBB);
138144
bool optimizeCrossBlock(MachineBasicBlock &HBB);
145+
std::pair<MachineInstr *, AArch64CC::CondCode>
146+
findCondConsumer(MachineBasicBlock *MBB);
139147
};
140148

141149
class AArch64ConditionOptimizerLegacy : public MachineFunctionPass {
@@ -603,7 +611,94 @@ bool AArch64ConditionOptimizerImpl::optimizeIntraBlock(MachineBasicBlock &MBB) {
603611
return Changed;
604612
}
605613

606-
// Optimizes CMP+Bcc pairs across two basic blocks in the dominator tree.
614+
// Finds the last valid conditional consumer in MBB and returns it together
615+
// with its condition code. Handles two cases:
616+
//
617+
// 1. Bcc terminator: if the block ends with a Bcc, analyzeBranch extracts
618+
// the condition code directly from the branch operands.
619+
//
620+
// 2. Select-family instruction (CSET/CSEL/CSINC/CSINV/CSNEG): scans
621+
// backward past terminators to find the sole non-branch NZCV consumer,
622+
// verifying there is no interfering NZCV read or write between it and
623+
// the CMP that produces the flags.
624+
//
625+
// Returns {nullptr, Invalid} if no suitable consumer is found or if any
626+
// safety check fails.
627+
std::pair<MachineInstr *, AArch64CC::CondCode>
628+
AArch64ConditionOptimizerImpl::findCondConsumer(MachineBasicBlock *MBB) {
629+
// Case 1: block ends with a Bcc terminator.
630+
if (MachineInstr *BrMI = getBccTerminator(MBB)) {
631+
SmallVector<MachineOperand, 4> CondOperands;
632+
MachineBasicBlock *TBBDest = nullptr, *FBBDest = nullptr;
633+
if (TII->analyzeBranch(*MBB, TBBDest, FBBDest, CondOperands))
634+
return {nullptr, AArch64CC::Invalid};
635+
AArch64CC::CondCode CC = parseCondCode(CondOperands);
636+
if (CC == AArch64CC::Invalid)
637+
return {nullptr, AArch64CC::Invalid};
638+
return {BrMI, CC};
639+
}
640+
641+
// Case 2: no Bcc terminator — scan backward for a select-family instruction
642+
// (CSET/CSEL/CSINC/CSINV/CSNEG) that is the sole NZCV consumer in the block.
643+
MachineInstr *Found = nullptr;
644+
AArch64CC::CondCode FoundCC = AArch64CC::Invalid;
645+
646+
for (MachineInstr &MI : reverse(*MBB)) {
647+
// Skip terminators (e.g. an unconditional branch at the end of the block)
648+
// and debug instructions, which carry no real semantics.
649+
if (MI.isTerminator() || MI.isDebugInstr())
650+
continue;
651+
652+
if (!Found) {
653+
// We have not yet found the select. Keep scanning backward.
654+
655+
// If something writes NZCV before we find a select, the flags at that
656+
// point are not from the CMP we are looking for. Stop searching.
657+
if (MI.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr))
658+
return {nullptr, AArch64CC::Invalid};
659+
660+
// findCondCodeUseOperandIdxForBranchOrSelect returns the operand index
661+
// of the condition code for any branch or select-family instruction, or
662+
// -1 if the instruction does not use a condition code.
663+
// We exclude branches because getBccTerminator already handles those;
664+
// we only want non-branch conditionals: CSET, CSEL, CSINC, CSINV, CSNEG.
665+
int CCOpIdx =
666+
AArch64InstrInfo::findCondCodeUseOperandIdxForBranchOrSelect(MI);
667+
if (CCOpIdx >= 0 && !MI.isBranch()) {
668+
Found = &MI;
669+
FoundCC = (AArch64CC::CondCode)(int)MI.getOperand(CCOpIdx).getImm();
670+
continue;
671+
}
672+
673+
// Any other instruction that reads NZCV (but is not a select) means the
674+
// flags are consumed by something we do not understand. Stop searching.
675+
if (MI.readsRegister(AArch64::NZCV, /*TRI=*/nullptr))
676+
return {nullptr, AArch64CC::Invalid};
677+
678+
} else {
679+
// We already found a select. Now verify there is no second NZCV reader
680+
// between the found select and the CMP. If there is, the CMP feeds two
681+
// consumers and cannot be safely adjusted.
682+
if (MI.readsRegister(AArch64::NZCV, /*TRI=*/nullptr))
683+
return {nullptr, AArch64CC::Invalid};
684+
685+
if (MI.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr)) {
686+
// A CMP instruction is the flag producer we are looking for; stop
687+
// scanning. findAdjustableCmp will locate it from CondMI.
688+
if (isCmpInstruction(MI.getOpcode()))
689+
break;
690+
// Any other NZCV writer means the select is not reading from the CMP
691+
// we would find further back.
692+
return {nullptr, AArch64CC::Invalid};
693+
}
694+
}
695+
}
696+
return {Found, FoundCC};
697+
}
698+
699+
// Optimizes CMP+conditional pairs across two basic blocks in the dominator
700+
// tree. The conditional consumer in each block may be a Bcc terminator or a
701+
// select-family instruction (CSEL/CSET/CSINC/CSINV/CSNEG).
607702
bool AArch64ConditionOptimizerImpl::optimizeCrossBlock(MachineBasicBlock &HBB) {
608703
SmallVector<MachineOperand, 4> HeadCondOperands;
609704
MachineBasicBlock *TBB = nullptr, *FBB = nullptr;
@@ -616,45 +711,37 @@ bool AArch64ConditionOptimizerImpl::optimizeCrossBlock(MachineBasicBlock &HBB) {
616711
return false;
617712
}
618713

619-
SmallVector<MachineOperand, 4> TrueCondOperands;
620-
MachineBasicBlock *TBB_TBB = nullptr, *TBB_FBB = nullptr;
621-
if (TII->analyzeBranch(*TBB, TBB_TBB, TBB_FBB, TrueCondOperands)) {
714+
// Find the conditional consumer(Bcc or select-family) and its condition
715+
// code in each block. findCondConsumer() handles both cases uniformly.
716+
auto [HeadCondMI, HeadCondCode] = findCondConsumer(&HBB);
717+
if (!HeadCondMI)
622718
return false;
623-
}
624719

625-
MachineInstr *HeadBrMI = getBccTerminator(&HBB);
626-
MachineInstr *TrueBrMI = getBccTerminator(TBB);
627-
if (!HeadBrMI || !TrueBrMI)
720+
auto [TrueCondMI, TrueCondCode] = findCondConsumer(TBB);
721+
if (!TrueCondMI)
628722
return false;
629723

630724
// Since we may modify cmps in these blocks, make sure NZCV does not live out.
631725
if (nzcvLivesOut(&HBB) || nzcvLivesOut(TBB))
632726
return false;
633727

634-
// Find the CMPs controlling each branch
635-
MachineInstr *HeadCmpMI = findAdjustableCmp(HeadBrMI);
636-
MachineInstr *TrueCmpMI = findAdjustableCmp(TrueBrMI);
728+
// Find the CMPs controlling each conditional.
729+
MachineInstr *HeadCmpMI = findAdjustableCmp(HeadCondMI);
730+
MachineInstr *TrueCmpMI = findAdjustableCmp(TrueCondMI);
637731
if (!HeadCmpMI || !TrueCmpMI)
638732
return false;
639733

640734
if (!registersMatch(HeadCmpMI, TrueCmpMI))
641735
return false;
642736

643-
AArch64CC::CondCode HeadCondCode = parseCondCode(HeadCondOperands);
644-
AArch64CC::CondCode TrueCondCode = parseCondCode(TrueCondOperands);
645-
if (HeadCondCode == AArch64CC::CondCode::Invalid ||
646-
TrueCondCode == AArch64CC::CondCode::Invalid) {
647-
return false;
648-
}
649-
650737
LLVM_DEBUG(dbgs() << "Checking cross-block pair: "
651738
<< AArch64CC::getCondCodeName(HeadCondCode) << " #"
652739
<< HeadCmpMI->getOperand(2).getImm() << ", "
653740
<< AArch64CC::getCondCodeName(TrueCondCode) << " #"
654741
<< TrueCmpMI->getOperand(2).getImm() << '\n');
655742

656-
CmpCondPair Head{HeadCmpMI, HeadBrMI, HeadCondCode};
657-
CmpCondPair True{TrueCmpMI, TrueBrMI, TrueCondCode};
743+
CmpCondPair Head{HeadCmpMI, HeadCondMI, HeadCondCode};
744+
CmpCondPair True{TrueCmpMI, TrueCondMI, TrueCondCode};
658745

659746
return tryOptimizePair(Head, True);
660747
}

0 commit comments

Comments
 (0)