[llvm] [AArch64] Peek through freeze in setcc if it has one_use, and is comparing against a constant (PR #153897)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 15 15:59:28 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-selectiondag
Author: AZero13 (AZero13)
<details>
<summary>Changes</summary>
Rather than having only brcond check, we should just allow all optimizations to see through the freeze if it is one-use.
---
Full diff: https://github.com/llvm/llvm-project/pull/153897.diff
4 Files Affected:
- (modified) llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp (+40-50)
- (modified) llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (+12-6)
- (modified) llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp (+11-4)
- (modified) llvm/test/CodeGen/AArch64/icmp-ult-eq-fold.ll (+19)
``````````diff
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 17703f58f2824..f4b3b4e1c8037 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -13544,6 +13544,46 @@ SDValue DAGCombiner::visitSETCC(SDNode *N) {
SDValue N0 = N->getOperand(0), N1 = N->getOperand(1);
SDLoc DL(N);
+ // Is 'X Cond C' always true or false?
+ auto IsAlwaysTrueOrFalse = [](ISD::CondCode Cond, ConstantSDNode *C) {
+ bool False = (Cond == ISD::SETULT && C->isZero()) ||
+ (Cond == ISD::SETLT && C->isMinSignedValue()) ||
+ (Cond == ISD::SETUGT && C->isAllOnes()) ||
+ (Cond == ISD::SETGT && C->isMaxSignedValue());
+ bool True = (Cond == ISD::SETULE && C->isAllOnes()) ||
+ (Cond == ISD::SETLE && C->isMaxSignedValue()) ||
+ (Cond == ISD::SETUGE && C->isZero()) ||
+ (Cond == ISD::SETGE && C->isMinSignedValue());
+ return True || False;
+ };
+
+ // Peek through freeze if it has one use. This is safe for comparisons that
+ // are not "always true" or "always false" when the operand is poison. Unsafe
+ // cases include:
+ // - X < 0 (SETULT), X >= 0 (SETUGE) - always false/true when X is poison
+ // - X < MIN_SIGNED (SETLT), X >= MIN_SIGNED (SETGE) - always false/true when
+ // X is poison
+ // - X > MAX_UNSIGNED (SETUGT), X <= MAX_UNSIGNED (SETULE) - always false/true
+ // when X is poison
+ // - X > MAX_SIGNED (SETGT), X <= MAX_SIGNED (SETLE) - always false/true when
+ // X is poison
+ if (N0->getOpcode() == ISD::FREEZE && N0.hasOneUse()) {
+ // Check if this comparison could be "always true" or "always false" when
+ // the operand is poison, which would make peeking through freeze unsafe.
+ ConstantSDNode *N1C = dyn_cast<ConstantSDNode>(N1);
+ if (!N1C || !IsAlwaysTrueOrFalse(Cond, N1C)) {
+ N0 = N0->getOperand(0);
+ }
+ }
+ if (N1->getOpcode() == ISD::FREEZE && N1.hasOneUse()) {
+ // Check if this comparison could be "always true" or "always false" when
+ // the operand is poison, which would make peeking through freeze unsafe.
+ ConstantSDNode *N0C = dyn_cast<ConstantSDNode>(N0);
+ if (!N0C || !IsAlwaysTrueOrFalse(ISD::getSetCCSwappedOperands(Cond), N0C)) {
+ N1 = N1->getOperand(0);
+ }
+ }
+
if (SDValue Combined = SimplifySetCC(VT, N0, N1, Cond, DL, !PreferSetCC)) {
// If we prefer to have a setcc, and we don't, we'll try our best to
// recreate one using rebuildSetCC.
@@ -19287,56 +19327,6 @@ SDValue DAGCombiner::visitBRCOND(SDNode *N) {
N1->getOperand(0), N2, N->getFlags());
}
- // Variant of the previous fold where there is a SETCC in between:
- // BRCOND(SETCC(FREEZE(X), CONST, Cond))
- // =>
- // BRCOND(FREEZE(SETCC(X, CONST, Cond)))
- // =>
- // BRCOND(SETCC(X, CONST, Cond))
- // This is correct if FREEZE(X) has one use and SETCC(FREEZE(X), CONST, Cond)
- // isn't equivalent to true or false.
- // For example, SETCC(FREEZE(X), -128, SETULT) cannot be folded to
- // FREEZE(SETCC(X, -128, SETULT)) because X can be poison.
- if (N1->getOpcode() == ISD::SETCC && N1.hasOneUse()) {
- SDValue S0 = N1->getOperand(0), S1 = N1->getOperand(1);
- ISD::CondCode Cond = cast<CondCodeSDNode>(N1->getOperand(2))->get();
- ConstantSDNode *S0C = dyn_cast<ConstantSDNode>(S0);
- ConstantSDNode *S1C = dyn_cast<ConstantSDNode>(S1);
- bool Updated = false;
-
- // Is 'X Cond C' always true or false?
- auto IsAlwaysTrueOrFalse = [](ISD::CondCode Cond, ConstantSDNode *C) {
- bool False = (Cond == ISD::SETULT && C->isZero()) ||
- (Cond == ISD::SETLT && C->isMinSignedValue()) ||
- (Cond == ISD::SETUGT && C->isAllOnes()) ||
- (Cond == ISD::SETGT && C->isMaxSignedValue());
- bool True = (Cond == ISD::SETULE && C->isAllOnes()) ||
- (Cond == ISD::SETLE && C->isMaxSignedValue()) ||
- (Cond == ISD::SETUGE && C->isZero()) ||
- (Cond == ISD::SETGE && C->isMinSignedValue());
- return True || False;
- };
-
- if (S0->getOpcode() == ISD::FREEZE && S0.hasOneUse() && S1C) {
- if (!IsAlwaysTrueOrFalse(Cond, S1C)) {
- S0 = S0->getOperand(0);
- Updated = true;
- }
- }
- if (S1->getOpcode() == ISD::FREEZE && S1.hasOneUse() && S0C) {
- if (!IsAlwaysTrueOrFalse(ISD::getSetCCSwappedOperands(Cond), S0C)) {
- S1 = S1->getOperand(0);
- Updated = true;
- }
- }
-
- if (Updated)
- return DAG.getNode(
- ISD::BRCOND, SDLoc(N), MVT::Other, Chain,
- DAG.getSetCC(SDLoc(N1), N1->getValueType(0), S0, S1, Cond), N2,
- N->getFlags());
- }
-
// If N is a constant we could fold this into a fallthrough or unconditional
// branch. However that doesn't happen very often in normal code, because
// Instcombine/SimplifyCFG should have handled the available opportunities.
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 2072e48914ae6..f4ca96768a6f2 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -3604,19 +3604,25 @@ static SDValue emitComparison(SDValue LHS, SDValue RHS, ISD::CondCode CC,
Opcode = AArch64ISD::ADDS;
LHS = LHS.getOperand(1);
} else if (isNullConstant(RHS) && !isUnsignedIntSetCC(CC)) {
- if (LHS.getOpcode() == ISD::AND) {
+ SDValue AndOp = LHS;
+ // Peek through freeze to find AND operation
+ if (LHS.getOpcode() == ISD::FREEZE && LHS.hasOneUse()) {
+ AndOp = LHS.getOperand(0);
+ }
+
+ if (AndOp.getOpcode() == ISD::AND) {
// Similarly, (CMP (and X, Y), 0) can be implemented with a TST
// (a.k.a. ANDS) except that the flags are only guaranteed to work for one
// of the signed comparisons.
const SDValue ANDSNode =
DAG.getNode(AArch64ISD::ANDS, DL, DAG.getVTList(VT, FlagsVT),
- LHS.getOperand(0), LHS.getOperand(1));
- // Replace all users of (and X, Y) with newly generated (ands X, Y)
- DAG.ReplaceAllUsesWith(LHS, ANDSNode);
+ AndOp.getOperand(0), AndOp.getOperand(1));
+ // Replace all users of the AND operation with newly generated (ands X, Y)
+ DAG.ReplaceAllUsesWith(AndOp, ANDSNode);
return ANDSNode.getValue(1);
- } else if (LHS.getOpcode() == AArch64ISD::ANDS) {
+ } else if (AndOp.getOpcode() == AArch64ISD::ANDS) {
// Use result of ANDS
- return LHS.getValue(1);
+ return AndOp.getValue(1);
}
}
diff --git a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
index ee34a85a5b507..170050b934934 100644
--- a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
+++ b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
@@ -5203,15 +5203,22 @@ MachineInstr *AArch64InstructionSelector::tryFoldIntegerCompare(
// Produce this if the compare is signed:
//
// tst x, y
- if (!CmpInst::isUnsigned(P) && LHSDef &&
- LHSDef->getOpcode() == TargetOpcode::G_AND) {
+ if (!CmpInst::isUnsigned(P) && LHSDef) {
+ MachineInstr *AndDef = LHSDef;
+ // Peek through freeze to find AND operation
+ if (LHSDef->getOpcode() == TargetOpcode::G_FREEZE &&
+ MRI.hasOneUse(LHSDef->getOperand(0).getReg())) {
+ AndDef = getDefIgnoringCopies(LHSDef->getOperand(0).getReg(), MRI);
+ }
+
+ if (AndDef && AndDef->getOpcode() == TargetOpcode::G_AND) {
// Make sure that the RHS is 0.
auto ValAndVReg = getIConstantVRegValWithLookThrough(RHS.getReg(), MRI);
if (!ValAndVReg || ValAndVReg->Value != 0)
return nullptr;
- return emitTST(LHSDef->getOperand(1),
- LHSDef->getOperand(2), MIRBuilder);
+ return emitTST(AndDef->getOperand(1),
+ AndDef->getOperand(2), MIRBuilder);
}
return nullptr;
diff --git a/llvm/test/CodeGen/AArch64/icmp-ult-eq-fold.ll b/llvm/test/CodeGen/AArch64/icmp-ult-eq-fold.ll
index 33c5ba7987974..74f6f7724c390 100644
--- a/llvm/test/CodeGen/AArch64/icmp-ult-eq-fold.ll
+++ b/llvm/test/CodeGen/AArch64/icmp-ult-eq-fold.ll
@@ -161,6 +161,25 @@ define i1 @lt64_u16_and_23(i64 %0) {
ret i1 %3
}
+define i1 @test_disjoint(i1 %0, i32 %1, i32 %2) {
+; CHECK-LABEL: test_disjoint:
+; CHECK: // %bb.0: // %entry
+; CHECK-NEXT: orr w8, w2, #0x800000
+; CHECK-NEXT: lsr w8, w8, w1
+; CHECK-NEXT: tst w8, #0x1
+; CHECK-NEXT: cset w8, eq
+; CHECK-NEXT: orr w8, w0, w8
+; CHECK-NEXT: and w0, w8, #0x1
+; CHECK-NEXT: ret
+entry:
+ %3 = or disjoint i32 %2, 8388608
+ %4 = shl nuw i32 1, %1
+ %5 = and i32 %3, %4
+ %6 = icmp eq i32 %5, 0
+ %7 = select i1 %0, i1 true, i1 %6
+ ret i1 %7
+}
+
; negative test
define i1 @lt3_u8(i8 %0) {
; CHECK-LABEL: lt3_u8:
``````````
</details>
https://github.com/llvm/llvm-project/pull/153897
More information about the llvm-commits
mailing list