[llvm] [InstCombine] Reassociate commutative binops over select/phi/minmax (PR #187493)

Mitch Briles via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 17 01:53:51 PDT 2026


https://github.com/MitchBriles updated https://github.com/llvm/llvm-project/pull/187493

>From ce84654a7a8d1a2ea52652de890145a32561b986 Mon Sep 17 00:00:00 2001
From: Mitch <mitchbriles at gmail.com>
Date: Thu, 19 Mar 2026 04:11:10 -0600
Subject: [PATCH 1/3] Pre-patch tests

---
 .../commutative-operation-over-minmax.ll      | 114 ++++++++++++++++++
 1 file changed, 114 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll

diff --git a/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll b/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll
new file mode 100644
index 0000000000000..239946ad1a4e4
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll
@@ -0,0 +1,114 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=instcombine -S < %s | FileCheck %s
+
+define i32 @sadd_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+; CHECK-LABEL: define i32 @sadd_min_max_assoc(
+; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
+; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.smax.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    ret i32 [[TMP7]]
+;
+  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
+  %5 = add i32 %2, %4
+  %6 = call i32 @llvm.smax.i32(i32 %0, i32 %1)
+  %7 = add i32 %5, %6
+  ret i32 %7
+}
+
+define i32 @or_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+; CHECK-LABEL: define i32 @or_min_max_assoc(
+; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
+; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP5:%.*]] = or i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.smax.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP7:%.*]] = or i32 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    ret i32 [[TMP7]]
+;
+  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
+  %5 = or i32 %2, %4
+  %6 = call i32 @llvm.smax.i32(i32 %0, i32 %1)
+  %7 = or i32 %5, %6
+  ret i32 %7
+}
+
+define i32 @smul_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+; CHECK-LABEL: define i32 @smul_min_max_assoc(
+; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
+; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.smax.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP7:%.*]] = mul i32 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    ret i32 [[TMP7]]
+;
+  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
+  %5 = mul i32 %2, %4
+  %6 = call i32 @llvm.smax.i32(i32 %0, i32 %1)
+  %7 = mul i32 %5, %6
+  ret i32 %7
+}
+
+define i32 @uadd_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+; CHECK-LABEL: define i32 @uadd_min_max_assoc(
+; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
+; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.umin.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    ret i32 [[TMP7]]
+;
+  %4 = call i32 @llvm.umin.i32(i32 %0, i32 %1)
+  %5 = add i32 %2, %4
+  %6 = call i32 @llvm.umax.i32(i32 %0, i32 %1)
+  %7 = add i32 %5, %6
+  ret i32 %7
+}
+
+define i32 @umul_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+; CHECK-LABEL: define i32 @umul_min_max_assoc(
+; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
+; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.umin.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP7:%.*]] = mul i32 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    ret i32 [[TMP7]]
+;
+  %4 = call i32 @llvm.umin.i32(i32 %0, i32 %1)
+  %5 = mul i32 %2, %4
+  %6 = call i32 @llvm.umax.i32(i32 %0, i32 %1)
+  %7 = mul i32 %5, %6
+  ret i32 %7
+}
+
+define i32 @sadd_min_max_assoc_wrong1(i32 %0, i32 %1, i32 %2) {
+; CHECK-LABEL: define i32 @sadd_min_max_assoc_wrong1(
+; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
+; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    ret i32 [[TMP7]]
+;
+  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
+  %5 = add i32 %2, %4
+  %6 = call i32 @llvm.umax.i32(i32 %0, i32 %1)
+  %7 = add i32 %5, %6
+  ret i32 %7
+}
+
+define i32 @sadd_min_max_assoc_wrong2(i32 %0, i32 %1, i32 %2) {
+; CHECK-LABEL: define i32 @sadd_min_max_assoc_wrong2(
+; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
+; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[TMP1]])
+; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    ret i32 [[TMP7]]
+;
+  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
+  %5 = mul i32 %2, %4
+  %6 = call i32 @llvm.umax.i32(i32 %0, i32 %1)
+  %7 = add i32 %5, %6
+  ret i32 %7
+}

>From dcb75f21395b669aaeacbcb8681b94a279294e4a Mon Sep 17 00:00:00 2001
From: Mitch <mitchbriles at gmail.com>
Date: Thu, 19 Mar 2026 06:04:36 -0600
Subject: [PATCH 2/3] [InstCombine] Reassociate commutative binops over
 select/phi/minmax

Fix test names

Check hasOneUse

Drop `nuw`/`nsw` + fixup tests

Use `dropPoisonGeneratingFlags`

Handle commuted pattern

Last commuted case

Refactor into helper + add test

Be more careful about operand indices

Mixed associativity tests (pre-patch)

Account for mixed associativity

Also no longer crashes when helper fails. Wasn't causing issues here, just a self nit.

Formatting somehow slipped through

Use assertions

Drop annotations

Update for new API
---
 .../InstCombine/InstCombineInternal.h         |  10 +
 .../InstCombine/InstructionCombining.cpp      |  55 +++
 .../commutative-operation-over-minmax.ll      | 330 +++++++++++++-----
 3 files changed, 314 insertions(+), 81 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 8b759e701da60..1ae5aef472be3 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -522,6 +522,16 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
   /// or commutative.
   bool SimplifyAssociativeOrCommutative(BinaryOperator &I);
 
+  /// If InnerVal1 and OuterVal form a symmetric pair, and "op" is commutative:
+  /// "(InnerVal0 op InnerVal1) op OuterVal" ==> "(X op' Y) op InnerVal0".
+  /// "OuterVal op (InnerVal0 op InnerVal1)" ==> "InnerVal0 op (X op' Y)".
+  /// X and Y are the inputs to the symmetric pair. "op'" indicates that
+  /// InnerOp will have its poison-generating flags dropped.
+  bool tryReassociateAndFoldSymmetricPair(BinaryOperator &OuterOp,
+                                          BinaryOperator &InnerOp,
+                                          Value *InnerVal0, Value *InnerVal1,
+                                          Value *OuterVal);
+
   /// Tries to simplify binary operations which some other binary
   /// operation distributes over.
   ///
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index ea38304531806..46b2c8288e0e0 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -530,6 +530,13 @@ bool InstCombinerImpl::SimplifyAssociativeOrCommutative(BinaryOperator &I) {
         Value *B = Op0->getOperand(1);
         Value *C = I.getOperand(1);
 
+        if (tryReassociateAndFoldSymmetricPair(I, *Op0, A, B, C)) {
+          I.swapOperands();
+          Changed = true;
+          ++NumReassoc;
+          continue;
+        }
+
         // Does "B op C" simplify?
         if (Value *V = simplifyBinOp(Opcode, B, C, SQ.getWithInstruction(&I))) {
           // It simplifies to V.  Form "A op V".
@@ -563,6 +570,13 @@ bool InstCombinerImpl::SimplifyAssociativeOrCommutative(BinaryOperator &I) {
         Value *B = Op1->getOperand(0);
         Value *C = Op1->getOperand(1);
 
+        if (tryReassociateAndFoldSymmetricPair(I, *Op1, C, B, A)) {
+          I.swapOperands();
+          Changed = true;
+          ++NumReassoc;
+          continue;
+        }
+
         // Does "A op B" simplify?
         if (Value *V = simplifyBinOp(Opcode, A, B, SQ.getWithInstruction(&I))) {
           // It simplifies to V.  Form "V op C".
@@ -592,6 +606,12 @@ bool InstCombinerImpl::SimplifyAssociativeOrCommutative(BinaryOperator &I) {
         Value *B = Op0->getOperand(1);
         Value *C = I.getOperand(1);
 
+        if (tryReassociateAndFoldSymmetricPair(I, *Op0, B, A, C)) {
+          Changed = true;
+          ++NumReassoc;
+          continue;
+        }
+
         // Does "C op A" simplify?
         if (Value *V = simplifyBinOp(Opcode, C, A, SQ.getWithInstruction(&I))) {
           // It simplifies to V.  Form "V op B".
@@ -613,6 +633,12 @@ bool InstCombinerImpl::SimplifyAssociativeOrCommutative(BinaryOperator &I) {
         Value *B = Op1->getOperand(0);
         Value *C = Op1->getOperand(1);
 
+        if (tryReassociateAndFoldSymmetricPair(I, *Op1, B, C, A)) {
+          Changed = true;
+          ++NumReassoc;
+          continue;
+        }
+
         // Does "C op A" simplify?
         if (Value *V = simplifyBinOp(Opcode, C, A, SQ.getWithInstruction(&I))) {
           // It simplifies to V.  Form "B op V".
@@ -1358,6 +1384,35 @@ InstCombinerImpl::matchSymmetricPair(Value *LHS, Value *RHS) {
   }
 }
 
+bool InstCombinerImpl::tryReassociateAndFoldSymmetricPair(
+    BinaryOperator &OuterOp, BinaryOperator &InnerOp, Value *InnerVal0,
+    Value *InnerVal1, Value *OuterVal) {
+  assert(OuterOp.isCommutative() && OuterOp.isAssociative() &&
+         "OuterOp must be commutative and associative");
+  assert(OuterOp.getOpcode() == InnerOp.getOpcode() &&
+         "OuterOp and InnerOp must have same opcode");
+  if (!InnerOp.isAssociative() || !InnerOp.hasOneUse())
+    return false;
+
+  unsigned OuterValIdx = OuterOp.getOperand(0) == OuterVal ? 0 : 1;
+  assert(OuterOp.getOperand(OuterValIdx) == OuterVal &&
+         "OuterVal must be an operand of OuterOp");
+
+  auto Pair = matchSymmetricPair(InnerVal1, OuterVal);
+  if (!Pair)
+    return false;
+
+  replaceOperand(InnerOp, 0, Pair->first);
+  replaceOperand(InnerOp, 1, Pair->second);
+  InnerOp.dropPoisonGeneratingAnnotations();
+  InnerOp.dropUBImplyingAttrsAndMetadata();
+  replaceOperand(OuterOp, OuterValIdx, InnerVal0);
+  if (!isa<FPMathOperator>(OuterOp))
+    OuterOp.dropPoisonGeneratingFlags();
+  OuterOp.dropUBImplyingAttrsAndMetadata();
+  return true;
+}
+
 Value *InstCombinerImpl::SimplifySelectsFeedingBinaryOp(BinaryOperator &I,
                                                         Value *LHS,
                                                         Value *RHS) {
diff --git a/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll b/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll
index 239946ad1a4e4..7d964c42bb349 100644
--- a/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll
+++ b/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll
@@ -1,114 +1,282 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; RUN: opt -passes=instcombine -S < %s | FileCheck %s
 
-define i32 @sadd_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+define i32 @sadd_min_max_assoc(i32 %x, i32 %y, i32 %z) {
 ; CHECK-LABEL: define i32 @sadd_min_max_assoc(
-; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
-; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.smax.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
-; CHECK-NEXT:    ret i32 [[TMP7]]
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = add i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = add i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = add i32 %lhs, %max
+  ret i32 %ret
+}
+
+define i32 @sadd_min_max_assoc2(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @sadd_min_max_assoc2(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[RHS:%.*]] = add i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[RET:%.*]] = add i32 [[RHS]], [[Z]]
+; CHECK-NEXT:    ret i32 [[RET]]
 ;
-  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
-  %5 = add i32 %2, %4
-  %6 = call i32 @llvm.smax.i32(i32 %0, i32 %1)
-  %7 = add i32 %5, %6
-  ret i32 %7
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %rhs = add i32 %min, %z
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = add i32 %max, %rhs
+  ret i32 %ret
 }
 
-define i32 @or_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+define i32 @sadd_min_max_assoc_comm(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @sadd_min_max_assoc_comm(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[RHS:%.*]] = add i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[RET:%.*]] = add i32 [[Z]], [[RHS]]
+; CHECK-NEXT:    ret i32 [[RET]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %rhs = add i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = add i32 %max, %rhs
+  ret i32 %ret
+}
+
+define i32 @or_min_max_assoc(i32 %x, i32 %y, i32 %z) {
 ; CHECK-LABEL: define i32 @or_min_max_assoc(
-; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
-; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP5:%.*]] = or i32 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.smax.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP7:%.*]] = or i32 [[TMP5]], [[TMP6]]
-; CHECK-NEXT:    ret i32 [[TMP7]]
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = or i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[TMP5:%.*]] = or i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = or i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = or i32 %lhs, %max
+  ret i32 %ret
+}
+
+define i32 @or_disjoint_min_max_assoc(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @or_disjoint_min_max_assoc(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = or i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[TMP5:%.*]] = or i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = or disjoint i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = or i32 %lhs, %max
+  ret i32 %ret
+}
+
+define i32 @or_outer_disjoint_min_max_assoc(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @or_outer_disjoint_min_max_assoc(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = or i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[RET:%.*]] = or i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[RET]]
 ;
-  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
-  %5 = or i32 %2, %4
-  %6 = call i32 @llvm.smax.i32(i32 %0, i32 %1)
-  %7 = or i32 %5, %6
-  ret i32 %7
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = or i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = or disjoint i32 %lhs, %max
+  ret i32 %ret
 }
 
-define i32 @smul_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+define i32 @smul_min_max_assoc(i32 %x, i32 %y, i32 %z) {
 ; CHECK-LABEL: define i32 @smul_min_max_assoc(
-; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
-; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.smax.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP7:%.*]] = mul i32 [[TMP5]], [[TMP6]]
-; CHECK-NEXT:    ret i32 [[TMP7]]
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = mul i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[TMP5]]
 ;
-  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
-  %5 = mul i32 %2, %4
-  %6 = call i32 @llvm.smax.i32(i32 %0, i32 %1)
-  %7 = mul i32 %5, %6
-  ret i32 %7
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = mul i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = mul i32 %lhs, %max
+  ret i32 %ret
 }
 
-define i32 @uadd_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+define i32 @smul_min_max_assoc_comm(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @smul_min_max_assoc_comm(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = mul i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[RET:%.*]] = mul i32 [[LHS]], [[Z]]
+; CHECK-NEXT:    ret i32 [[RET]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = mul i32 %min, %z
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = mul i32 %lhs, %max
+  ret i32 %ret
+}
+
+define i32 @uadd_min_max_assoc(i32 %x, i32 %y, i32 %z) {
 ; CHECK-LABEL: define i32 @uadd_min_max_assoc(
-; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
-; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.umin.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
-; CHECK-NEXT:    ret i32 [[TMP7]]
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = add i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[TMP5]]
 ;
-  %4 = call i32 @llvm.umin.i32(i32 %0, i32 %1)
-  %5 = add i32 %2, %4
-  %6 = call i32 @llvm.umax.i32(i32 %0, i32 %1)
-  %7 = add i32 %5, %6
-  ret i32 %7
+  %min = call i32 @llvm.umin.i32(i32 %x, i32 %y)
+  %lhs = add i32 %z, %min
+  %max = call i32 @llvm.umax.i32(i32 %x, i32 %y)
+  %ret = add i32 %lhs, %max
+  ret i32 %ret
 }
 
-define i32 @umul_min_max_assoc(i32 %0, i32 %1, i32 %2) {
+define i32 @umul_min_max_assoc(i32 %x, i32 %y, i32 %z) {
 ; CHECK-LABEL: define i32 @umul_min_max_assoc(
-; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
-; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.umin.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP7:%.*]] = mul i32 [[TMP5]], [[TMP6]]
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = mul i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %min = call i32 @llvm.umin.i32(i32 %x, i32 %y)
+  %lhs = mul i32 %z, %min
+  %max = call i32 @llvm.umax.i32(i32 %x, i32 %y)
+  %ret = mul i32 %lhs, %max
+  ret i32 %ret
+}
+
+define i32 @add_min_max_assoc_wrong(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @add_min_max_assoc_wrong(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.smin.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[Z]], [[MIN]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
 ; CHECK-NEXT:    ret i32 [[TMP7]]
 ;
-  %4 = call i32 @llvm.umin.i32(i32 %0, i32 %1)
-  %5 = mul i32 %2, %4
-  %6 = call i32 @llvm.umax.i32(i32 %0, i32 %1)
-  %7 = mul i32 %5, %6
-  ret i32 %7
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = add i32 %z, %min
+  %max = call i32 @llvm.umax.i32(i32 %x, i32 %y)
+  %ret = add i32 %lhs, %max
+  ret i32 %ret
 }
 
-define i32 @sadd_min_max_assoc_wrong1(i32 %0, i32 %1, i32 %2) {
-; CHECK-LABEL: define i32 @sadd_min_max_assoc_wrong1(
-; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
-; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[TMP1]])
+define i32 @add_min_max_assoc_wrong2(i32 %x, i32 %y, i32 %z, ptr %p) {
+; CHECK-LABEL: define i32 @add_min_max_assoc_wrong2(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.smin.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    [[TMP5:%.*]] = add i32 [[Z]], [[MIN]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.smax.i32(i32 [[X]], i32 [[Y]])
 ; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    store i32 [[TMP5]], ptr [[P]], align 4
 ; CHECK-NEXT:    ret i32 [[TMP7]]
 ;
-  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
-  %5 = add i32 %2, %4
-  %6 = call i32 @llvm.umax.i32(i32 %0, i32 %1)
-  %7 = add i32 %5, %6
-  ret i32 %7
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = add i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = add i32 %lhs, %max
+  store i32 %lhs, ptr %p
+  ret i32 %ret
 }
 
-define i32 @sadd_min_max_assoc_wrong2(i32 %0, i32 %1, i32 %2) {
-; CHECK-LABEL: define i32 @sadd_min_max_assoc_wrong2(
-; CHECK-SAME: i32 [[TMP0:%.*]], i32 [[TMP1:%.*]], i32 [[TMP2:%.*]]) {
-; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 [[TMP1]])
-; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[TMP0]], i32 [[TMP1]])
+define i32 @mul_min_max_assoc_wrong(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @mul_min_max_assoc_wrong(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.smin.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[Z]], [[MIN]]
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.umax.i32(i32 [[X]], i32 [[Y]])
 ; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[TMP5]], [[TMP6]]
 ; CHECK-NEXT:    ret i32 [[TMP7]]
 ;
-  %4 = call i32 @llvm.smin.i32(i32 %0, i32 %1)
-  %5 = mul i32 %2, %4
-  %6 = call i32 @llvm.umax.i32(i32 %0, i32 %1)
-  %7 = add i32 %5, %6
-  ret i32 %7
+  %min = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+  %lhs = mul i32 %z, %min
+  %max = call i32 @llvm.umax.i32(i32 %x, i32 %y)
+  %ret = add i32 %lhs, %max
+  ret i32 %ret
+}
+
+define i32 @sadd_nsw_min_max_assoc(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @sadd_nsw_min_max_assoc(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = add i32 [[Y]], [[X]]
+; CHECK-NEXT:    [[RET:%.*]] = add i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[RET]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %y, i32 %x)
+  %lhs = add nsw i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = add i32 %lhs, %max
+  ret i32 %ret
+}
+
+define i32 @sadd_nuw_min_max_assoc(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @sadd_nuw_min_max_assoc(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = add i32 [[Y]], [[X]]
+; CHECK-NEXT:    [[RET:%.*]] = add i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[RET]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %y, i32 %x)
+  %lhs = add nuw i32 %z, %min
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = add i32 %lhs, %max
+  ret i32 %ret
+}
+
+define i32 @sadd_inner_metadata_outer_annotations(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i32 @sadd_inner_metadata_outer_annotations(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[LHS:%.*]] = add i32 [[Y]], [[X]]
+; CHECK-NEXT:    [[RET:%.*]] = add i32 [[Z]], [[LHS]]
+; CHECK-NEXT:    ret i32 [[RET]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %y, i32 %x)
+  %lhs = add i32 %z, %min, !noundef !0
+  %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+  %ret = add nuw nsw i32 %lhs, %max, !noundef !0
+  ret i32 %ret
+}
+
+define float @fp_inner_no_reassoc_cmp(i1 %c, float %a, float %x, float %y) {
+; CHECK-LABEL: define float @fp_inner_no_reassoc_cmp(
+; CHECK-SAME: i1 [[C:%.*]], float [[A:%.*]], float [[X:%.*]], float [[Y:%.*]]) {
+; CHECK-NEXT:    [[S0:%.*]] = select i1 [[C]], float [[X]], float [[Y]]
+; CHECK-NEXT:    [[S1:%.*]] = select i1 [[C]], float [[Y]], float [[X]]
+; CHECK-NEXT:    [[INNER:%.*]] = fadd float [[A]], [[S0]]
+; CHECK-NEXT:    [[RETF:%.*]] = fadd reassoc nsz float [[INNER]], [[S1]]
+; CHECK-NEXT:    ret float [[RETF]]
+;
+  %s0 = select i1 %c, float %x, float %y
+  %s1 = select i1 %c, float %y, float %x
+  %inner = fadd float %a, %s0
+  %retf = fadd reassoc nsz float %inner, %s1
+  ret float %retf
 }
+
+define float @fp_mixed_fmf_cmp(i1 %c, float %a, float %x, float %y) {
+; CHECK-LABEL: define float @fp_mixed_fmf_cmp(
+; CHECK-SAME: i1 [[C:%.*]], float [[A:%.*]], float [[X:%.*]], float [[Y:%.*]]) {
+; CHECK-NEXT:    [[S0:%.*]] = select i1 [[C]], float [[X]], float [[Y]]
+; CHECK-NEXT:    [[S1:%.*]] = select i1 [[C]], float [[Y]], float [[X]]
+; CHECK-NEXT:    [[INNER:%.*]] = fadd nnan float [[A]], [[S0]]
+; CHECK-NEXT:    [[RETF:%.*]] = fadd reassoc nnan ninf nsz float [[INNER]], [[S1]]
+; CHECK-NEXT:    ret float [[RETF]]
+;
+  %s0 = select i1 %c, float %x, float %y
+  %s1 = select i1 %c, float %y, float %x
+  %inner = fadd nnan float %a, %s0
+  %retf = fadd reassoc nsz nnan ninf float %inner, %s1
+  ret float %retf
+}
+
+define float @fp_outer_fmf_metadata(i1 %c, float %a, float %x, float %y) {
+; CHECK-LABEL: define float @fp_outer_fmf_metadata(
+; CHECK-SAME: i1 [[C:%.*]], float [[A:%.*]], float [[X:%.*]], float [[Y:%.*]]) {
+; CHECK-NEXT:    [[INNER:%.*]] = fadd reassoc nsz float [[X]], [[Y]]
+; CHECK-NEXT:    [[RETF:%.*]] = fadd reassoc nnan ninf nsz float [[A]], [[INNER]]
+; CHECK-NEXT:    ret float [[RETF]]
+;
+  %s0 = select i1 %c, float %x, float %y
+  %s1 = select i1 %c, float %y, float %x
+  %inner = fadd reassoc nnan ninf nsz float %a, %s0, !noundef !0
+  %retf = fadd reassoc nnan ninf nsz float %inner, %s1, !noundef !0
+  ret float %retf
+}
+
+!0 = !{}

>From 623f31efde8b7110565cc84468615093043f21d1 Mon Sep 17 00:00:00 2001
From: Mitch <mitchbriles at gmail.com>
Date: Fri, 17 Jul 2026 02:53:25 -0600
Subject: [PATCH 3/3] Patch tests after metadata change

---
 .../Transforms/InstCombine/InstructionCombining.cpp |  2 +-
 .../commutative-operation-over-minmax.ll            | 13 ++++++++-----
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 46b2c8288e0e0..a55e50f9550d2 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -1409,7 +1409,7 @@ bool InstCombinerImpl::tryReassociateAndFoldSymmetricPair(
   replaceOperand(OuterOp, OuterValIdx, InnerVal0);
   if (!isa<FPMathOperator>(OuterOp))
     OuterOp.dropPoisonGeneratingFlags();
-  OuterOp.dropUBImplyingAttrsAndMetadata();
+  OuterOp.dropUnknownNonDebugMetadata();
   return true;
 }
 
diff --git a/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll b/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll
index 7d964c42bb349..2a172426c196f 100644
--- a/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll
+++ b/llvm/test/Transforms/InstCombine/commutative-operation-over-minmax.ll
@@ -227,9 +227,9 @@ define i32 @sadd_inner_metadata_outer_annotations(i32 %x, i32 %y, i32 %z) {
 ; CHECK-NEXT:    ret i32 [[RET]]
 ;
   %min = call i32 @llvm.smin.i32(i32 %y, i32 %x)
-  %lhs = add i32 %z, %min, !noundef !0
+  %lhs = add i32 %z, %min, !unknown !0
   %max = call i32 @llvm.smax.i32(i32 %x, i32 %y)
-  %ret = add nuw nsw i32 %lhs, %max, !noundef !0
+  %ret = add nuw nsw i32 %lhs, %max, !annotation !1, !unknown !2
   ret i32 %ret
 }
 
@@ -274,9 +274,12 @@ define float @fp_outer_fmf_metadata(i1 %c, float %a, float %x, float %y) {
 ;
   %s0 = select i1 %c, float %x, float %y
   %s1 = select i1 %c, float %y, float %x
-  %inner = fadd reassoc nnan ninf nsz float %a, %s0, !noundef !0
-  %retf = fadd reassoc nnan ninf nsz float %inner, %s1, !noundef !0
+  %inner = fadd reassoc nnan ninf nsz float %a, %s0, !unknown !0
+  %retf = fadd reassoc nnan ninf nsz float %inner, %s1, !fpmath !3, !annotation !1, !unknown !2
   ret float %retf
 }
 
-!0 = !{}
+!0 = !{!"inner unknown"}
+!1 = !{!"outer annotation"}
+!2 = !{!"outer unknown"}
+!3 = !{float 2.500000e+00}



More information about the llvm-commits mailing list