[llvm] Ic abs mul shl (PR #207539)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 4 13:48:47 PDT 2026
https://github.com/fhahn created https://github.com/llvm/llvm-project/pull/207539
None
>From afff61c5e95f5f070f59e73504f8e24870417753 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 25 May 2026 12:02:58 +0100
Subject: [PATCH 1/2] [InstCombine] Add tests for select-abs through positive-K
multiply (NFC).
---
.../InstCombine/select-abs-positive-mul.ll | 345 ++++++++++++++++++
1 file changed, 345 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/select-abs-positive-mul.ll
diff --git a/llvm/test/Transforms/InstCombine/select-abs-positive-mul.ll b/llvm/test/Transforms/InstCombine/select-abs-positive-mul.ll
new file mode 100644
index 0000000000000..8da05f211b99d
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/select-abs-positive-mul.ll
@@ -0,0 +1,345 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=instcombine %s -S | FileCheck %s
+
+;; Tests for abs idioms with signed multiplies, like
+; `int v = (int)narrow * K; return v < 0 ? v : -v;`.
+
+define i32 @sel_mul_pos_x_lt_zero(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_x_lt_zero(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+define i32 @sel_shl_pos_x_lt_zero(i32 %x) {
+; CHECK-LABEL: define i32 @sel_shl_pos_x_lt_zero(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = shl nsw i32 [[X]], 2
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = shl nsw i32 %x, 2
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+; x >= 0 ? K*x : -K*x (arms swapped, sge predicate).
+define i32 @sel_mul_pos_x_ge_zero(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_x_ge_zero(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP1:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP1]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp sge i32 %x, 0
+ %sel = select i1 %cmp, i32 %m, i32 %neg
+ ret i32 %sel
+}
+
+; x > 0 ? K*x : -K*x
+define i32 @sel_mul_pos_x_sgt_zero(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_x_sgt_zero(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[M]], i32 [[NEG]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp sgt i32 %x, 0
+ %sel = select i1 %cmp, i32 %m, i32 %neg
+ ret i32 %sel
+}
+
+define i32 @sel_mul_pos_x_sgt_minus_one(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_x_sgt_minus_one(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP1:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP1]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp sgt i32 %x, -1
+ %sel = select i1 %cmp, i32 %m, i32 %neg
+ ret i32 %sel
+}
+
+define i32 @sel_mul_pos_x_lt_zero_lhs_const(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_x_lt_zero_lhs_const(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 5, %x
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+define <4 x i32> @sel_mul_pos_x_lt_zero_vec(<4 x i32> %x) {
+; CHECK-LABEL: define <4 x i32> @sel_mul_pos_x_lt_zero_vec(
+; CHECK-SAME: <4 x i32> [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = shl nsw <4 x i32> [[X]], splat (i32 2)
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw <4 x i32> zeroinitializer, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt <4 x i32> [[X]], zeroinitializer
+; CHECK-NEXT: [[SEL:%.*]] = select <4 x i1> [[CMP]], <4 x i32> [[NEG]], <4 x i32> [[M]]
+; CHECK-NEXT: ret <4 x i32> [[SEL]]
+;
+ %m = mul nsw <4 x i32> %x, splat (i32 4)
+ %neg = sub nsw <4 x i32> zeroinitializer, %m
+ %cmp = icmp slt <4 x i32> %x, zeroinitializer
+ %sel = select <4 x i1> %cmp, <4 x i32> %neg, <4 x i32> %m
+ ret <4 x i32> %sel
+}
+
+define <4 x i32> @sel_mul_pos_vec_nonsplat(<4 x i32> %x) {
+; CHECK-LABEL: define <4 x i32> @sel_mul_pos_vec_nonsplat(
+; CHECK-SAME: <4 x i32> [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw <4 x i32> [[X]], <i32 3, i32 5, i32 7, i32 11>
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw <4 x i32> zeroinitializer, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt <4 x i32> [[X]], zeroinitializer
+; CHECK-NEXT: [[SEL:%.*]] = select <4 x i1> [[CMP]], <4 x i32> [[NEG]], <4 x i32> [[M]]
+; CHECK-NEXT: ret <4 x i32> [[SEL]]
+;
+ %m = mul nsw <4 x i32> %x, <i32 3, i32 5, i32 7, i32 11>
+ %neg = sub nsw <4 x i32> zeroinitializer, %m
+ %cmp = icmp slt <4 x i32> %x, zeroinitializer
+ %sel = select <4 x i1> %cmp, <4 x i32> %neg, <4 x i32> %m
+ ret <4 x i32> %sel
+}
+
+; (X < 0) ? K*X : -K*X --> -abs(K*X)
+define i32 @sel_mul_pos_nabs(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_nabs(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[M]], i32 [[NEG]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %m, i32 %neg
+ ret i32 %sel
+}
+
+define i32 @sel_mul_pos_neg_no_nsw(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_neg_no_nsw(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+; Constant is negative.
+define i32 @sel_mul_neg_x_lt_zero(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_neg_x_lt_zero(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], -5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, -5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+; Mul is missing nsw.
+define i32 @sel_mul_pos_no_nsw(i32 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_no_nsw(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul i32 %x, 5
+ %neg = sub i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+; Shl missing nsw.
+define i32 @sel_shl_pos_no_nsw(i32 %x) {
+; CHECK-LABEL: define i32 @sel_shl_pos_no_nsw(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = shl i32 [[X]], 2
+; CHECK-NEXT: [[NEG:%.*]] = sub i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = shl i32 %x, 2
+ %neg = sub i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+define i32 @sel_unrelated_cmp(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @sel_unrelated_cmp(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[Y]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp slt i32 %y, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+define i32 @sel_mul_pos_multi_use_cmp(i32 %x, ptr %p) {
+; CHECK-LABEL: define i32 @sel_mul_pos_multi_use_cmp(
+; CHECK-SAME: i32 [[X:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: store i1 [[CMP]], ptr [[P]], align 1
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp slt i32 %x, 0
+ store i1 %cmp, ptr %p
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+define i32 @sel_mul_pos_multi_use_neg(i32 %x, ptr %p) {
+; CHECK-LABEL: define i32 @sel_mul_pos_multi_use_neg(
+; CHECK-SAME: i32 [[X:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: store i32 [[NEG]], ptr [[P]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ store i32 %neg, ptr %p
+ %cmp = icmp slt i32 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+define i32 @sel_mul_pos_multi_use_both(i32 %x, ptr %p, ptr %q) {
+; CHECK-LABEL: define i32 @sel_mul_pos_multi_use_both(
+; CHECK-SAME: i32 [[X:%.*]], ptr [[P:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: store i32 [[NEG]], ptr [[Q]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT: store i1 [[CMP]], ptr [[P]], align 1
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %m = mul nsw i32 %x, 5
+ %neg = sub nsw i32 0, %m
+ store i32 %neg, ptr %q
+ %cmp = icmp slt i32 %x, 0
+ store i1 %cmp, ptr %p
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
+
+define i16 @sel_mul_pos_sext_x_lt_zero(i8 %x) {
+; CHECK-LABEL: define i16 @sel_mul_pos_sext_x_lt_zero(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[XW:%.*]] = sext i8 [[X]] to i16
+; CHECK-NEXT: [[M:%.*]] = mul nsw i16 [[XW]], 3
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i16 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i16 [[NEG]], i16 [[M]]
+; CHECK-NEXT: ret i16 [[SEL]]
+;
+ %xw = sext i8 %x to i16
+ %m = mul nsw i16 %xw, 3
+ %neg = sub nsw i16 0, %m
+ %cmp = icmp slt i8 %x, 0
+ %sel = select i1 %cmp, i16 %neg, i16 %m
+ ret i16 %sel
+}
+
+define i16 @sel_shl_pos_sext_x_lt_zero(i8 %x) {
+; CHECK-LABEL: define i16 @sel_shl_pos_sext_x_lt_zero(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[XW:%.*]] = sext i8 [[X]] to i16
+; CHECK-NEXT: [[M:%.*]] = shl nsw i16 [[XW]], 2
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i16 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i16 [[NEG]], i16 [[M]]
+; CHECK-NEXT: ret i16 [[SEL]]
+;
+ %xw = sext i8 %x to i16
+ %m = shl nsw i16 %xw, 2
+ %neg = sub nsw i16 0, %m
+ %cmp = icmp slt i8 %x, 0
+ %sel = select i1 %cmp, i16 %neg, i16 %m
+ ret i16 %sel
+}
+
+define i32 @sel_mul_pos_sext_i8_i32(i8 %x) {
+; CHECK-LABEL: define i32 @sel_mul_pos_sext_i8_i32(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[XW:%.*]] = sext i8 [[X]] to i32
+; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[XW]], 5
+; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %xw = sext i8 %x to i32
+ %m = mul nsw i32 %xw, 5
+ %neg = sub nsw i32 0, %m
+ %cmp = icmp slt i8 %x, 0
+ %sel = select i1 %cmp, i32 %neg, i32 %m
+ ret i32 %sel
+}
>From cad2f90e8b3cba342c8e4ef16f62b9898bf7857f Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 25 May 2026 12:09:06 +0100
Subject: [PATCH 2/2] [InstCombine] Recognize abs through positive-K nsw
multiply.
InstCombine canonicalizes `(K * X) <s 0` to `X <s 0` for positive K with
nsw mul/shl, which leaves the abs idiom shaped as
`select(slt(X, 0), neg(K*X), K*X)` -- the cmp operand (X) no longer
matches the select arms (K*X, -(K*X)), so the standard abs matcher
(matchSelectPattern) does not fire and the chain stays as a select.
End-to-end, this allows vectorizing with narrower element types for code
such as below. This triggers in an ffmpeg kernel.
void scaled_absdiff_u8(uint8_t * __restrict out,
const uint8_t * __restrict a,
const uint8_t * __restrict b,
size_t n) {
for (size_t i = 0; i < n; ++i) {
int32_t d = 4 * ((int32_t)a[i] - (int32_t)b[i]);
if (d < 0) d = -d;
if (d > 255) d = 255;
out[i] = (uint8_t)d;
}
}
---
llvm/lib/Analysis/ValueTracking.cpp | 16 ++++-
.../InstCombine/select-abs-positive-mul.ll | 60 ++++++-------------
2 files changed, 30 insertions(+), 46 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 7dd23f24dfcc7..f242fdeef9dfb 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -9002,10 +9002,20 @@ static SelectPatternResult matchSelectPattern(CmpInst::Predicate Pred,
return getSelectPattern(Pred, NaNBehavior, Ordered);
if (isKnownNegation(TrueVal, FalseVal)) {
- // Sign-extending LHS does not change its sign, so TrueVal/FalseVal can
- // match against either LHS or sext(LHS).
- auto MaybeSExtCmpLHS =
+ // Sign-extending CmpLHS does not change its sign, so TrueVal/FalseVal
+ // can match against either CmpLHS or sext(CmpLHS). The same is true
+ // for K*Inner or Inner<<S under nsw with positive K / non-negative S,
+ // since those preserve sign -- and the multiplied/shifted operand may
+ // itself be sext(CmpLHS) (a narrow value widened before scaling, with
+ // the sign test folded back onto the narrow CmpLHS).
+ auto CmpLHSOrSExt =
m_CombineOr(m_Specific(CmpLHS), m_SExt(m_Specific(CmpLHS)));
+ auto MaybeSExtCmpLHS = m_CombineOr(
+ CmpLHSOrSExt,
+ m_CombineOr(
+ m_CombineOr(m_NSWMul(CmpLHSOrSExt, m_StrictlyPositive()),
+ m_NSWMul(m_StrictlyPositive(), CmpLHSOrSExt)),
+ m_NSWShl(CmpLHSOrSExt, m_NonNegative())));
auto ZeroOrAllOnes = m_CombineOr(m_ZeroInt(), m_AllOnes());
auto ZeroOrOne = m_CombineOr(m_ZeroInt(), m_One());
if (match(TrueVal, MaybeSExtCmpLHS)) {
diff --git a/llvm/test/Transforms/InstCombine/select-abs-positive-mul.ll b/llvm/test/Transforms/InstCombine/select-abs-positive-mul.ll
index 8da05f211b99d..af92a99417a71 100644
--- a/llvm/test/Transforms/InstCombine/select-abs-positive-mul.ll
+++ b/llvm/test/Transforms/InstCombine/select-abs-positive-mul.ll
@@ -8,9 +8,7 @@ define i32 @sel_mul_pos_x_lt_zero(i32 %x) {
; CHECK-LABEL: define i32 @sel_mul_pos_x_lt_zero(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 %x, 5
@@ -24,9 +22,7 @@ define i32 @sel_shl_pos_x_lt_zero(i32 %x) {
; CHECK-LABEL: define i32 @sel_shl_pos_x_lt_zero(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = shl nsw i32 [[X]], 2
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = shl nsw i32 %x, 2
@@ -41,9 +37,7 @@ define i32 @sel_mul_pos_x_ge_zero(i32 %x) {
; CHECK-LABEL: define i32 @sel_mul_pos_x_ge_zero(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
-; CHECK-NEXT: [[CMP1:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP1]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 %x, 5
@@ -58,9 +52,7 @@ define i32 @sel_mul_pos_x_sgt_zero(i32 %x) {
; CHECK-LABEL: define i32 @sel_mul_pos_x_sgt_zero(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[M]], i32 [[NEG]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 %x, 5
@@ -74,9 +66,7 @@ define i32 @sel_mul_pos_x_sgt_minus_one(i32 %x) {
; CHECK-LABEL: define i32 @sel_mul_pos_x_sgt_minus_one(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
-; CHECK-NEXT: [[CMP1:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP1]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 %x, 5
@@ -90,9 +80,7 @@ define i32 @sel_mul_pos_x_lt_zero_lhs_const(i32 %x) {
; CHECK-LABEL: define i32 @sel_mul_pos_x_lt_zero_lhs_const(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 5, %x
@@ -106,9 +94,7 @@ define <4 x i32> @sel_mul_pos_x_lt_zero_vec(<4 x i32> %x) {
; CHECK-LABEL: define <4 x i32> @sel_mul_pos_x_lt_zero_vec(
; CHECK-SAME: <4 x i32> [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = shl nsw <4 x i32> [[X]], splat (i32 2)
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw <4 x i32> zeroinitializer, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt <4 x i32> [[X]], zeroinitializer
-; CHECK-NEXT: [[SEL:%.*]] = select <4 x i1> [[CMP]], <4 x i32> [[NEG]], <4 x i32> [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call <4 x i32> @llvm.abs.v4i32(<4 x i32> [[M]], i1 true)
; CHECK-NEXT: ret <4 x i32> [[SEL]]
;
%m = mul nsw <4 x i32> %x, splat (i32 4)
@@ -122,9 +108,7 @@ define <4 x i32> @sel_mul_pos_vec_nonsplat(<4 x i32> %x) {
; CHECK-LABEL: define <4 x i32> @sel_mul_pos_vec_nonsplat(
; CHECK-SAME: <4 x i32> [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw <4 x i32> [[X]], <i32 3, i32 5, i32 7, i32 11>
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw <4 x i32> zeroinitializer, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt <4 x i32> [[X]], zeroinitializer
-; CHECK-NEXT: [[SEL:%.*]] = select <4 x i1> [[CMP]], <4 x i32> [[NEG]], <4 x i32> [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call <4 x i32> @llvm.abs.v4i32(<4 x i32> [[M]], i1 true)
; CHECK-NEXT: ret <4 x i32> [[SEL]]
;
%m = mul nsw <4 x i32> %x, <i32 3, i32 5, i32 7, i32 11>
@@ -139,9 +123,8 @@ define i32 @sel_mul_pos_nabs(i32 %x) {
; CHECK-LABEL: define i32 @sel_mul_pos_nabs(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[M]], i32 [[NEG]]
+; CHECK-NEXT: [[TMP1:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 false)
+; CHECK-NEXT: [[SEL:%.*]] = sub i32 0, [[TMP1]]
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 %x, 5
@@ -155,9 +138,7 @@ define i32 @sel_mul_pos_neg_no_nsw(i32 %x) {
; CHECK-LABEL: define i32 @sel_mul_pos_neg_no_nsw(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub i32 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 false)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 %x, 5
@@ -218,6 +199,7 @@ define i32 @sel_shl_pos_no_nsw(i32 %x) {
ret i32 %sel
}
+; Negative test: cmp operand is unrelated to the scaled value.
define i32 @sel_unrelated_cmp(i32 %x, i32 %y) {
; CHECK-LABEL: define i32 @sel_unrelated_cmp(
; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
@@ -238,10 +220,9 @@ define i32 @sel_mul_pos_multi_use_cmp(i32 %x, ptr %p) {
; CHECK-LABEL: define i32 @sel_mul_pos_multi_use_cmp(
; CHECK-SAME: i32 [[X:%.*]], ptr [[P:%.*]]) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
; CHECK-NEXT: store i1 [[CMP]], ptr [[P]], align 1
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 %x, 5
@@ -258,8 +239,7 @@ define i32 @sel_mul_pos_multi_use_neg(i32 %x, ptr %p) {
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[X]], 5
; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
; CHECK-NEXT: store i32 [[NEG]], ptr [[P]], align 4
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%m = mul nsw i32 %x, 5
@@ -295,9 +275,7 @@ define i16 @sel_mul_pos_sext_x_lt_zero(i8 %x) {
; CHECK-SAME: i8 [[X:%.*]]) {
; CHECK-NEXT: [[XW:%.*]] = sext i8 [[X]] to i16
; CHECK-NEXT: [[M:%.*]] = mul nsw i16 [[XW]], 3
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i16 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i16 [[NEG]], i16 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i16 @llvm.abs.i16(i16 [[M]], i1 true)
; CHECK-NEXT: ret i16 [[SEL]]
;
%xw = sext i8 %x to i16
@@ -313,9 +291,7 @@ define i16 @sel_shl_pos_sext_x_lt_zero(i8 %x) {
; CHECK-SAME: i8 [[X:%.*]]) {
; CHECK-NEXT: [[XW:%.*]] = sext i8 [[X]] to i16
; CHECK-NEXT: [[M:%.*]] = shl nsw i16 [[XW]], 2
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i16 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i16 [[NEG]], i16 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i16 @llvm.abs.i16(i16 [[M]], i1 true)
; CHECK-NEXT: ret i16 [[SEL]]
;
%xw = sext i8 %x to i16
@@ -331,9 +307,7 @@ define i32 @sel_mul_pos_sext_i8_i32(i8 %x) {
; CHECK-SAME: i8 [[X:%.*]]) {
; CHECK-NEXT: [[XW:%.*]] = sext i8 [[X]] to i32
; CHECK-NEXT: [[M:%.*]] = mul nsw i32 [[XW]], 5
-; CHECK-NEXT: [[NEG:%.*]] = sub nsw i32 0, [[M]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[NEG]], i32 [[M]]
+; CHECK-NEXT: [[SEL:%.*]] = call i32 @llvm.abs.i32(i32 [[M]], i1 true)
; CHECK-NEXT: ret i32 [[SEL]]
;
%xw = sext i8 %x to i32
More information about the llvm-commits
mailing list