[llvm] [InstCombine] Canoncalize complex boolean expressions into ~((y | z) ^ x) (PR #149530)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 21 05:38:31 PDT 2025


https://github.com/yafet-a updated https://github.com/llvm/llvm-project/pull/149530

>From 0db68ca0f6f0f97475b19e350af5a28cbd7f049a Mon Sep 17 00:00:00 2001
From: Yafet Beyene <ybeyene at nvidia.com>
Date: Sat, 19 Jul 2025 15:02:06 -0700
Subject: [PATCH 1/2] [InstCombine] Add pre-commit tests for boolean
 canonicalization (NFC)

---
 llvm/test/Transforms/InstCombine/pr97044.ll | 99 +++++++++++++++++++++
 1 file changed, 99 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/pr97044.ll

diff --git a/llvm/test/Transforms/InstCombine/pr97044.ll b/llvm/test/Transforms/InstCombine/pr97044.ll
new file mode 100644
index 0000000000000..e61fb76ab43ba
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/pr97044.ll
@@ -0,0 +1,99 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+; Tests for GitHub issue #97044 - Boolean expression canonicalization
+define i32 @test0_4way_or(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test0_4way_or(
+; CHECK-NEXT:    [[NOT:%.*]] = xor i32 [[Z:%.*]], -1
+; CHECK-NEXT:    [[AND:%.*]] = and i32 [[Y:%.*]], [[NOT]]
+; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[AND]], [[X:%.*]]
+; CHECK-NEXT:    [[NOT2:%.*]] = xor i32 [[Y]], -1
+; CHECK-NEXT:    [[AND3:%.*]] = and i32 [[X]], [[NOT2]]
+; CHECK-NEXT:    [[AND4:%.*]] = and i32 [[AND3]], [[Z]]
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[AND1]], [[AND4]]
+; CHECK-NEXT:    [[AND7_DEMORGAN:%.*]] = or i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[AND9_DEMORGAN:%.*]] = or i32 [[AND7_DEMORGAN]], [[Z]]
+; CHECK-NEXT:    [[AND9:%.*]] = xor i32 [[AND9_DEMORGAN]], -1
+; CHECK-NEXT:    [[OR10:%.*]] = or i32 [[OR]], [[AND9]]
+; CHECK-NEXT:    [[AND11:%.*]] = and i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[AND12:%.*]] = and i32 [[AND11]], [[Z]]
+; CHECK-NEXT:    [[OR13:%.*]] = or i32 [[OR10]], [[AND12]]
+; CHECK-NEXT:    ret i32 [[OR13]]
+;
+  %not = xor i32 %z, -1
+  %and = and i32 %y, %not
+  %and1 = and i32 %and, %x
+  %not2 = xor i32 %y, -1
+  %and3 = and i32 %x, %not2
+  %and4 = and i32 %and3, %z
+  %or = or i32 %and1, %and4
+  %not5 = xor i32 %x, -1
+  %not6 = xor i32 %y, -1
+  %and7 = and i32 %not5, %not6
+  %not8 = xor i32 %z, -1
+  %and9 = and i32 %and7, %not8
+  %or10 = or i32 %or, %and9
+  %and11 = and i32 %x, %y
+  %and12 = and i32 %and11, %z
+  %or13 = or i32 %or10, %and12
+  ret i32 %or13
+}
+define i32 @test1_xor_pattern(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test1_xor_pattern(
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[AND4_DEMORGAN:%.*]] = or i32 [[TMP1]], [[Z:%.*]]
+; CHECK-NEXT:    [[AND8:%.*]] = and i32 [[Z]], [[X]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[AND4_DEMORGAN]], -1
+; CHECK-NEXT:    [[XOR:%.*]] = or i32 [[AND8]], [[TMP2]]
+; CHECK-NEXT:    ret i32 [[XOR]]
+;
+  %not = xor i32 %z, -1
+  %and = and i32 %x, %y
+  %not1 = xor i32 %x, -1
+  %not2 = xor i32 %y, -1
+  %and3 = and i32 %not1, %not2
+  %or = or i32 %and, %and3
+  %and4 = and i32 %not, %or
+  %and5 = and i32 %x, %y
+  %and6 = and i32 %x, %not2
+  %or7 = or i32 %and5, %and6
+  %and8 = and i32 %z, %or7
+  %xor = xor i32 %and4, %and8
+  ret i32 %xor
+}
+define i32 @test2_nested_xor(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test2_nested_xor(
+; CHECK-NEXT:    [[NOT7:%.*]] = xor i32 [[Y:%.*]], -1
+; CHECK-NEXT:    [[AND8:%.*]] = and i32 [[Z:%.*]], [[NOT7]]
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[X:%.*]], [[AND8]]
+; CHECK-NEXT:    ret i32 [[TMP1]]
+;
+  %and = and i32 %x, %y
+  %not = xor i32 %x, -1
+  %not1 = xor i32 %y, -1
+  %and2 = and i32 %not, %not1
+  %or = or i32 %and, %and2
+  %and3 = and i32 %x, %y
+  %not4 = xor i32 %y, -1
+  %and5 = and i32 %x, %not4
+  %or6 = or i32 %and3, %and5
+  %xor = xor i32 %or, %or6
+  %not7 = xor i32 %y, -1
+  %and8 = and i32 %z, %not7
+  %and9 = and i32 %xor, %and8
+  %xor10 = xor i32 %or, %and9
+  %xor11 = xor i32 %xor10, %y
+  %xor12 = xor i32 %xor11, -1
+  ret i32 %xor12
+}
+define i32 @test3_already_optimal(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test3_already_optimal(
+; CHECK-NEXT:    [[OR:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[XOR:%.*]] = xor i32 [[OR]], [[X:%.*]]
+; CHECK-NEXT:    [[NOT:%.*]] = xor i32 [[XOR]], -1
+; CHECK-NEXT:    ret i32 [[NOT]]
+;
+  %or = or i32 %y, %z
+  %xor = xor i32 %or, %x
+  %not = xor i32 %xor, -1
+  ret i32 %not
+}

>From 60f76f9ba68d7c5198d4115066602f0ddf8dd09b Mon Sep 17 00:00:00 2001
From: Yafet Beyene <ybeyene at nvidia.com>
Date: Mon, 21 Jul 2025 05:08:39 -0700
Subject: [PATCH 2/2] [InstCombine] Optimised expressions in issue #97044

---
 .../InstCombine/InstCombineAndOrXor.cpp       | 56 +++++++++++++++++++
 llvm/test/Transforms/InstCombine/pr97044.ll   | 33 ++++-------
 2 files changed, 66 insertions(+), 23 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 3beda6bc5ba38..74a5815eddd13 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -3780,6 +3780,43 @@ Instruction *InstCombinerImpl::visitOr(BinaryOperator &I) {
     return replaceInstUsesWith(I, V);
 
   Value *Op0 = I.getOperand(0), *Op1 = I.getOperand(1);
+
+  // ((X & Y & ~Z) | (X & ~Y & Z) | (~X & ~Y &~Z) | (X & Y &Z)) -> ~((Y | Z) ^
+  // X)
+  {
+    Value *X, *Y, *Z;
+    Value *Term1, *Term2, *XAndYAndZ;
+    if (match(&I,
+              m_Or(m_Or(m_Value(Term1), m_Value(Term2)), m_Value(XAndYAndZ))) &&
+        match(XAndYAndZ, m_And(m_And(m_Value(X), m_Value(Y)), m_Value(Z)))) {
+      Value *YOrZ = Builder.CreateOr(Y, Z);
+      Value *YOrZXorX = Builder.CreateXor(YOrZ, X);
+      return BinaryOperator::CreateNot(YOrZXorX);
+    }
+  }
+
+  // (Z & X) | ~((Y ^ X) | Z) -> ~((Y | Z) ^ X)
+  {
+    Value *X, *Y, *Z;
+    Value *ZAndX, *NotPattern;
+
+    if (match(&I, m_c_Or(m_Value(ZAndX), m_Value(NotPattern))) &&
+        match(ZAndX, m_c_And(m_Value(Z), m_Value(X)))) {
+      
+      Value *YXorXOrZ;
+      if (match(NotPattern, m_Not(m_Value(YXorXOrZ)))) {
+        Value *YXorX;
+        if (match(YXorXOrZ, m_c_Or(m_Value(YXorX), m_Specific(Z))) &&
+            match(YXorX, m_c_Xor(m_Value(Y), m_Specific(X)))) {
+          
+          Value *YOrZ = Builder.CreateOr(Y, Z);
+          Value *YOrZXorX = Builder.CreateXor(YOrZ, X);
+          return BinaryOperator::CreateNot(YOrZXorX);
+        }
+      }
+    }
+  }
+  
   Type *Ty = I.getType();
   if (Ty->isIntOrIntVectorTy(1)) {
     if (auto *SI0 = dyn_cast<SelectInst>(Op0)) {
@@ -5191,6 +5228,25 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
     }
   }
 
+  // ((X & Y) | (~X & ~Y)) ^ (Z & (((X & Y) | (~X & ~Y)) ^ ((X & Y) | (X &
+  // ~Y)))) -> ~((Y | Z) ^ X)
+  if (match(Op1, m_AllOnes())) {
+    Value *X, *Y, *Z;
+    Value *XorWithY;
+    if (match(Op0, m_Xor(m_Value(XorWithY), m_Value(Y)))) {
+      Value *ZAndNotY;
+      if (match(XorWithY, m_Xor(m_Value(X), m_Value(ZAndNotY)))) {
+        Value *NotY;
+        if (match(ZAndNotY, m_And(m_Value(Z), m_Value(NotY))) &&
+            match(NotY, m_Not(m_Specific(Y)))) {
+          Value *YOrZ = Builder.CreateOr(Y, Z);
+          Value *YOrZXorX = Builder.CreateXor(YOrZ, X);
+          return BinaryOperator::CreateNot(YOrZXorX);
+        }
+      }
+    }
+  }
+
   if (auto *LHS = dyn_cast<ICmpInst>(I.getOperand(0)))
     if (auto *RHS = dyn_cast<ICmpInst>(I.getOperand(1)))
       if (Value *V = foldXorOfICmps(LHS, RHS, I))
diff --git a/llvm/test/Transforms/InstCombine/pr97044.ll b/llvm/test/Transforms/InstCombine/pr97044.ll
index e61fb76ab43ba..9c9bf9aface25 100644
--- a/llvm/test/Transforms/InstCombine/pr97044.ll
+++ b/llvm/test/Transforms/InstCombine/pr97044.ll
@@ -3,20 +3,9 @@
 ; Tests for GitHub issue #97044 - Boolean expression canonicalization
 define i32 @test0_4way_or(i32 %x, i32 %y, i32 %z) {
 ; CHECK-LABEL: @test0_4way_or(
-; CHECK-NEXT:    [[NOT:%.*]] = xor i32 [[Z:%.*]], -1
-; CHECK-NEXT:    [[AND:%.*]] = and i32 [[Y:%.*]], [[NOT]]
-; CHECK-NEXT:    [[AND1:%.*]] = and i32 [[AND]], [[X:%.*]]
-; CHECK-NEXT:    [[NOT2:%.*]] = xor i32 [[Y]], -1
-; CHECK-NEXT:    [[AND3:%.*]] = and i32 [[X]], [[NOT2]]
-; CHECK-NEXT:    [[AND4:%.*]] = and i32 [[AND3]], [[Z]]
-; CHECK-NEXT:    [[OR:%.*]] = or i32 [[AND1]], [[AND4]]
-; CHECK-NEXT:    [[AND7_DEMORGAN:%.*]] = or i32 [[X]], [[Y]]
-; CHECK-NEXT:    [[AND9_DEMORGAN:%.*]] = or i32 [[AND7_DEMORGAN]], [[Z]]
-; CHECK-NEXT:    [[AND9:%.*]] = xor i32 [[AND9_DEMORGAN]], -1
-; CHECK-NEXT:    [[OR10:%.*]] = or i32 [[OR]], [[AND9]]
-; CHECK-NEXT:    [[AND11:%.*]] = and i32 [[X]], [[Y]]
-; CHECK-NEXT:    [[AND12:%.*]] = and i32 [[AND11]], [[Z]]
-; CHECK-NEXT:    [[OR13:%.*]] = or i32 [[OR10]], [[AND12]]
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]]
+; CHECK-NEXT:    [[OR13:%.*]] = xor i32 [[TMP2]], -1
 ; CHECK-NEXT:    ret i32 [[OR13]]
 ;
   %not = xor i32 %z, -1
@@ -39,11 +28,9 @@ define i32 @test0_4way_or(i32 %x, i32 %y, i32 %z) {
 }
 define i32 @test1_xor_pattern(i32 %x, i32 %y, i32 %z) {
 ; CHECK-LABEL: @test1_xor_pattern(
-; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
-; CHECK-NEXT:    [[AND4_DEMORGAN:%.*]] = or i32 [[TMP1]], [[Z:%.*]]
-; CHECK-NEXT:    [[AND8:%.*]] = and i32 [[Z]], [[X]]
-; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[AND4_DEMORGAN]], -1
-; CHECK-NEXT:    [[XOR:%.*]] = or i32 [[AND8]], [[TMP2]]
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]]
+; CHECK-NEXT:    [[XOR:%.*]] = xor i32 [[TMP2]], -1
 ; CHECK-NEXT:    ret i32 [[XOR]]
 ;
   %not = xor i32 %z, -1
@@ -62,10 +49,10 @@ define i32 @test1_xor_pattern(i32 %x, i32 %y, i32 %z) {
 }
 define i32 @test2_nested_xor(i32 %x, i32 %y, i32 %z) {
 ; CHECK-LABEL: @test2_nested_xor(
-; CHECK-NEXT:    [[NOT7:%.*]] = xor i32 [[Y:%.*]], -1
-; CHECK-NEXT:    [[AND8:%.*]] = and i32 [[Z:%.*]], [[NOT7]]
-; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[X:%.*]], [[AND8]]
-; CHECK-NEXT:    ret i32 [[TMP1]]
+; CHECK-NEXT:    [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT:    [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]]
+; CHECK-NEXT:    [[TMP3:%.*]] = xor i32 [[TMP2]], [[Y]]
+; CHECK-NEXT:    ret i32 [[TMP3]]
 ;
   %and = and i32 %x, %y
   %not = xor i32 %x, -1



More information about the llvm-commits mailing list