[llvm] [IR] Handle vector tys in ConstantExpr::getIntrinsicIdentity for smin/max. (PR #208367)
Justin Lebar via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 8 19:57:52 PDT 2026
https://github.com/jlebar created https://github.com/llvm/llvm-project/pull/208367
- **[LoopInfo] Avoid a throwaway vector in getLoopsInPreorder. NFC (#207607)**
- **[IR] Handle vector tys in ConstantExpr::getIntrinsicIdentity for smin/max.**
>From 3b302d63f1eae7f05024c20fc04ade77aafd1ab1 Mon Sep 17 00:00:00 2001
From: Justin Lebar <justin.lebar at gmail.com>
Date: Mon, 6 Jul 2026 02:17:08 +0000
Subject: [PATCH] [IR] Handle vector tys in ConstantExpr::getIntrinsicIdentity
for smin/max.
The code was using getIntegerBitWidth() when it should have been using
getScalarSizeInBits().
Reproducer:
define <4 x i32> @f(<4 x i32> %a, i32 %b) {
%c = call <4 x i32> @f(<4 x i32> %a, i32 %b)
%m = call <4 x i32> @llvm.smax.v4i32(<4 x i32> %c, <4 x i32> %a)
ret <4 x i32> %m
}
$ opt -passes=tailcallelim repro.ll
Casting.h:572: Assertion `isa<To>(Val) && "cast<Ty>() argument of
incompatible type!"' failed.
---
llvm/lib/IR/Constants.cpp | 4 +-
.../TailCallElim/accum_recursion.ll | 77 ++++++++++++++++++
.../TailCallElim/tre-minmax-intrinsic.ll | 81 +++++++++++++++++++
3 files changed, 160 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/IR/Constants.cpp b/llvm/lib/IR/Constants.cpp
index a606a2a27669b..39f5cbce32d9d 100644
--- a/llvm/lib/IR/Constants.cpp
+++ b/llvm/lib/IR/Constants.cpp
@@ -2900,10 +2900,10 @@ Constant *ConstantExpr::getIntrinsicIdentity(Intrinsic::ID ID, Type *Ty) {
return Constant::getAllOnesValue(Ty);
case Intrinsic::smax:
return Constant::getIntegerValue(
- Ty, APInt::getSignedMinValue(Ty->getIntegerBitWidth()));
+ Ty, APInt::getSignedMinValue(Ty->getScalarSizeInBits()));
case Intrinsic::smin:
return Constant::getIntegerValue(
- Ty, APInt::getSignedMaxValue(Ty->getIntegerBitWidth()));
+ Ty, APInt::getSignedMaxValue(Ty->getScalarSizeInBits()));
default:
return nullptr;
}
diff --git a/llvm/test/Transforms/TailCallElim/accum_recursion.ll b/llvm/test/Transforms/TailCallElim/accum_recursion.ll
index c8b46edd716bb..0ee581c006950 100644
--- a/llvm/test/Transforms/TailCallElim/accum_recursion.ll
+++ b/llvm/test/Transforms/TailCallElim/accum_recursion.ll
@@ -366,3 +366,80 @@ rec:
%acc = mul nsw i32 %r, %g
ret i32 %acc
}
+
+%struct.VecListNode = type { <4 x i32>, ptr }
+
+; The identity for a vector add accumulator is the zero vector.
+define <4 x i32> @test_vector_add_accumulator(ptr %a) local_unnamed_addr {
+; CHECK-LABEL: define <4 x i32> @test_vector_add_accumulator(
+; CHECK-SAME: ptr [[A:%.*]]) local_unnamed_addr {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[TAILRECURSE:%.*]]
+; CHECK: tailrecurse:
+; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi <4 x i32> [ zeroinitializer, [[ENTRY:%.*]] ], [ [[ADD:%.*]], [[IF_END:%.*]] ]
+; CHECK-NEXT: [[A_TR:%.*]] = phi ptr [ [[A]], [[ENTRY]] ], [ [[TMP1:%.*]], [[IF_END]] ]
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A_TR]], null
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET:%.*]], label [[IF_END]]
+; CHECK: common.ret:
+; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = add <4 x i32> zeroinitializer, [[ACCUMULATOR_TR]]
+; CHECK-NEXT: ret <4 x i32> [[ACCUMULATOR_RET_TR]]
+; CHECK: if.end:
+; CHECK-NEXT: [[TMP0:%.*]] = load <4 x i32>, ptr [[A_TR]], align 16
+; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_VECLISTNODE:%.*]], ptr [[A_TR]], i64 0, i32 1
+; CHECK-NEXT: [[TMP1]] = load ptr, ptr [[NEXT]], align 8
+; CHECK-NEXT: [[ADD]] = add <4 x i32> [[TMP0]], [[ACCUMULATOR_TR]]
+; CHECK-NEXT: br label [[TAILRECURSE]]
+;
+entry:
+ %tobool.not = icmp eq ptr %a, null
+ br i1 %tobool.not, label %common.ret, label %if.end
+
+common.ret: ; preds = %entry, %if.end
+ %common.ret.op = phi <4 x i32> [ %add, %if.end ], [ zeroinitializer, %entry ]
+ ret <4 x i32> %common.ret.op
+
+if.end: ; preds = %entry
+ %0 = load <4 x i32>, ptr %a
+ %next = getelementptr inbounds %struct.VecListNode, ptr %a, i64 0, i32 1
+ %1 = load ptr, ptr %next
+ %call = tail call <4 x i32> @test_vector_add_accumulator(ptr %1)
+ %add = add <4 x i32> %0, %call
+ br label %common.ret
+}
+
+; Like the scalar case above, vector sadd.sat is not associative and must not
+; be turned into an accumulator.
+define <4 x i32> @test_non_associative_sadd_sat_vector(ptr %a) local_unnamed_addr {
+; CHECK-LABEL: define <4 x i32> @test_non_associative_sadd_sat_vector(
+; CHECK-SAME: ptr [[A:%.*]]) local_unnamed_addr {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A]], null
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET:%.*]], label [[IF_END:%.*]]
+; CHECK: common.ret:
+; CHECK-NEXT: ret <4 x i32> zeroinitializer
+; CHECK: if.end:
+; CHECK-NEXT: [[TMP0:%.*]] = load <4 x i32>, ptr [[A]], align 16
+; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_VECLISTNODE:%.*]], ptr [[A]], i64 0, i32 1
+; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[NEXT]], align 8
+; CHECK-NEXT: [[CALL:%.*]] = tail call <4 x i32> @test_non_associative_sadd_sat_vector(ptr [[TMP1]])
+; CHECK-NEXT: [[SAT:%.*]] = tail call <4 x i32> @llvm.sadd.sat.v4i32(<4 x i32> [[TMP0]], <4 x i32> [[CALL]])
+; CHECK-NEXT: ret <4 x i32> [[SAT]]
+;
+entry:
+ %tobool.not = icmp eq ptr %a, null
+ br i1 %tobool.not, label %common.ret, label %if.end
+
+common.ret: ; preds = %entry, %if.end
+ %common.ret.op = phi <4 x i32> [ %sat, %if.end ], [ zeroinitializer, %entry ]
+ ret <4 x i32> %common.ret.op
+
+if.end: ; preds = %entry
+ %0 = load <4 x i32>, ptr %a
+ %next = getelementptr inbounds %struct.VecListNode, ptr %a, i64 0, i32 1
+ %1 = load ptr, ptr %next
+ %call = tail call <4 x i32> @test_non_associative_sadd_sat_vector(ptr %1)
+ %sat = tail call <4 x i32> @llvm.sadd.sat.v4i32(<4 x i32> %0, <4 x i32> %call)
+ br label %common.ret
+}
+
+declare <4 x i32> @llvm.sadd.sat.v4i32(<4 x i32>, <4 x i32>)
diff --git a/llvm/test/Transforms/TailCallElim/tre-minmax-intrinsic.ll b/llvm/test/Transforms/TailCallElim/tre-minmax-intrinsic.ll
index f8d9f9f9fbcb7..b62a6215876d4 100644
--- a/llvm/test/Transforms/TailCallElim/tre-minmax-intrinsic.ll
+++ b/llvm/test/Transforms/TailCallElim/tre-minmax-intrinsic.ll
@@ -2,6 +2,7 @@
; RUN: opt < %s -passes=tailcallelim -verify-dom-info -S | FileCheck %s
%struct.ListNode = type { i32, ptr }
+%struct.VecListNode = type { <4 x i32>, ptr }
define i32 @umin(ptr readonly %a) {
; CHECK-LABEL: define i32 @umin
@@ -303,3 +304,83 @@ declare i32 @llvm.umin.i32(i32, i32)
declare i32 @llvm.umax.i32(i32, i32)
declare i32 @llvm.smin.i32(i32, i32)
declare i32 @llvm.smax.i32(i32, i32)
+
+; The identity for a vector min/max accumulator is a splat of the scalar
+; identity.
+
+define <4 x i32> @smax_vector(ptr readonly %a) {
+; CHECK-LABEL: define <4 x i32> @smax_vector
+; CHECK-SAME: (ptr readonly [[A:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[TAILRECURSE:%.*]]
+; CHECK: tailrecurse:
+; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi <4 x i32> [ splat (i32 -2147483648), [[ENTRY:%.*]] ], [ [[MAX:%.*]], [[IF_END:%.*]] ]
+; CHECK-NEXT: [[A_TR:%.*]] = phi ptr [ [[A]], [[ENTRY]] ], [ [[TMP1:%.*]], [[IF_END]] ]
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A_TR]], null
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET:%.*]], label [[IF_END]]
+; CHECK: common.ret:
+; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = tail call <4 x i32> @llvm.smax.v4i32(<4 x i32> zeroinitializer, <4 x i32> [[ACCUMULATOR_TR]])
+; CHECK-NEXT: ret <4 x i32> [[ACCUMULATOR_RET_TR]]
+; CHECK: if.end:
+; CHECK-NEXT: [[TMP0:%.*]] = load <4 x i32>, ptr [[A_TR]], align 16
+; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_VECLISTNODE:%.*]], ptr [[A_TR]], i64 0, i32 1
+; CHECK-NEXT: [[TMP1]] = load ptr, ptr [[NEXT]], align 8
+; CHECK-NEXT: [[MAX]] = tail call <4 x i32> @llvm.smax.v4i32(<4 x i32> [[TMP0]], <4 x i32> [[ACCUMULATOR_TR]])
+; CHECK-NEXT: br label [[TAILRECURSE]]
+;
+entry:
+ %tobool.not = icmp eq ptr %a, null
+ br i1 %tobool.not, label %common.ret, label %if.end
+
+common.ret: ; preds = %entry, %if.end
+ %common.ret.op = phi <4 x i32> [ %max, %if.end ], [ zeroinitializer, %entry ]
+ ret <4 x i32> %common.ret.op
+
+if.end: ; preds = %entry
+ %0 = load <4 x i32>, ptr %a
+ %next = getelementptr inbounds %struct.VecListNode, ptr %a, i64 0, i32 1
+ %1 = load ptr, ptr %next
+ %call = tail call <4 x i32> @smax_vector(ptr %1)
+ %max = tail call <4 x i32> @llvm.smax.v4i32(<4 x i32> %0, <4 x i32> %call)
+ br label %common.ret
+}
+
+define <4 x i32> @smin_vector(ptr readonly %a) {
+; CHECK-LABEL: define <4 x i32> @smin_vector
+; CHECK-SAME: (ptr readonly [[A:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[TAILRECURSE:%.*]]
+; CHECK: tailrecurse:
+; CHECK-NEXT: [[ACCUMULATOR_TR:%.*]] = phi <4 x i32> [ splat (i32 2147483647), [[ENTRY:%.*]] ], [ [[MIN:%.*]], [[IF_END:%.*]] ]
+; CHECK-NEXT: [[A_TR:%.*]] = phi ptr [ [[A]], [[ENTRY]] ], [ [[TMP1:%.*]], [[IF_END]] ]
+; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq ptr [[A_TR]], null
+; CHECK-NEXT: br i1 [[TOBOOL_NOT]], label [[COMMON_RET:%.*]], label [[IF_END]]
+; CHECK: common.ret:
+; CHECK-NEXT: [[ACCUMULATOR_RET_TR:%.*]] = tail call <4 x i32> @llvm.smin.v4i32(<4 x i32> zeroinitializer, <4 x i32> [[ACCUMULATOR_TR]])
+; CHECK-NEXT: ret <4 x i32> [[ACCUMULATOR_RET_TR]]
+; CHECK: if.end:
+; CHECK-NEXT: [[TMP0:%.*]] = load <4 x i32>, ptr [[A_TR]], align 16
+; CHECK-NEXT: [[NEXT:%.*]] = getelementptr inbounds [[STRUCT_VECLISTNODE:%.*]], ptr [[A_TR]], i64 0, i32 1
+; CHECK-NEXT: [[TMP1]] = load ptr, ptr [[NEXT]], align 8
+; CHECK-NEXT: [[MIN]] = tail call <4 x i32> @llvm.smin.v4i32(<4 x i32> [[TMP0]], <4 x i32> [[ACCUMULATOR_TR]])
+; CHECK-NEXT: br label [[TAILRECURSE]]
+;
+entry:
+ %tobool.not = icmp eq ptr %a, null
+ br i1 %tobool.not, label %common.ret, label %if.end
+
+common.ret: ; preds = %entry, %if.end
+ %common.ret.op = phi <4 x i32> [ %min, %if.end ], [ zeroinitializer, %entry ]
+ ret <4 x i32> %common.ret.op
+
+if.end: ; preds = %entry
+ %0 = load <4 x i32>, ptr %a
+ %next = getelementptr inbounds %struct.VecListNode, ptr %a, i64 0, i32 1
+ %1 = load ptr, ptr %next
+ %call = tail call <4 x i32> @smin_vector(ptr %1)
+ %min = tail call <4 x i32> @llvm.smin.v4i32(<4 x i32> %0, <4 x i32> %call)
+ br label %common.ret
+}
+
+declare <4 x i32> @llvm.smax.v4i32(<4 x i32>, <4 x i32>)
+declare <4 x i32> @llvm.smin.v4i32(<4 x i32>, <4 x i32>)
More information about the llvm-commits
mailing list