[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