Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,76 @@ static Instruction *shrinkInsertElt(CastInst &Trunc,
return nullptr;
}

/// Return true if Trunc has a same-block fixed-vector signed min/max DAG
/// that can be rebuilt in the destination type without leaving wide uses.
static bool
collectNarrowableSignedMinMaxDAG(Value *V, FixedVectorType *WideTy,
FixedVectorType *NarrowTy, BasicBlock *BB,
SmallSetVector<Instruction *, 16> &Nodes,
unsigned &NumMinMax) {
auto *I = dyn_cast<Instruction>(V);
if (!I || I->getParent() != BB || I->getType() != WideTy)
return false;

// A shared node in the DAG may be reached by multiple paths.
if (!Nodes.insert(I))
return true;

// Every leaf must be a matching sext from the destination type.
if (auto *Ext = dyn_cast<SExtInst>(I))
return Ext->getSrcTy() == NarrowTy;

// Every non-leaf node must be signed min/max.
auto *MM = dyn_cast<MinMaxIntrinsic>(I);
if (!MM || !MM->isSigned())
return false;

++NumMinMax;
return collectNarrowableSignedMinMaxDAG(MM->getLHS(), WideTy, NarrowTy, BB,
Nodes, NumMinMax) &&
collectNarrowableSignedMinMaxDAG(MM->getRHS(), WideTy, NarrowTy, BB,
Nodes, NumMinMax);
}

/// Return true if Trunc can be replaced by rebuilding its signed min/max
/// operand DAG in the destination type.
static bool canNarrowSignedMinMaxDAG(TruncInst &Trunc) {
auto *WideTy = dyn_cast<FixedVectorType>(Trunc.getSrcTy());
auto *NarrowTy = dyn_cast<FixedVectorType>(Trunc.getDestTy());
if (!WideTy || !NarrowTy)
return false;

if (WideTy->getNumElements() != NarrowTy->getNumElements() ||
!WideTy->getElementType()->isIntegerTy() ||
!NarrowTy->getElementType()->isIntegerTy())
return false;

SmallSetVector<Instruction *, 16> Nodes;
unsigned NumMinMax = 0;

if (!collectNarrowableSignedMinMaxDAG(Trunc.getOperand(0), WideTy, NarrowTy,
Trunc.getParent(), Nodes, NumMinMax))
return false;

// Avoid overlap with existing one-node folds.
if (NumMinMax < 2)
return false;

// Every wide node and extension must become dead after replacing Trunc.
for (Instruction *I : Nodes) {
for (User *U : I->users()) {
if (U == &Trunc)
continue;

auto *UserI = dyn_cast<Instruction>(U);
if (!UserI || !Nodes.contains(UserI))
return false;
}
}

return true;
}

Instruction *InstCombinerImpl::visitTrunc(TruncInst &Trunc) {
if (Instruction *Result = commonCastTransforms(Trunc))
return Result;
Expand All @@ -1056,7 +1126,8 @@ Instruction *InstCombinerImpl::visitTrunc(TruncInst &Trunc) {
// expression tree to something weird like i93 unless the source is also
// strange.
if ((DestTy->isVectorTy() || shouldChangeType(SrcTy, DestTy)) &&
TypeEvaluationHelper::canEvaluateTruncated(Src, DestTy, *this, &Trunc)) {
(TypeEvaluationHelper::canEvaluateTruncated(Src, DestTy, *this, &Trunc) ||
canNarrowSignedMinMaxDAG(Trunc))) {

// If this cast is a truncate, evaluting in a different type always
// eliminates the cast, so it is always a win.
Expand Down
118 changes: 118 additions & 0 deletions llvm/test/Transforms/InstCombine/trunc-minmax-intrinsics.ll
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
; RUN: opt -passes=instcombine %s -S | FileCheck %s

declare void @use_i32(i32)
declare void @use_v8i32(<8 x i32>)

define i8 @umin_mul3_clamp(i8 %x) {
; CHECK-LABEL: define i8 @umin_mul3_clamp(
Expand Down Expand Up @@ -621,3 +622,120 @@ define <vscale x 4 x i8> @smax_sext_scalable_vec(<vscale x 4 x i8> %x, <vscale x
%t = trunc <vscale x 4 x i32> %u to <vscale x 4 x i8>
ret <vscale x 4 x i8> %t
}

define <8 x i16> @smin_smax_dag_shared_sext_vec(
; CHECK-LABEL: define <8 x i16> @smin_smax_dag_shared_sext_vec(
; CHECK-SAME: <8 x i16> [[A:%.*]], <8 x i16> [[B:%.*]], <8 x i16> [[C:%.*]]) {
; CHECK-NEXT: [[LO:%.*]] = call <8 x i16> @llvm.smin.v8i16(<8 x i16> [[A]], <8 x i16> [[B]])
; CHECK-NEXT: [[HI:%.*]] = call <8 x i16> @llvm.smax.v8i16(<8 x i16> [[A]], <8 x i16> [[B]])
; CHECK-NEXT: [[MID:%.*]] = call <8 x i16> @llvm.smin.v8i16(<8 x i16> [[C]], <8 x i16> [[HI]])
; CHECK-NEXT: [[WIDE:%.*]] = call <8 x i16> @llvm.smax.v8i16(<8 x i16> [[MID]], <8 x i16> [[LO]])
; CHECK-NEXT: ret <8 x i16> [[WIDE]]
;
<8 x i16> %a, <8 x i16> %b, <8 x i16> %c) {
%aw = sext <8 x i16> %a to <8 x i32>
%bw = sext <8 x i16> %b to <8 x i32>
%cw = sext <8 x i16> %c to <8 x i32>
%lo = call <8 x i32> @llvm.smin.v8i32(<8 x i32> %aw, <8 x i32> %bw)
%hi = call <8 x i32> @llvm.smax.v8i32(<8 x i32> %aw, <8 x i32> %bw)
%mid = call <8 x i32> @llvm.smin.v8i32(<8 x i32> %cw, <8 x i32> %hi)
%wide = call <8 x i32> @llvm.smax.v8i32(<8 x i32> %mid, <8 x i32> %lo)
%result = trunc nsw <8 x i32> %wide to <8 x i16>
ret <8 x i16> %result
}

; Check that the fold is independent of the element widths and also handles a
; non-adjacent narrowing from i64 to i8.
define <4 x i8> @smin_smax_dag_sext_i8_i64_vec(
; CHECK-LABEL: define <4 x i8> @smin_smax_dag_sext_i8_i64_vec(
; CHECK-SAME: <4 x i8> [[A:%.*]], <4 x i8> [[B:%.*]], <4 x i8> [[C:%.*]]) {
; CHECK-NEXT: [[LO:%.*]] = call <4 x i8> @llvm.smin.v4i8(<4 x i8> [[A]], <4 x i8> [[B]])
; CHECK-NEXT: [[HI:%.*]] = call <4 x i8> @llvm.smax.v4i8(<4 x i8> [[A]], <4 x i8> [[B]])
; CHECK-NEXT: [[MID:%.*]] = call <4 x i8> @llvm.smin.v4i8(<4 x i8> [[C]], <4 x i8> [[HI]])
; CHECK-NEXT: [[WIDE:%.*]] = call <4 x i8> @llvm.smax.v4i8(<4 x i8> [[MID]], <4 x i8> [[LO]])
; CHECK-NEXT: ret <4 x i8> [[WIDE]]
;
<4 x i8> %a, <4 x i8> %b, <4 x i8> %c) {
%aw = sext <4 x i8> %a to <4 x i64>
%bw = sext <4 x i8> %b to <4 x i64>
%cw = sext <4 x i8> %c to <4 x i64>
%lo = call <4 x i64> @llvm.smin.v4i64(<4 x i64> %aw, <4 x i64> %bw)
%hi = call <4 x i64> @llvm.smax.v4i64(<4 x i64> %aw, <4 x i64> %bw)
%mid = call <4 x i64> @llvm.smin.v4i64(<4 x i64> %cw, <4 x i64> %hi)
%wide = call <4 x i64> @llvm.smax.v4i64(<4 x i64> %mid, <4 x i64> %lo)
%result = trunc nsw <4 x i64> %wide to <4 x i8>
ret <4 x i8> %result
}

; Do not build a narrow DAG when that would leave an externally-used copy of
; the wide DAG alive.
define <8 x i16> @smin_smax_dag_external_wide_use_vec(
; CHECK-LABEL: define <8 x i16> @smin_smax_dag_external_wide_use_vec(
; CHECK-SAME: <8 x i16> [[A:%.*]], <8 x i16> [[B:%.*]], <8 x i16> [[C:%.*]]) {
; CHECK-NEXT: [[AW:%.*]] = sext <8 x i16> [[A]] to <8 x i32>
; CHECK-NEXT: [[BW:%.*]] = sext <8 x i16> [[B]] to <8 x i32>
; CHECK-NEXT: [[CW:%.*]] = sext <8 x i16> [[C]] to <8 x i32>
; CHECK-NEXT: [[LO:%.*]] = call <8 x i32> @llvm.smin.v8i32(<8 x i32> [[AW]], <8 x i32> [[BW]])
; CHECK-NEXT: call void @use_v8i32(<8 x i32> [[LO]])
; CHECK-NEXT: [[HI:%.*]] = call <8 x i32> @llvm.smax.v8i32(<8 x i32> [[AW]], <8 x i32> [[BW]])
; CHECK-NEXT: [[MID:%.*]] = call <8 x i32> @llvm.smin.v8i32(<8 x i32> [[CW]], <8 x i32> [[HI]])
; CHECK-NEXT: [[WIDE:%.*]] = call <8 x i32> @llvm.smax.v8i32(<8 x i32> [[MID]], <8 x i32> [[LO]])
; CHECK-NEXT: [[RESULT:%.*]] = trunc nsw <8 x i32> [[WIDE]] to <8 x i16>
; CHECK-NEXT: ret <8 x i16> [[RESULT]]
;
<8 x i16> %a, <8 x i16> %b, <8 x i16> %c) {
%aw = sext <8 x i16> %a to <8 x i32>
%bw = sext <8 x i16> %b to <8 x i32>
%cw = sext <8 x i16> %c to <8 x i32>
%lo = call <8 x i32> @llvm.smin.v8i32(<8 x i32> %aw, <8 x i32> %bw)
call void @use_v8i32(<8 x i32> %lo)
%hi = call <8 x i32> @llvm.smax.v8i32(<8 x i32> %aw, <8 x i32> %bw)
%mid = call <8 x i32> @llvm.smin.v8i32(<8 x i32> %cw, <8 x i32> %hi)
%wide = call <8 x i32> @llvm.smax.v8i32(<8 x i32> %mid, <8 x i32> %lo)
%result = trunc nsw <8 x i32> %wide to <8 x i16>
ret <8 x i16> %result
}

; Do not narrow a DAG with a leaf that is not a sign extension from the
; destination type.
define <8 x i16> @smin_smax_dag_non_sext_leaf_vec(
; CHECK-LABEL: define <8 x i16> @smin_smax_dag_non_sext_leaf_vec(
; CHECK-SAME: <8 x i16> [[A:%.*]], <8 x i16> [[B:%.*]], <8 x i32> [[X:%.*]]) {
; CHECK-NEXT: [[AW:%.*]] = sext <8 x i16> [[A]] to <8 x i32>
; CHECK-NEXT: [[BW:%.*]] = sext <8 x i16> [[B]] to <8 x i32>
; CHECK-NEXT: [[LO:%.*]] = call <8 x i32> @llvm.smin.v8i32(<8 x i32> [[AW]], <8 x i32> [[X]])
; CHECK-NEXT: [[WIDE:%.*]] = call <8 x i32> @llvm.smax.v8i32(<8 x i32> [[LO]], <8 x i32> [[BW]])
; CHECK-NEXT: [[RESULT:%.*]] = trunc nsw <8 x i32> [[WIDE]] to <8 x i16>
; CHECK-NEXT: ret <8 x i16> [[RESULT]]
;
<8 x i16> %a, <8 x i16> %b, <8 x i32> %x) {
%aw = sext <8 x i16> %a to <8 x i32>
%bw = sext <8 x i16> %b to <8 x i32>
%lo = call <8 x i32> @llvm.smin.v8i32(<8 x i32> %aw, <8 x i32> %x)
%wide = call <8 x i32> @llvm.smax.v8i32(<8 x i32> %lo, <8 x i32> %bw)
%result = trunc nsw <8 x i32> %wide to <8 x i16>
ret <8 x i16> %result
}

; The min/max DAG itself proves that the wide result fits in the narrow type,
; so the final trunc does not need an nsw flag.
define <4 x i16> @smin_smax_dag_plain_trunc_vec(
; CHECK-LABEL: define <4 x i16> @smin_smax_dag_plain_trunc_vec(
; CHECK-SAME: <4 x i16> [[A:%.*]], <4 x i16> [[B:%.*]], <4 x i16> [[C:%.*]]) {
; CHECK-NEXT: [[LO:%.*]] = call <4 x i16> @llvm.smin.v4i16(<4 x i16> [[A]], <4 x i16> [[B]])
; CHECK-NEXT: [[HI:%.*]] = call <4 x i16> @llvm.smax.v4i16(<4 x i16> [[A]], <4 x i16> [[B]])
; CHECK-NEXT: [[MID:%.*]] = call <4 x i16> @llvm.smin.v4i16(<4 x i16> [[C]], <4 x i16> [[HI]])
; CHECK-NEXT: [[WIDE:%.*]] = call <4 x i16> @llvm.smax.v4i16(<4 x i16> [[MID]], <4 x i16> [[LO]])
; CHECK-NEXT: ret <4 x i16> [[WIDE]]
;
<4 x i16> %a, <4 x i16> %b, <4 x i16> %c) {
%aw = sext <4 x i16> %a to <4 x i32>
%bw = sext <4 x i16> %b to <4 x i32>
%cw = sext <4 x i16> %c to <4 x i32>
%lo = call <4 x i32> @llvm.smin.v4i32(<4 x i32> %aw, <4 x i32> %bw)
%hi = call <4 x i32> @llvm.smax.v4i32(<4 x i32> %aw, <4 x i32> %bw)
%mid = call <4 x i32> @llvm.smin.v4i32(<4 x i32> %cw, <4 x i32> %hi)
%wide = call <4 x i32> @llvm.smax.v4i32(<4 x i32> %mid, <4 x i32> %lo)
%result = trunc <4 x i32> %wide to <4 x i16>
ret <4 x i16> %result
}