diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp --- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp +++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp @@ -754,6 +754,45 @@ ICmpInst::ICMP_SLT, Op, Constant::getNullValue(Op->getType()), CxtI, DL); } +/// Try to canonicalize min/max(X + C0, C1) as min/max(X, C1 - C0) + C0. This +/// can trigger other combines. +static Instruction *moveAddAfterMinMax(IntrinsicInst *II, + InstCombiner::BuilderTy &Builder) { + Intrinsic::ID MinMaxID = II->getIntrinsicID(); + assert((MinMaxID == Intrinsic::smax || MinMaxID == Intrinsic::smin || + MinMaxID == Intrinsic::umax || MinMaxID == Intrinsic::umin) && + "Expected a min or max intrinsic"); + + // TODO: Match vectors with undef elements, but undef may not propagate. + Value *Op0 = II->getArgOperand(0), *Op1 = II->getArgOperand(1); + Value *X; + const APInt *C0, *C1; + if (!match(Op0, m_OneUse(m_Add(m_Value(X), m_APInt(C0)))) || + !match(Op1, m_APInt(C1))) + return nullptr; + + // Check for necessary no-wrap and overflow constraints. + bool IsSigned = MinMaxID == Intrinsic::smax || MinMaxID == Intrinsic::smin; + auto *Add = cast(Op0); + if ((IsSigned && !Add->hasNoSignedWrap()) || + (!IsSigned && !Add->hasNoUnsignedWrap())) + return nullptr; + + // If the constant difference overflows, then instsimplify should reduce the + // min/max to the add or C1. + bool Overflow; + APInt CDiff = + IsSigned ? C1->ssub_ov(*C0, Overflow) : C1->usub_ov(*C0, Overflow); + assert(!Overflow && "Expected simplify of min/max"); + + // min/max (add X, C0), C1 --> add (min/max X, C1 - C0), C0 + // Note: the "mismatched" no-overflow setting does not propagate. + Constant *NewMinMaxC = ConstantInt::get(II->getType(), CDiff); + Value *NewMinMax = Builder.CreateBinaryIntrinsic(MinMaxID, X, NewMinMaxC); + return IsSigned ? BinaryOperator::CreateNSWAdd(NewMinMax, Add->getOperand(1)) + : BinaryOperator::CreateNUWAdd(NewMinMax, Add->getOperand(1)); +} + /// If we have a clamp pattern like max (min X, 42), 41 -- where the output /// can only be one of two possible constant values -- turn that into a select /// of constants. @@ -1101,6 +1140,9 @@ if (Instruction *I = moveNotAfterMinMax(I1, I0)) return I; + if (Instruction *I = moveAddAfterMinMax(II, Builder)) + return I; + // smax(X, -X) --> abs(X) // smin(X, -X) --> -abs(X) // umax(X, -X) --> -abs(X) diff --git a/llvm/test/Transforms/InstCombine/minmax-intrinsics.ll b/llvm/test/Transforms/InstCombine/minmax-intrinsics.ll --- a/llvm/test/Transforms/InstCombine/minmax-intrinsics.ll +++ b/llvm/test/Transforms/InstCombine/minmax-intrinsics.ll @@ -1869,8 +1869,8 @@ define i8 @smax_offset(i8 %x) { ; CHECK-LABEL: @smax_offset( -; CHECK-NEXT: [[A:%.*]] = add nsw i8 [[X:%.*]], 3 -; CHECK-NEXT: [[M:%.*]] = call i8 @llvm.smax.i8(i8 [[A]], i8 -124) +; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.smax.i8(i8 [[X:%.*]], i8 -127) +; CHECK-NEXT: [[M:%.*]] = add nsw i8 [[TMP1]], 3 ; CHECK-NEXT: ret i8 [[M]] ; %a = add nsw i8 %x, 3 @@ -1878,6 +1878,8 @@ ret i8 %m } +; This is handled by InstSimplify; testing here to confirm assert. + define i8 @smax_offset_limit(i8 %x) { ; CHECK-LABEL: @smax_offset_limit( ; CHECK-NEXT: [[A:%.*]] = add nsw i8 [[X:%.*]], 3 @@ -1888,6 +1890,8 @@ ret i8 %m } +; This is handled by InstSimplify; testing here to confirm assert. + define i8 @smax_offset_overflow(i8 %x) { ; CHECK-LABEL: @smax_offset_overflow( ; CHECK-NEXT: [[A:%.*]] = add nsw i8 [[X:%.*]], 3 @@ -1898,6 +1902,8 @@ ret i8 %m } +; negative test - require nsw + define i8 @smax_offset_may_wrap(i8 %x) { ; CHECK-LABEL: @smax_offset_may_wrap( ; CHECK-NEXT: [[A:%.*]] = add i8 [[X:%.*]], 3 @@ -1909,6 +1915,8 @@ ret i8 %m } +; negative test + define i8 @smax_offset_uses(i8 %x) { ; CHECK-LABEL: @smax_offset_uses( ; CHECK-NEXT: [[A:%.*]] = add nsw i8 [[X:%.*]], 3 @@ -1924,8 +1932,8 @@ define <3 x i8> @smin_offset(<3 x i8> %x) { ; CHECK-LABEL: @smin_offset( -; CHECK-NEXT: [[A:%.*]] = add nuw nsw <3 x i8> [[X:%.*]], -; CHECK-NEXT: [[M:%.*]] = call <3 x i8> @llvm.smin.v3i8(<3 x i8> [[A]], <3 x i8> ) +; CHECK-NEXT: [[TMP1:%.*]] = call <3 x i8> @llvm.smin.v3i8(<3 x i8> [[X:%.*]], <3 x i8> ) +; CHECK-NEXT: [[M:%.*]] = or <3 x i8> [[TMP1]], ; CHECK-NEXT: ret <3 x i8> [[M]] ; %a = add nsw nuw <3 x i8> %x, @@ -1933,6 +1941,8 @@ ret <3 x i8> %m } +; This is handled by InstSimplify; testing here to confirm assert. + define i8 @smin_offset_limit(i8 %x) { ; CHECK-LABEL: @smin_offset_limit( ; CHECK-NEXT: ret i8 -3 @@ -1942,6 +1952,8 @@ ret i8 %m } +; This is handled by InstSimplify; testing here to confirm assert. + define i8 @smin_offset_overflow(i8 %x) { ; CHECK-LABEL: @smin_offset_overflow( ; CHECK-NEXT: ret i8 -3 @@ -1951,6 +1963,8 @@ ret i8 %m } +; negative test - require nsw + define i8 @smin_offset_may_wrap(i8 %x) { ; CHECK-LABEL: @smin_offset_may_wrap( ; CHECK-NEXT: [[A:%.*]] = add nuw i8 [[X:%.*]], 124 @@ -1962,6 +1976,8 @@ ret i8 %m } +; negative test + define i8 @smin_offset_uses(i8 %x) { ; CHECK-LABEL: @smin_offset_uses( ; CHECK-NEXT: [[A:%.*]] = add nsw i8 [[X:%.*]], 124 @@ -1975,10 +1991,12 @@ ret i8 %m } +; Note: 'nsw' must not propagate here. + define <3 x i8> @umax_offset(<3 x i8> %x) { ; CHECK-LABEL: @umax_offset( -; CHECK-NEXT: [[A:%.*]] = add nuw nsw <3 x i8> [[X:%.*]], -; CHECK-NEXT: [[M:%.*]] = call <3 x i8> @llvm.umax.v3i8(<3 x i8> [[A]], <3 x i8> ) +; CHECK-NEXT: [[TMP1:%.*]] = call <3 x i8> @llvm.umax.v3i8(<3 x i8> [[X:%.*]], <3 x i8> ) +; CHECK-NEXT: [[M:%.*]] = add nuw <3 x i8> [[TMP1]], ; CHECK-NEXT: ret <3 x i8> [[M]] ; %a = add nsw nuw <3 x i8> %x, @@ -1986,6 +2004,8 @@ ret <3 x i8> %m } +; This is handled by InstSimplify; testing here to confirm assert. + define i8 @umax_offset_limit(i8 %x) { ; CHECK-LABEL: @umax_offset_limit( ; CHECK-NEXT: [[A:%.*]] = add nuw i8 [[X:%.*]], 3 @@ -1996,6 +2016,8 @@ ret i8 %m } +; This is handled by InstSimplify; testing here to confirm assert. + define i8 @umax_offset_overflow(i8 %x) { ; CHECK-LABEL: @umax_offset_overflow( ; CHECK-NEXT: [[A:%.*]] = add nuw i8 [[X:%.*]], 3 @@ -2006,6 +2028,8 @@ ret i8 %m } +; negative test - require nuw + define i8 @umax_offset_may_wrap(i8 %x) { ; CHECK-LABEL: @umax_offset_may_wrap( ; CHECK-NEXT: [[A:%.*]] = add i8 [[X:%.*]], 3 @@ -2017,6 +2041,8 @@ ret i8 %m } +; negative test + define i8 @umax_offset_uses(i8 %x) { ; CHECK-LABEL: @umax_offset_uses( ; CHECK-NEXT: [[A:%.*]] = add nuw i8 [[X:%.*]], 3 @@ -2032,8 +2058,8 @@ define i8 @umin_offset(i8 %x) { ; CHECK-LABEL: @umin_offset( -; CHECK-NEXT: [[A:%.*]] = add nuw i8 [[X:%.*]], -5 -; CHECK-NEXT: [[M:%.*]] = call i8 @llvm.umin.i8(i8 [[A]], i8 -4) +; CHECK-NEXT: [[DOTNOT:%.*]] = icmp eq i8 [[X:%.*]], 0 +; CHECK-NEXT: [[M:%.*]] = select i1 [[DOTNOT]], i8 -5, i8 -4 ; CHECK-NEXT: ret i8 [[M]] ; %a = add nuw i8 %x, 251 @@ -2041,6 +2067,8 @@ ret i8 %m } +; This is handled by InstSimplify; testing here to confirm assert. + define i8 @umin_offset_limit(i8 %x) { ; CHECK-LABEL: @umin_offset_limit( ; CHECK-NEXT: ret i8 -4 @@ -2050,6 +2078,8 @@ ret i8 %m } +; This is handled by InstSimplify; testing here to confirm assert. + define i8 @umin_offset_overflow(i8 %x) { ; CHECK-LABEL: @umin_offset_overflow( ; CHECK-NEXT: ret i8 -4 @@ -2059,6 +2089,8 @@ ret i8 %m } +; negative test - require nuw + define i8 @umin_offset_may_wrap(i8 %x) { ; CHECK-LABEL: @umin_offset_may_wrap( ; CHECK-NEXT: [[A:%.*]] = add nsw i8 [[X:%.*]], -5 @@ -2070,6 +2102,8 @@ ret i8 %m } +; negative test + define i8 @umin_offset_uses(i8 %x) { ; CHECK-LABEL: @umin_offset_uses( ; CHECK-NEXT: [[A:%.*]] = add nuw i8 [[X:%.*]], -5 @@ -2083,6 +2117,8 @@ ret i8 %m } +; TODO: This could transform, but undef element must not propagate to the new add. + define <3 x i8> @umax_vector_splat_undef(<3 x i8> %x) { ; CHECK-LABEL: @umax_vector_splat_undef( ; CHECK-NEXT: [[A:%.*]] = add nuw <3 x i8> [[X:%.*]],