Skip to content
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

[InstCombine] Fold select Cond, not X, X into Cond ^ X #90089

Closed
wants to merge 4 commits into from

Conversation

dtcxzyw
Copy link
Member

@dtcxzyw dtcxzyw commented Apr 25, 2024

See the following example:

define i1 @src(i64 %x, i1 %y) {
  %1526 = icmp ne i64 %x, 0
  %1527 = icmp eq i64 %x, 0
  %sel = select i1 %y, i1 %1526, i1 %1527
  ret i1 %sel
}

define i1 @tgt(i64 %x, i1 %y) {
  %1527 = icmp eq i64 %x, 0
  %sel = xor i1 %y, %1527
  ret i1 %sel
}

I find that this pattern is common in C/C++/Rust code base.
This patch folds select Cond, Y, X into Cond ^ X iff:

  1. X has the same type as Cond
  2. X is poison -> Y is poison
  3. X == !Y

Alive2: https://alive2.llvm.org/ce/z/hSmkHS

@llvmbot
Copy link
Collaborator

llvmbot commented Apr 25, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Yingwei Zheng (dtcxzyw)

Changes

See the following example:

define i1 @<!-- -->src(i64 %x, i1 %y) {
  %1526 = icmp ne i64 %x, 0
  %1527 = icmp eq i64 %x, 0
  %sel = select i1 %y, i1 %1526, i1 %1527
  ret i1 %sel
}

define i1 @<!-- -->tgt(i64 %x, i1 %y) {
  %1527 = icmp eq i64 %x, 0
  %sel = xor i1 %y, %1527
  ret i1 %sel
}

I find that this pattern is common in C/C++/Rust code base.
This patch folds select Cond, Y, X into Cond ^ X iff:

  1. X has the same type as Cond
  2. X is poison -> Y is poison
  3. X == !Y

Alive2: https://alive2.llvm.org/ce/z/hSmkHS


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp (+5)
  • (modified) llvm/test/Transforms/InstCombine/select-cmp.ll (+74)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 117eb7a1dcc933..69341c8d3f41cf 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -3985,5 +3985,10 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
     }
   }
 
+  if (CondVal->getType() == SI.getType() && impliesPoison(FalseVal, TrueVal) &&
+      isImpliedCondition(FalseVal, TrueVal, DL, /*LHSIsTrue=*/true) == false &&
+      isImpliedCondition(FalseVal, TrueVal, DL, /*LHSIsTrue=*/false) == true)
+    return BinaryOperator::CreateXor(CondVal, FalseVal);
+
   return nullptr;
 }
diff --git a/llvm/test/Transforms/InstCombine/select-cmp.ll b/llvm/test/Transforms/InstCombine/select-cmp.ll
index 711fac542179f8..3ec89af8d868c7 100644
--- a/llvm/test/Transforms/InstCombine/select-cmp.ll
+++ b/llvm/test/Transforms/InstCombine/select-cmp.ll
@@ -345,4 +345,78 @@ define i1 @icmp_no_common(i1 %c, i8 %x, i8 %y, i8 %z) {
   ret i1 %r
 }
 
+define i1 @test_select_inverse1(i64 %x, i1 %y) {
+; CHECK-LABEL: @test_select_inverse1(
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp eq i64 [[X:%.*]], 0
+; CHECK-NEXT:    [[SEL:%.*]] = xor i1 [[CMP2]], [[Y:%.*]]
+; CHECK-NEXT:    ret i1 [[SEL]]
+;
+  %cmp1 = icmp ne i64 %x, 0
+  %cmp2 = icmp eq i64 %x, 0
+  %sel = select i1 %y, i1 %cmp1, i1 %cmp2
+  ret i1 %sel
+}
+
+define i1 @test_select_inverse2(i64 %x, i1 %y) {
+; CHECK-LABEL: @test_select_inverse2(
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp slt i64 [[X:%.*]], 0
+; CHECK-NEXT:    [[SEL:%.*]] = xor i1 [[CMP2]], [[Y:%.*]]
+; CHECK-NEXT:    ret i1 [[SEL]]
+;
+  %cmp1 = icmp sgt i64 %x, -1
+  %cmp2 = icmp slt i64 %x, 0
+  %sel = select i1 %y, i1 %cmp1, i1 %cmp2
+  ret i1 %sel
+}
+
+define i1 @test_select_inverse3(ptr %x, i1 %y) {
+; CHECK-LABEL: @test_select_inverse3(
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp ne ptr [[X:%.*]], null
+; CHECK-NEXT:    [[SEL:%.*]] = xor i1 [[CMP2]], [[Y:%.*]]
+; CHECK-NEXT:    ret i1 [[SEL]]
+;
+  %cmp1 = icmp eq ptr %x, null
+  %cmp2 = icmp ne ptr %x, null
+  %sel = select i1 %y, i1 %cmp1, i1 %cmp2
+  ret i1 %sel
+}
+
+define i1 @test_select_inverse_fail(i64 %x, i1 %y) {
+; CHECK-LABEL: @test_select_inverse_fail(
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp sgt i64 [[X:%.*]], 0
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp slt i64 [[X]], 0
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[Y:%.*]], i1 [[CMP1]], i1 [[CMP2]]
+; CHECK-NEXT:    ret i1 [[SEL]]
+;
+  %cmp1 = icmp sgt i64 %x, 0
+  %cmp2 = icmp slt i64 %x, 0
+  %sel = select i1 %y, i1 %cmp1, i1 %cmp2
+  ret i1 %sel
+}
+
+define <2 x i1> @test_select_inverse_vec(<2 x i64> %x, <2 x i1> %y) {
+; CHECK-LABEL: @test_select_inverse_vec(
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp eq <2 x i64> [[X:%.*]], zeroinitializer
+; CHECK-NEXT:    [[SEL:%.*]] = xor <2 x i1> [[CMP2]], [[Y:%.*]]
+; CHECK-NEXT:    ret <2 x i1> [[SEL]]
+;
+  %cmp1 = icmp ne <2 x i64> %x, zeroinitializer
+  %cmp2 = icmp eq <2 x i64> %x, zeroinitializer
+  %sel = select <2 x i1> %y, <2 x i1> %cmp1, <2 x i1> %cmp2
+  ret <2 x i1> %sel
+}
+
+define <2 x i1> @test_select_inverse_vec_fail(<2 x i64> %x, i1 %y) {
+; CHECK-LABEL: @test_select_inverse_vec_fail(
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp ne <2 x i64> [[X:%.*]], zeroinitializer
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp eq <2 x i64> [[X]], zeroinitializer
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[Y:%.*]], <2 x i1> [[CMP1]], <2 x i1> [[CMP2]]
+; CHECK-NEXT:    ret <2 x i1> [[SEL]]
+;
+  %cmp1 = icmp ne <2 x i64> %x, zeroinitializer
+  %cmp2 = icmp eq <2 x i64> %x, zeroinitializer
+  %sel = select i1 %y, <2 x i1> %cmp1, <2 x i1> %cmp2
+  ret <2 x i1> %sel
+}
+
 declare void @use(i1)

dtcxzyw added a commit to dtcxzyw/llvm-opt-benchmark that referenced this pull request Apr 25, 2024
@@ -3985,5 +3985,10 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
}
}

if (CondVal->getType() == SI.getType() && impliesPoison(FalseVal, TrueVal) &&
isImpliedCondition(FalseVal, TrueVal, DL, /*LHSIsTrue=*/true) == false &&
isImpliedCondition(FalseVal, TrueVal, DL, /*LHSIsTrue=*/false) == true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it cover the motivating cases to check for icmp with same operands and inverted predicate?

This is a pretty weird check and I'm not sure if doing the poison check in one direction is sufficient here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, I guess it's fine as FalseVal is the value we're replacing with.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd still like an answer to this question. This is a very roundabout way to check whether the conditions are inverses of each other, and it does have some overhead (http://llvm-compile-time-tracker.com/compare.php?from=76a3be7c766bd55221c3d0d0a74c42f82c5d76ed&to=1731798e2ef457fb5cabd29ae054c700b8b13c14&stat=instructions%3Au). Not enough to make me overly concerned, about enough to contribute to the death by a thousand cuts :)

Copy link
Member Author

Choose a reason for hiding this comment

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

I can simplify the implementation to just handle select Cond, icmp X, C1, icmp X, C2, which covers all the motivating cases.

@@ -3985,5 +3985,10 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
}
}

if (CondVal->getType() == SI.getType() && impliesPoison(FalseVal, TrueVal) &&
isImpliedCondition(FalseVal, TrueVal, DL, /*LHSIsTrue=*/true) == false &&
isImpliedCondition(FalseVal, TrueVal, DL, /*LHSIsTrue=*/false) == true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldnt the check be (isImpliedCondition(..., true) ^ isImpliedCondition(..., false))?

Copy link
Member Author

Choose a reason for hiding this comment

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

When FVal implies TVal and !FVal -> !TVal, TVal must be the same as FVal. If not, we need a canonicalization for this pattern.

BTW, I tried isImpliedCondition(FalseVal, TrueVal, DL, /*LHSIsTrue=*/true) == true && isImpliedCondition(FalseVal, TrueVal, DL, /*LHSIsTrue=*/false) == false before submitting this patch. But it didn't improve the result.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add a comment that the inverse case doesn't naturally occur in code to avoid people re-testing / re-submitting.

@RSilicon
Copy link
Contributor

This food gives me a few more ideas. Particularly it takes me back to that rejected PR. Perhaps I could use this property somehow to fold expressions regarding bitwise nots of each other...

@goldsteinn
Copy link
Contributor

LGTM, wait for 1 more

@dtcxzyw
Copy link
Member Author

dtcxzyw commented May 18, 2024

TEST 'BOLT :: RISCV/unnamed-sym-no-entry.c' FAILED

The test failure is unrelated with this patch.

@goldsteinn
Copy link
Contributor

TEST 'BOLT :: RISCV/unnamed-sym-no-entry.c' FAILED

The test failure is unrelated with this patch.

I've also seen that test fail on a few buildbots.

@dtcxzyw
Copy link
Member Author

dtcxzyw commented May 27, 2024

Ping @nikic

@antoniofrighetto
Copy link
Contributor

LGTM!

@dtcxzyw
Copy link
Member Author

dtcxzyw commented Jun 4, 2024

Closing in favor of #93591.

@dtcxzyw dtcxzyw closed this Jun 4, 2024
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.

None yet

6 participants