[llvm] ec06b38 - [InstCombine] canonicalize 'not' ops before logical shifts

Sanjay Patel via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 22 06:39:37 PDT 2020


Author: Sanjay Patel
Date: 2020-08-22T09:38:13-04:00
New Revision: ec06b381304140b2553cfdfae5a063f39c5c59ff

URL: https://github.com/llvm/llvm-project/commit/ec06b381304140b2553cfdfae5a063f39c5c59ff
DIFF: https://github.com/llvm/llvm-project/commit/ec06b381304140b2553cfdfae5a063f39c5c59ff.diff

LOG: [InstCombine] canonicalize 'not' ops before logical shifts

This reverses the existing transform that would uniformly canonicalize any 'xor' after any shift. In the case of logical shifts, that turns a 'not' into an arbitrary 'xor' with constant, and that's probably not as good for analysis, SCEV, or codegen.

The SCEV motivating case is discussed in:
http://bugs.llvm.org/PR47136

There's an analysis motivating case at:
http://bugs.llvm.org/PR38781

I did draft a patch that would do the same for 'ashr' but that's questionable because it's just swapping the position of a 'not' and uncovers at least 2 missing folds that we would probably need to deal with as preliminary steps.

Alive proofs:
https://rise4fun.com/Alive/BBV

  Name: shift right of 'not'
  Pre: C2 == (-1 u>> C1)
  %a = lshr i8 %x, C1
  %r = xor i8 %a, C2
  =>
  %n = xor i8 %x, -1
  %r = lshr i8 %n, C1

  Name: shift left of 'not'
  Pre: C2 == (-1 << C1)
  %a = shl i8 %x, C1
  %r = xor i8 %a, C2
  =>
  %n = xor i8 %x, -1
  %r = shl i8 %n, C1

  Name: ashr of 'not'
  %a = ashr i8 %x, C1
  %r = xor i8 %a, -1
  =>
  %n = xor i8 %x, -1
  %r = ashr i8 %n, C1

Differential Revision: https://reviews.llvm.org/D86243

Added: 
    

Modified: 
    llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
    llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
    llvm/test/Transforms/InstCombine/2010-11-01-lshr-mask.ll
    llvm/test/Transforms/InstCombine/and-xor-merge.ll
    llvm/test/Transforms/InstCombine/compare-signs.ll
    llvm/test/Transforms/InstCombine/icmp.ll
    llvm/test/Transforms/InstCombine/xor.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 3abdf4072589..85d84fb5e5db 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -3267,6 +3267,24 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
       if (match(Op0, m_Or(m_Value(X), m_APInt(C))) &&
           MaskedValueIsZero(X, *C, 0, &I))
         return BinaryOperator::CreateXor(X, ConstantInt::get(Ty, *C ^ *RHSC));
+
+      // If RHSC is inverting the remaining bits of shifted X,
+      // canonicalize to a 'not' before the shift to help SCEV and codegen:
+      // (X << C) ^ RHSC --> ~X << C
+      if (match(Op0, m_OneUse(m_Shl(m_Value(X), m_APInt(C)))) &&
+          *RHSC == APInt::getAllOnesValue(Ty->getScalarSizeInBits()).shl(*C)) {
+        Value *NotX = Builder.CreateNot(X);
+        return BinaryOperator::CreateShl(NotX, ConstantInt::get(Ty, *C));
+      }
+      // (X >>u C) ^ RHSC --> ~X >>u C
+      if (match(Op0, m_OneUse(m_LShr(m_Value(X), m_APInt(C)))) &&
+          *RHSC == APInt::getAllOnesValue(Ty->getScalarSizeInBits()).lshr(*C)) {
+        Value *NotX = Builder.CreateNot(X);
+        return BinaryOperator::CreateLShr(NotX, ConstantInt::get(Ty, *C));
+      }
+      // TODO: We could handle 'ashr' here as well. That would be matching
+      //       a 'not' op and moving it before the shift. Doing that requires
+      //       preventing the inverse fold in canShiftBinOpWithConstantRHS().
     }
   }
 

diff  --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index e6d1b31e8df5..983b45cbb11f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -667,9 +667,12 @@ static bool canShiftBinOpWithConstantRHS(BinaryOperator &Shift,
   case Instruction::Add:
     return Shift.getOpcode() == Instruction::Shl;
   case Instruction::Or:
-  case Instruction::Xor:
   case Instruction::And:
     return true;
+  case Instruction::Xor:
+    // Do not change a 'not' of logical shift because that would create a normal
+    // 'xor'. The 'not' is likely better for analysis, SCEV, and codegen.
+    return !(Shift.isLogicalShift() && match(BO, m_Not(m_Value())));
   }
 }
 

diff  --git a/llvm/test/Transforms/InstCombine/2010-11-01-lshr-mask.ll b/llvm/test/Transforms/InstCombine/2010-11-01-lshr-mask.ll
index 7d75e16793a7..f367b47fe05b 100644
--- a/llvm/test/Transforms/InstCombine/2010-11-01-lshr-mask.ll
+++ b/llvm/test/Transforms/InstCombine/2010-11-01-lshr-mask.ll
@@ -5,9 +5,9 @@
 define i32 @main(i32 %argc) {
 ; CHECK-LABEL: @main(
 ; CHECK-NEXT:    [[T3151:%.*]] = trunc i32 [[ARGC:%.*]] to i8
-; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[T3151]], 5
-; CHECK-NEXT:    [[T4126:%.*]] = and i8 [[TMP1]], 64
-; CHECK-NEXT:    [[T4127:%.*]] = xor i8 [[T4126]], 64
+; CHECK-NEXT:    [[T3163:%.*]] = xor i8 [[T3151]], -1
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[T3163]], 5
+; CHECK-NEXT:    [[T4127:%.*]] = and i8 [[TMP1]], 64
 ; CHECK-NEXT:    [[T4086:%.*]] = zext i8 [[T4127]] to i32
 ; CHECK-NEXT:    ret i32 [[T4086]]
 ;

diff  --git a/llvm/test/Transforms/InstCombine/and-xor-merge.ll b/llvm/test/Transforms/InstCombine/and-xor-merge.ll
index 3d86754a6635..fdb6e8365ef6 100644
--- a/llvm/test/Transforms/InstCombine/and-xor-merge.ll
+++ b/llvm/test/Transforms/InstCombine/and-xor-merge.ll
@@ -28,11 +28,9 @@ define i32 @test2(i32 %x, i32 %y, i32 %z) {
 
 define i32 @PR38761(i32 %a, i32 %b) {
 ; CHECK-LABEL: @PR38761(
-; CHECK-NEXT:    [[A_LOBIT:%.*]] = lshr i32 [[A:%.*]], 31
-; CHECK-NEXT:    [[A_LOBIT_NOT:%.*]] = xor i32 [[A_LOBIT]], 1
-; CHECK-NEXT:    [[B_LOBIT:%.*]] = lshr i32 [[B:%.*]], 31
-; CHECK-NEXT:    [[B_LOBIT_NOT:%.*]] = xor i32 [[B_LOBIT]], -1
-; CHECK-NEXT:    [[AND:%.*]] = and i32 [[A_LOBIT_NOT]], [[B_LOBIT_NOT]]
+; CHECK-NEXT:    [[B_LOBIT_NOT1_DEMORGAN:%.*]] = or i32 [[B:%.*]], [[A:%.*]]
+; CHECK-NEXT:    [[B_LOBIT_NOT1:%.*]] = xor i32 [[B_LOBIT_NOT1_DEMORGAN]], -1
+; CHECK-NEXT:    [[AND:%.*]] = lshr i32 [[B_LOBIT_NOT1]], 31
 ; CHECK-NEXT:    ret i32 [[AND]]
 ;
   %a.lobit = lshr i32 %a, 31

diff  --git a/llvm/test/Transforms/InstCombine/compare-signs.ll b/llvm/test/Transforms/InstCombine/compare-signs.ll
index c6c56f2361ec..99a117adbb90 100644
--- a/llvm/test/Transforms/InstCombine/compare-signs.ll
+++ b/llvm/test/Transforms/InstCombine/compare-signs.ll
@@ -6,8 +6,8 @@
 define i32 @test1(i32 %a, i32 %b) nounwind readnone {
 ; CHECK-LABEL: @test1(
 ; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[B:%.*]], [[A:%.*]]
-; CHECK-NEXT:    [[DOTLOBIT:%.*]] = lshr i32 [[TMP1]], 31
-; CHECK-NEXT:    [[DOTLOBIT_NOT:%.*]] = xor i32 [[DOTLOBIT]], 1
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], -1
+; CHECK-NEXT:    [[DOTLOBIT_NOT:%.*]] = lshr i32 [[TMP2]], 31
 ; CHECK-NEXT:    ret i32 [[DOTLOBIT_NOT]]
 ;
   %t0 = icmp sgt i32 %a, -1
@@ -36,8 +36,8 @@ define i32 @test2(i32 %a, i32 %b) nounwind readnone {
 define i32 @test3(i32 %a, i32 %b) nounwind readnone {
 ; CHECK-LABEL: @test3(
 ; CHECK-NEXT:    [[T2_UNSHIFTED:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
-; CHECK-NEXT:    [[T2_UNSHIFTED_LOBIT:%.*]] = lshr i32 [[T2_UNSHIFTED]], 31
-; CHECK-NEXT:    [[T2_UNSHIFTED_LOBIT_NOT:%.*]] = xor i32 [[T2_UNSHIFTED_LOBIT]], 1
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[T2_UNSHIFTED]], -1
+; CHECK-NEXT:    [[T2_UNSHIFTED_LOBIT_NOT:%.*]] = lshr i32 [[TMP1]], 31
 ; CHECK-NEXT:    ret i32 [[T2_UNSHIFTED_LOBIT_NOT]]
 ;
   %t0 = lshr i32 %a, 31
@@ -68,8 +68,8 @@ define <2 x i32> @test3vec(<2 x i32> %a, <2 x i32> %b) nounwind readnone {
 define i32 @test3i(i32 %a, i32 %b) nounwind readnone {
 ; CHECK-LABEL: @test3i(
 ; CHECK-NEXT:    [[T01:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
-; CHECK-NEXT:    [[TMP1:%.*]] = lshr i32 [[T01]], 31
-; CHECK-NEXT:    [[T4:%.*]] = xor i32 [[TMP1]], 1
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[T01]], -1
+; CHECK-NEXT:    [[T4:%.*]] = lshr i32 [[TMP1]], 31
 ; CHECK-NEXT:    ret i32 [[T4]]
 ;
   %t0 = lshr i32 %a, 29

diff  --git a/llvm/test/Transforms/InstCombine/icmp.ll b/llvm/test/Transforms/InstCombine/icmp.ll
index 7addc85c4c8b..3b223d71f9d4 100644
--- a/llvm/test/Transforms/InstCombine/icmp.ll
+++ b/llvm/test/Transforms/InstCombine/icmp.ll
@@ -25,8 +25,8 @@ define <2 x i32> @test1vec(<2 x i32> %X) {
 
 define i32 @test2(i32 %X) {
 ; CHECK-LABEL: @test2(
-; CHECK-NEXT:    [[X_LOBIT:%.*]] = lshr i32 [[X:%.*]], 31
-; CHECK-NEXT:    [[X_LOBIT_NOT:%.*]] = xor i32 [[X_LOBIT]], 1
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[X:%.*]], -1
+; CHECK-NEXT:    [[X_LOBIT_NOT:%.*]] = lshr i32 [[TMP1]], 31
 ; CHECK-NEXT:    ret i32 [[X_LOBIT_NOT]]
 ;
   %a = icmp ult i32 %X, -2147483648
@@ -36,8 +36,8 @@ define i32 @test2(i32 %X) {
 
 define <2 x i32> @test2vec(<2 x i32> %X) {
 ; CHECK-LABEL: @test2vec(
-; CHECK-NEXT:    [[X_LOBIT:%.*]] = lshr <2 x i32> [[X:%.*]], <i32 31, i32 31>
-; CHECK-NEXT:    [[X_LOBIT_NOT:%.*]] = xor <2 x i32> [[X_LOBIT]], <i32 1, i32 1>
+; CHECK-NEXT:    [[TMP1:%.*]] = xor <2 x i32> [[X:%.*]], <i32 -1, i32 -1>
+; CHECK-NEXT:    [[X_LOBIT_NOT:%.*]] = lshr <2 x i32> [[TMP1]], <i32 31, i32 31>
 ; CHECK-NEXT:    ret <2 x i32> [[X_LOBIT_NOT]]
 ;
   %a = icmp ult <2 x i32> %X, <i32 -2147483648, i32 -2147483648>

diff  --git a/llvm/test/Transforms/InstCombine/xor.ll b/llvm/test/Transforms/InstCombine/xor.ll
index 5db898552562..312b0125f626 100644
--- a/llvm/test/Transforms/InstCombine/xor.ll
+++ b/llvm/test/Transforms/InstCombine/xor.ll
@@ -1015,8 +1015,8 @@ define i32 @not_is_canonical(i32 %x, i32 %y) {
 
 define i8 @not_shl(i8 %x) {
 ; CHECK-LABEL: @not_shl(
-; CHECK-NEXT:    [[A:%.*]] = shl i8 [[X:%.*]], 7
-; CHECK-NEXT:    [[R:%.*]] = xor i8 [[A]], -128
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i8 [[X:%.*]], -1
+; CHECK-NEXT:    [[R:%.*]] = shl i8 [[TMP1]], 7
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %a = shl i8 %x, 7
@@ -1026,8 +1026,8 @@ define i8 @not_shl(i8 %x) {
 
 define <2 x i8> @not_shl_vec(<2 x i8> %x) {
 ; CHECK-LABEL: @not_shl_vec(
-; CHECK-NEXT:    [[A:%.*]] = shl <2 x i8> [[X:%.*]], <i8 5, i8 5>
-; CHECK-NEXT:    [[R:%.*]] = xor <2 x i8> [[A]], <i8 -32, i8 -32>
+; CHECK-NEXT:    [[TMP1:%.*]] = xor <2 x i8> [[X:%.*]], <i8 -1, i8 -1>
+; CHECK-NEXT:    [[R:%.*]] = shl <2 x i8> [[TMP1]], <i8 5, i8 5>
 ; CHECK-NEXT:    ret <2 x i8> [[R]]
 ;
   %a = shl <2 x i8> %x, <i8 5, i8 5>
@@ -1035,6 +1035,8 @@ define <2 x i8> @not_shl_vec(<2 x i8> %x) {
   ret <2 x i8> %r
 }
 
+; negative test
+
 define i8 @not_shl_extra_use(i8 %x) {
 ; CHECK-LABEL: @not_shl_extra_use(
 ; CHECK-NEXT:    [[A:%.*]] = shl i8 [[X:%.*]], 7
@@ -1048,6 +1050,8 @@ define i8 @not_shl_extra_use(i8 %x) {
   ret i8 %r
 }
 
+; negative test
+
 define i8 @not_shl_wrong_const(i8 %x) {
 ; CHECK-LABEL: @not_shl_wrong_const(
 ; CHECK-NEXT:    [[A:%.*]] = shl i8 [[X:%.*]], 6
@@ -1061,8 +1065,8 @@ define i8 @not_shl_wrong_const(i8 %x) {
 
 define i8 @not_lshr(i8 %x) {
 ; CHECK-LABEL: @not_lshr(
-; CHECK-NEXT:    [[A:%.*]] = lshr i8 [[X:%.*]], 5
-; CHECK-NEXT:    [[R:%.*]] = xor i8 [[A]], 7
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i8 [[X:%.*]], -1
+; CHECK-NEXT:    [[R:%.*]] = lshr i8 [[TMP1]], 5
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %a = lshr i8 %x, 5
@@ -1072,8 +1076,8 @@ define i8 @not_lshr(i8 %x) {
 
 define <2 x i8> @not_lshr_vec(<2 x i8> %x) {
 ; CHECK-LABEL: @not_lshr_vec(
-; CHECK-NEXT:    [[A:%.*]] = lshr <2 x i8> [[X:%.*]], <i8 7, i8 7>
-; CHECK-NEXT:    [[R:%.*]] = xor <2 x i8> [[A]], <i8 1, i8 1>
+; CHECK-NEXT:    [[TMP1:%.*]] = xor <2 x i8> [[X:%.*]], <i8 -1, i8 -1>
+; CHECK-NEXT:    [[R:%.*]] = lshr <2 x i8> [[TMP1]], <i8 7, i8 7>
 ; CHECK-NEXT:    ret <2 x i8> [[R]]
 ;
   %a = lshr <2 x i8> %x, <i8 7, i8 7>
@@ -1081,6 +1085,8 @@ define <2 x i8> @not_lshr_vec(<2 x i8> %x) {
   ret <2 x i8> %r
 }
 
+; negative test
+
 define i8 @not_lshr_extra_use(i8 %x) {
 ; CHECK-LABEL: @not_lshr_extra_use(
 ; CHECK-NEXT:    [[A:%.*]] = lshr i8 [[X:%.*]], 5
@@ -1094,6 +1100,8 @@ define i8 @not_lshr_extra_use(i8 %x) {
   ret i8 %r
 }
 
+; negative test
+
 define i8 @not_lshr_wrong_const(i8 %x) {
 ; CHECK-LABEL: @not_lshr_wrong_const(
 ; CHECK-NEXT:    [[A:%.*]] = lshr i8 [[X:%.*]], 5
@@ -1116,6 +1124,8 @@ define i8 @ashr_not(i8 %x) {
   ret i8 %r
 }
 
+; Unlike the logicial shifts, 'not' is canonicalized after ashr.
+
 define i8 @not_ashr(i8 %x) {
 ; CHECK-LABEL: @not_ashr(
 ; CHECK-NEXT:    [[A:%.*]] = ashr i8 [[X:%.*]], 5


        


More information about the llvm-commits mailing list