Skip to content

[InstCombine] Improve coverage of foldSelectValueEquivalence for non-constants #94719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

goldsteinn
Copy link
Contributor

@goldsteinn goldsteinn commented Jun 7, 2024

  • [InstCombine] Add tests for expanding foldSelectValueEquivalence; NFC
  • [InstCombine] Improve coverage of foldSelectValueEquivalence for non-constants

There are a few more cases we can easily cover.

  • If f(Y) simplifies to Y
  • If replace X with Y makes X one-use (and make Y non one-use).

These all require that Y is not undef

@goldsteinn goldsteinn requested a review from nikic as a code owner June 7, 2024 04:28
@goldsteinn goldsteinn requested a review from dtcxzyw June 7, 2024 04:28
@llvmbot
Copy link
Member

llvmbot commented Jun 7, 2024

@llvm/pr-subscribers-llvm-transforms

Author: None (goldsteinn)

Changes
  • [InstCombine] Add tests for expanding foldSelectValueEquivalence; NFC
  • [InstCombine] Improve coverage of foldSelectValueEquivalence for non-constants

Full diff: https://github.com/llvm/llvm-project/pull/94719.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp (+47-6)
  • (added) llvm/test/Transforms/InstCombine/select-cmp-eq-op-fold.ll (+184)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index b04e0b300f95a..dc0e7a032198b 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -1300,6 +1300,8 @@ Instruction *InstCombinerImpl::foldSelectValueEquivalence(SelectInst &Sel,
     if (TrueVal == OldOp)
       return nullptr;
 
+    bool NewOpNeverUndef = false;
+
     if (Value *V = simplifyWithOpReplaced(TrueVal, OldOp, NewOp, SQ,
                                           /* AllowRefinement=*/true)) {
       // Need some guarantees about the new simplified op to ensure we don't inf
@@ -1309,12 +1311,51 @@ Instruction *InstCombinerImpl::foldSelectValueEquivalence(SelectInst &Sel,
           isGuaranteedNotToBeUndef(V, SQ.AC, &Sel, &DT))
         return replaceOperand(Sel, Swapped ? 2 : 1, V);
 
-      // If NewOp is a constant and OldOp is not replace iff NewOp doesn't
-      // contain and undef elements.
-      if (match(NewOp, m_ImmConstant())) {
-        if (isGuaranteedNotToBeUndef(NewOp, SQ.AC, &Sel, &DT))
-          return replaceOperand(Sel, Swapped ? 2 : 1, V);
+      // We can't do any further replacement if NewOp may be undef.
+      if (!isGuaranteedNotToBeUndef(NewOp, SQ.AC, &Sel, &DT))
         return nullptr;
+
+      // If NewOp is a constant, replace.
+      if (match(NewOp, m_ImmConstant()))
+        return replaceOperand(Sel, Swapped ? 2 : 1, V);
+
+      // If we simplified the TrueArm -> NewOp then replace.
+      // This handles things like `select`/`min`/`max`/`or`/`and`/etc...
+      if (NewOp == V)
+        return replaceOperand(Sel, Swapped ? 2 : 1, V);
+
+      NewOpNeverUndef = true;
+    }
+
+    // If we can't simplify, but we will either:
+    //  1) Create a new binop where both ops are NewOp i.e (add x, y) is "worse"
+    //     than (add y, y) in this case, wait until the second call so we don't
+    //     miss a one-use simplification.
+    //  2) Create a new one-use instruction.
+    // proceed.
+    if (TrueVal->hasOneUse() && isa<Instruction>(TrueVal)) {
+      bool BinOpMatch =
+          match(TrueVal, m_c_BinOp(m_Specific(OldOp), m_Specific(NewOp)));
+      bool NewOneUse = isa<Instruction>(OldOp) && OldOp->hasNUses(2) &&
+                       (!isa<Instruction>(NewOp) || !NewOp->hasOneUse());
+      if (BinOpMatch || NewOneUse) {
+        bool Replaced = false;
+        auto *TrueIns = cast<Instruction>(TrueVal);
+        for (unsigned OpIdx = 0; OpIdx < TrueIns->getNumOperands(); ++OpIdx) {
+          if (TrueIns->getOperand(OpIdx) != OldOp)
+            continue;
+          // Need to ensure NewOp is noundef (same reason as above). Wait
+          // until the last moment to do this check as it can be relatively
+          // expensive.
+          if (!NewOpNeverUndef &&
+              !isGuaranteedNotToBeUndef(NewOp, SQ.AC, &Sel, &DT))
+            break;
+          NewOpNeverUndef = true;
+          TrueIns->setOperand(OpIdx, NewOp);
+          Replaced = true;
+        }
+        if (Replaced)
+          return replaceOperand(Sel, Swapped ? 2 : 1, TrueIns);
       }
     }
 
@@ -1327,7 +1368,7 @@ Instruction *InstCombinerImpl::foldSelectValueEquivalence(SelectInst &Sel,
     // FIXME: Support vectors.
     if (OldOp == CmpLHS && match(NewOp, m_ImmConstant()) &&
         !match(OldOp, m_ImmConstant()) && !Cmp.getType()->isVectorTy() &&
-        isGuaranteedNotToBeUndef(NewOp, SQ.AC, &Sel, &DT))
+        (NewOpNeverUndef || isGuaranteedNotToBeUndef(NewOp, SQ.AC, &Sel, &DT)))
       if (replaceInInstruction(TrueVal, OldOp, NewOp))
         return &Sel;
     return nullptr;
diff --git a/llvm/test/Transforms/InstCombine/select-cmp-eq-op-fold.ll b/llvm/test/Transforms/InstCombine/select-cmp-eq-op-fold.ll
new file mode 100644
index 0000000000000..d2a5c71025370
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/select-cmp-eq-op-fold.ll
@@ -0,0 +1,184 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare void @use.i1(i1)
+declare void @use.i8(i8)
+define i8 @replace_with_y_noundef(i8 %x, i8 noundef %y, i8 %z) {
+; CHECK-LABEL: @replace_with_y_noundef(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[Y]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %cmp = icmp eq i8 %x, %y
+  %and = and i8 %x, %y
+  %sel = select i1 %cmp, i8 %and, i8 %z
+  ret i8 %sel
+}
+
+define i8 @replace_with_x_noundef(i8 noundef %x, i8 %y, i8 %z) {
+; CHECK-LABEL: @replace_with_x_noundef(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    call void @use.i1(i1 [[CMP]])
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[Z:%.*]], i8 [[X]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %cmp = icmp ne i8 %x, %y
+  call void @use.i1(i1 %cmp)
+  %and = or i8 %x, %y
+  %sel = select i1 %cmp, i8 %z, i8 %and
+  ret i8 %sel
+}
+
+define i8 @replace_with_x_maybe_undef_fail(i8 %x, i8 %y, i8 %z) {
+; CHECK-LABEL: @replace_with_x_maybe_undef_fail(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    call void @use.i1(i1 [[CMP]])
+; CHECK-NEXT:    [[AND:%.*]] = or i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[Z:%.*]], i8 [[AND]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %cmp = icmp ne i8 %x, %y
+  call void @use.i1(i1 %cmp)
+  %and = or i8 %x, %y
+  %sel = select i1 %cmp, i8 %z, i8 %and
+  ret i8 %sel
+}
+
+define i8 @replace_with_y_for_new_oneuse(i8 noundef %xx, i8 noundef %y, i8 %z) {
+; CHECK-LABEL: @replace_with_y_for_new_oneuse(
+; CHECK-NEXT:    [[X:%.*]] = mul i8 [[XX:%.*]], 13
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], [[Y:%.*]]
+; CHECK-NEXT:    [[ADD:%.*]] = shl nuw i8 [[Y]], 1
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[ADD]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %x = mul i8 %xx, 13
+  %cmp = icmp eq i8 %x, %y
+  %add = add nuw i8 %x, %y
+  %sel = select i1 %cmp, i8 %add, i8 %z
+  ret i8 %sel
+}
+
+define i8 @replace_with_y_for_new_oneuse2(i8 %xx, i8 noundef %y, i8 %z, i8 %q) {
+; CHECK-LABEL: @replace_with_y_for_new_oneuse2(
+; CHECK-NEXT:    [[X:%.*]] = mul i8 [[XX:%.*]], 13
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], [[Y:%.*]]
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[Y]], [[Q:%.*]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[ADD]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %x = mul i8 %xx, 13
+  %cmp = icmp eq i8 %x, %y
+  %add = add nuw i8 %x, %q
+  %sel = select i1 %cmp, i8 %add, i8 %z
+  ret i8 %sel
+}
+
+define i8 @replace_with_x_for_new_oneuse(i8 noundef %xx, i8 noundef %yy, i8 %z, i8 %w) {
+; CHECK-LABEL: @replace_with_x_for_new_oneuse(
+; CHECK-NEXT:    [[X:%.*]] = mul i8 [[XX:%.*]], 13
+; CHECK-NEXT:    [[Y:%.*]] = add i8 [[YY:%.*]], [[W:%.*]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[MUL:%.*]] = mul i8 [[X]], [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[MUL]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %x = mul i8 %xx, 13
+  %y = add i8 %yy, %w
+  %cmp = icmp eq i8 %x, %y
+  %mul = mul i8 %x, %y
+  %sel = select i1 %cmp, i8 %mul, i8 %z
+  ret i8 %sel
+}
+
+define i8 @replace_with_x_for_new_oneuse2(i8 noundef %xx, i8 %yy, i8 %z, i8 %w, i8 %q) {
+; CHECK-LABEL: @replace_with_x_for_new_oneuse2(
+; CHECK-NEXT:    [[X:%.*]] = mul i8 [[XX:%.*]], 13
+; CHECK-NEXT:    [[Y:%.*]] = add i8 [[YY:%.*]], [[W:%.*]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[MUL:%.*]] = mul i8 [[Y]], [[Q:%.*]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[MUL]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %x = mul i8 %xx, 13
+  %y = add i8 %yy, %w
+  %cmp = icmp eq i8 %x, %y
+  %mul = mul i8 %q, %y
+  %sel = select i1 %cmp, i8 %mul, i8 %z
+  ret i8 %sel
+}
+
+define i8 @replace_with_x_for_simple_binop(i8 noundef %xx, i8 %yy, i8 %z, i8 %w) {
+; CHECK-LABEL: @replace_with_x_for_simple_binop(
+; CHECK-NEXT:    [[X:%.*]] = mul i8 [[XX:%.*]], 13
+; CHECK-NEXT:    [[Y:%.*]] = add i8 [[YY:%.*]], [[W:%.*]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[MUL:%.*]] = mul i8 [[X]], [[X]]
+; CHECK-NEXT:    call void @use.i8(i8 [[Y]])
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[MUL]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %x = mul i8 %xx, 13
+  %y = add i8 %yy, %w
+  %cmp = icmp eq i8 %x, %y
+  %mul = mul i8 %x, %y
+  call void @use.i8(i8 %y)
+  %sel = select i1 %cmp, i8 %mul, i8 %z
+  ret i8 %sel
+}
+
+define i8 @replace_with_none_for_new_oneuse_fail_maybe_undef(i8 %xx, i8 %y, i8 %z) {
+; CHECK-LABEL: @replace_with_none_for_new_oneuse_fail_maybe_undef(
+; CHECK-NEXT:    [[X:%.*]] = mul i8 [[XX:%.*]], 13
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], [[Y:%.*]]
+; CHECK-NEXT:    [[MUL:%.*]] = mul i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[MUL]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %x = mul i8 %xx, 13
+  %cmp = icmp eq i8 %x, %y
+  %mul = mul i8 %x, %y
+  %sel = select i1 %cmp, i8 %mul, i8 %z
+  ret i8 %sel
+}
+
+define i8 @replace_with_y_for_simple_binop(i8 %x, i8 noundef %y, i8 %z) {
+; CHECK-LABEL: @replace_with_y_for_simple_binop(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[MUL:%.*]] = mul nsw i8 [[Y]], [[Y]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[MUL]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %cmp = icmp eq i8 %x, %y
+  %mul = mul nsw i8 %x, %y
+  %sel = select i1 %cmp, i8 %mul, i8 %z
+  ret i8 %sel
+}
+
+define i8 @replace_with_y_for_simple_binop_fail_multiuse(i8 %x, i8 noundef %y, i8 %z) {
+; CHECK-LABEL: @replace_with_y_for_simple_binop_fail_multiuse(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[MUL:%.*]] = mul nsw i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[MUL]], i8 [[Z:%.*]]
+; CHECK-NEXT:    call void @use.i8(i8 [[MUL]])
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %cmp = icmp eq i8 %x, %y
+  %mul = mul nsw i8 %x, %y
+  %sel = select i1 %cmp, i8 %mul, i8 %z
+  call void @use.i8(i8 %mul)
+  ret i8 %sel
+}
+
+define i8 @replace_with_y_for_simple_binop_fail(i8 %x, i8 noundef %y, i8 %z, i8 %q) {
+; CHECK-LABEL: @replace_with_y_for_simple_binop_fail(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[MUL:%.*]] = mul nsw i8 [[X]], [[Q:%.*]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i8 [[MUL]], i8 [[Z:%.*]]
+; CHECK-NEXT:    ret i8 [[SEL]]
+;
+  %cmp = icmp eq i8 %x, %y
+  %mul = mul nsw i8 %x, %q
+  %sel = select i1 %cmp, i8 %mul, i8 %z
+  ret i8 %sel
+}

@goldsteinn goldsteinn changed the title goldsteinn/select equiv non const [InstCombine] Improve coverage of foldSelectValueEquivalence for non-constants Jun 7, 2024
@goldsteinn
Copy link
Contributor Author

Going to run csmith for a few days w/ this. Will ping back results probably monday.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is worth the complication. The only part of this I would consider is the NewOp == V case, though I'm having some trouble convincing myself that this really can't lead to a combine loop.

@goldsteinn
Copy link
Contributor Author

I don't think this is worth the complication. The only part of this I would consider is the NewOp == V case, though I'm having some trouble convincing myself that this really can't lead to a combine loop.

Is the second half so complicated? I guess I can yet again split it too a knew PR so we can test it.

Regarding NewOp == V been running csmith over night ~10^5 cases so far.
But I can add some gurantees to the code.

Think the following constraints make it pretty trivially impossible.

  1. TrueVal/OldOp 1-Use.
  2. NewOp not an instruction

Although the more I think about, the more I think it can't lead to inf loop.
I think for an inf loop, we would need a cycle in the IR, the SSA / dominating uses
pretty much prohibits that.

@goldsteinn
Copy link
Contributor Author

Ran ~5million csmith tests. No inf loops or bad codegen. Although note only got different resulting IR ~100 times over all the tests.

dtcxzyw added a commit to dtcxzyw/llvm-opt-benchmark that referenced this pull request Jun 12, 2024
@dtcxzyw
Copy link
Member

dtcxzyw commented Jun 12, 2024

I don't think this is worth the complication. The only part of this I would consider is the NewOp == V case, though I'm having some trouble convincing myself that this really can't lead to a combine loop.

I agree.

@goldsteinn goldsteinn force-pushed the goldsteinn/select-equiv-non-const branch from ba2fbcf to 78d332b Compare June 22, 2024 00:07
@goldsteinn
Copy link
Contributor Author

Dropped the use-reduction code. Think for this to inf loop you would need to violate SSA i.e: f(x, y) -> y -> y(x, z) -> x. For us to go from x -> y, then y would need to both be dominated by x and dominate x.

…n-constants

If f(Y) simplifies to Y, replace with Y. This requires Y to be
non-undef.
@goldsteinn goldsteinn force-pushed the goldsteinn/select-equiv-non-const branch from 78d332b to ed53a45 Compare June 22, 2024 16:20
Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

AlexisPerry pushed a commit to llvm-project-tlp/llvm-project that referenced this pull request Jul 9, 2024
…n-constants

If f(Y) simplifies to Y, replace with Y. This requires Y to be
non-undef.

Closes llvm#94719
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants