llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-llvm-transforms Author: Matt Arsenault (arsenm) <details> <summary>Changes</summary> --- Patch is 63.91 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/173897.diff 6 Files Affected: - (modified) llvm/include/llvm/Support/KnownFPClass.h (+14) - (modified) llvm/lib/Analysis/ValueTracking.cpp (+26-87) - (modified) llvm/lib/Support/KnownFPClass.cpp (+92) - (modified) llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp (+114-1) - (modified) llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll (+37-56) - (modified) llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-minimum.ll (+33-48) ``````````diff diff --git a/llvm/include/llvm/Support/KnownFPClass.h b/llvm/include/llvm/Support/KnownFPClass.h index 62df87ad8a67e..2db25eca66df4 100644 --- a/llvm/include/llvm/Support/KnownFPClass.h +++ b/llvm/include/llvm/Support/KnownFPClass.h @@ -173,6 +173,20 @@ struct KnownFPClass { signBitMustBeZero(); } + // Enum of min/max intrinsics to avoid dependency on IR. + enum class MinMaxKind { + minimum, + maximum, + minimumnum, + maximumnum, + minnum, + maxnum + }; + + LLVM_ABI static KnownFPClass + minMaxLike(const KnownFPClass &LHS, const KnownFPClass &RHS, MinMaxKind Kind, + DenormalMode DenormMode = DenormalMode::getDynamic()); + /// Apply the canonicalize intrinsic to this value. This is essentially a /// stronger form of propagateCanonicalizingSrc. LLVM_ABI static KnownFPClass diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp index cddd6f9c25074..281c59f671250 100644 --- a/llvm/lib/Analysis/ValueTracking.cpp +++ b/llvm/lib/Analysis/ValueTracking.cpp @@ -4909,6 +4909,25 @@ static void computeKnownFPClassForFPTrunc(const Operator *Op, // Infinity needs a range check. } +static constexpr KnownFPClass::MinMaxKind getMinMaxKind(Intrinsic::ID IID) { + switch (IID) { + case Intrinsic::minimum: + return KnownFPClass::MinMaxKind::minimum; + case Intrinsic::maximum: + return KnownFPClass::MinMaxKind::maximum; + case Intrinsic::minimumnum: + return KnownFPClass::MinMaxKind::minimumnum; + case Intrinsic::maximumnum: + return KnownFPClass::MinMaxKind::maximumnum; + case Intrinsic::minnum: + return KnownFPClass::MinMaxKind::minnum; + case Intrinsic::maxnum: + return KnownFPClass::MinMaxKind::maxnum; + default: + llvm_unreachable("not a floating-point min-max intrinsic"); + } +} + void computeKnownFPClass(const Value *V, const APInt &DemandedElts, FPClassTest InterestedClasses, KnownFPClass &Known, const SimplifyQuery &Q, unsigned Depth) { @@ -5179,95 +5198,15 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts, computeKnownFPClass(II->getArgOperand(1), DemandedElts, InterestedClasses, KnownRHS, Q, Depth + 1); - bool NeverNaN = KnownLHS.isKnownNeverNaN() || KnownRHS.isKnownNeverNaN(); - Known = KnownLHS | KnownRHS; - - // If either operand is not NaN, the result is not NaN. - if (NeverNaN && - (IID == Intrinsic::minnum || IID == Intrinsic::maxnum || - IID == Intrinsic::minimumnum || IID == Intrinsic::maximumnum)) - Known.knownNot(fcNan); - - if (IID == Intrinsic::maxnum || IID == Intrinsic::maximumnum) { - // If at least one operand is known to be positive, the result must be - // positive. - if ((KnownLHS.cannotBeOrderedLessThanZero() && - KnownLHS.isKnownNeverNaN()) || - (KnownRHS.cannotBeOrderedLessThanZero() && - KnownRHS.isKnownNeverNaN())) - Known.knownNot(KnownFPClass::OrderedLessThanZeroMask); - } else if (IID == Intrinsic::maximum) { - // If at least one operand is known to be positive, the result must be - // positive. - if (KnownLHS.cannotBeOrderedLessThanZero() || - KnownRHS.cannotBeOrderedLessThanZero()) - Known.knownNot(KnownFPClass::OrderedLessThanZeroMask); - } else if (IID == Intrinsic::minnum || IID == Intrinsic::minimumnum) { - // If at least one operand is known to be negative, the result must be - // negative. - if ((KnownLHS.cannotBeOrderedGreaterThanZero() && - KnownLHS.isKnownNeverNaN()) || - (KnownRHS.cannotBeOrderedGreaterThanZero() && - KnownRHS.isKnownNeverNaN())) - Known.knownNot(KnownFPClass::OrderedGreaterThanZeroMask); - } else if (IID == Intrinsic::minimum) { - // If at least one operand is known to be negative, the result must be - // negative. - if (KnownLHS.cannotBeOrderedGreaterThanZero() || - KnownRHS.cannotBeOrderedGreaterThanZero()) - Known.knownNot(KnownFPClass::OrderedGreaterThanZeroMask); - } else - llvm_unreachable("unhandled intrinsic"); - - // Fixup zero handling if denormals could be returned as a zero. - // - // As there's no spec for denormal flushing, be conservative with the - // treatment of denormals that could be flushed to zero. For older - // subtargets on AMDGPU the min/max instructions would not flush the - // output and return the original value. - // - if ((Known.KnownFPClasses & fcZero) != fcNone && - !Known.isKnownNeverSubnormal()) { - const Function *Parent = II->getFunction(); - if (!Parent) - break; + const Function *F = II->getFunction(); - DenormalMode Mode = Parent->getDenormalMode( - II->getType()->getScalarType()->getFltSemantics()); - if (Mode != DenormalMode::getIEEE()) - Known.KnownFPClasses |= fcZero; - } + DenormalMode Mode = + F ? F->getDenormalMode( + II->getType()->getScalarType()->getFltSemantics()) + : DenormalMode::getDynamic(); - if (Known.isKnownNeverNaN()) { - if (KnownLHS.SignBit && KnownRHS.SignBit && - *KnownLHS.SignBit == *KnownRHS.SignBit) { - if (*KnownLHS.SignBit) - Known.signBitMustBeOne(); - else - Known.signBitMustBeZero(); - } else if ((IID == Intrinsic::maximum || IID == Intrinsic::minimum || - IID == Intrinsic::maximumnum || - IID == Intrinsic::minimumnum) || - // FIXME: Should be using logical zero versions - ((KnownLHS.isKnownNeverNegZero() || - KnownRHS.isKnownNeverPosZero()) && - (KnownLHS.isKnownNeverPosZero() || - KnownRHS.isKnownNeverNegZero()))) { - // Don't take sign bit from NaN operands. - if (!KnownLHS.isKnownNeverNaN()) - KnownLHS.SignBit = std::nullopt; - if (!KnownRHS.isKnownNeverNaN()) - KnownRHS.SignBit = std::nullopt; - if ((IID == Intrinsic::maximum || IID == Intrinsic::maximumnum || - IID == Intrinsic::maxnum) && - (KnownLHS.SignBit == false || KnownRHS.SignBit == false)) - Known.signBitMustBeZero(); - else if ((IID == Intrinsic::minimum || IID == Intrinsic::minimumnum || - IID == Intrinsic::minnum) && - (KnownLHS.SignBit == true || KnownRHS.SignBit == true)) - Known.signBitMustBeOne(); - } - } + Known = KnownFPClass::minMaxLike(KnownLHS, KnownRHS, getMinMaxKind(IID), + Mode); break; } case Intrinsic::canonicalize: { diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp index 125bee00c38ff..ee4114cf5a060 100644 --- a/llvm/lib/Support/KnownFPClass.cpp +++ b/llvm/lib/Support/KnownFPClass.cpp @@ -91,6 +91,98 @@ void KnownFPClass::propagateDenormal(const KnownFPClass &Src, } } +KnownFPClass KnownFPClass::minMaxLike(const KnownFPClass &LHS_, + const KnownFPClass &RHS_, MinMaxKind Kind, + DenormalMode Mode) { + KnownFPClass KnownLHS = LHS_; + KnownFPClass KnownRHS = RHS_; + + bool NeverNaN = KnownLHS.isKnownNeverNaN() || KnownRHS.isKnownNeverNaN(); + KnownFPClass Known = KnownLHS | KnownRHS; + + // If either operand is not NaN, the result is not NaN. + if (NeverNaN && + (Kind == MinMaxKind::minnum || Kind == MinMaxKind::maxnum || + Kind == MinMaxKind::minimumnum || Kind == MinMaxKind::maximumnum)) + Known.knownNot(fcNan); + + if (Kind == MinMaxKind::maxnum || Kind == MinMaxKind::maximumnum) { + // If at least one operand is known to be positive, the result must be + // positive. + if ((KnownLHS.cannotBeOrderedLessThanZero() && + KnownLHS.isKnownNeverNaN()) || + (KnownRHS.cannotBeOrderedLessThanZero() && KnownRHS.isKnownNeverNaN())) + Known.knownNot(KnownFPClass::OrderedLessThanZeroMask); + } else if (Kind == MinMaxKind::maximum) { + // If at least one operand is known to be positive, the result must be + // positive. + if (KnownLHS.cannotBeOrderedLessThanZero() || + KnownRHS.cannotBeOrderedLessThanZero()) + Known.knownNot(KnownFPClass::OrderedLessThanZeroMask); + } else if (Kind == MinMaxKind::minnum || Kind == MinMaxKind::minimumnum) { + // If at least one operand is known to be negative, the result must be + // negative. + if ((KnownLHS.cannotBeOrderedGreaterThanZero() && + KnownLHS.isKnownNeverNaN()) || + (KnownRHS.cannotBeOrderedGreaterThanZero() && + KnownRHS.isKnownNeverNaN())) + Known.knownNot(KnownFPClass::OrderedGreaterThanZeroMask); + } else if (Kind == MinMaxKind::minimum) { + // If at least one operand is known to be negative, the result must be + // negative. + if (KnownLHS.cannotBeOrderedGreaterThanZero() || + KnownRHS.cannotBeOrderedGreaterThanZero()) + Known.knownNot(KnownFPClass::OrderedGreaterThanZeroMask); + } else + llvm_unreachable("unhandled intrinsic"); + + // Fixup zero handling if denormals could be returned as a zero. + // + // As there's no spec for denormal flushing, be conservative with the + // treatment of denormals that could be flushed to zero. For older + // subtargets on AMDGPU the min/max instructions would not flush the + // output and return the original value. + // + if ((Known.KnownFPClasses & fcZero) != fcNone && + !Known.isKnownNeverSubnormal()) { + if (Mode != DenormalMode::getIEEE()) + Known.KnownFPClasses |= fcZero; + } + + if (Known.isKnownNeverNaN()) { + if (KnownLHS.SignBit && KnownRHS.SignBit && + *KnownLHS.SignBit == *KnownRHS.SignBit) { + if (*KnownLHS.SignBit) + Known.signBitMustBeOne(); + else + Known.signBitMustBeZero(); + } else if ((Kind == MinMaxKind::maximum || Kind == MinMaxKind::minimum || + Kind == MinMaxKind::maximumnum || + Kind == MinMaxKind::minimumnum) || + // FIXME: Should be using logical zero versions + ((KnownLHS.isKnownNeverNegZero() || + KnownRHS.isKnownNeverPosZero()) && + (KnownLHS.isKnownNeverPosZero() || + KnownRHS.isKnownNeverNegZero()))) { + // Don't take sign bit from NaN operands. + if (!KnownLHS.isKnownNeverNaN()) + KnownLHS.SignBit = std::nullopt; + if (!KnownRHS.isKnownNeverNaN()) + KnownRHS.SignBit = std::nullopt; + if ((Kind == MinMaxKind::maximum || Kind == MinMaxKind::maximumnum || + Kind == MinMaxKind::maxnum) && + (KnownLHS.SignBit == false || KnownRHS.SignBit == false)) + Known.signBitMustBeZero(); + else if ((Kind == MinMaxKind::minimum || Kind == MinMaxKind::minimumnum || + Kind == MinMaxKind::minnum) && + (KnownLHS.SignBit == true || KnownRHS.SignBit == true)) + Known.signBitMustBeOne(); + } + } + + return Known; +} + KnownFPClass KnownFPClass::canonicalize(const KnownFPClass &KnownSrc, DenormalMode DenormMode) { KnownFPClass Known; diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp index 33ece6c2b69d8..762f7421631b3 100644 --- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp +++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp @@ -2077,7 +2077,8 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V, } case Instruction::Call: { CallInst *CI = cast<CallInst>(I); - switch (CI->getIntrinsicID()) { + const Intrinsic::ID IID = CI->getIntrinsicID(); + switch (IID) { case Intrinsic::fabs: if (SimplifyDemandedFPClass(I, 0, llvm::inverse_fabs(DemandedMask), Known, Depth + 1)) @@ -2111,6 +2112,118 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V, Known.copysign(KnownSign); break; } + case Intrinsic::maximum: + case Intrinsic::minimum: { + KnownFPClass KnownLHS, KnownRHS; + + // We can't tell much based on the demanded result without inspecting the + // operands (e.g., a known-positive result could have been clamped), but + // we can still prune known-nan inputs. + FPClassTest SrcDemandedMask = DemandedMask | ~fcNan; + + if (SimplifyDemandedFPClass(CI, 1, SrcDemandedMask, KnownRHS, + Depth + 1) || + SimplifyDemandedFPClass(CI, 0, SrcDemandedMask, KnownLHS, Depth + 1)) + return I; + + /// Propagate nnan-ness to simplify edge case checks. + if ((DemandedMask & fcNan) == fcNone) { + KnownLHS.knownNot(fcNan); + KnownRHS.knownNot(fcNan); + } + + if (IID == Intrinsic::maximum) { + // If at least one operand is known to be positive and the other + // negative, the result must be the positive (unless the other operand + // may be propagating a nan). + if (KnownLHS.isKnownNever(fcNegative) && + KnownRHS.isKnownNever(fcPositive | fcNan)) + return CI->getArgOperand(0); + + if (KnownRHS.isKnownNever(fcNegative) && + KnownLHS.isKnownNever(fcPositive | fcNan)) + return CI->getArgOperand(1); + + // If one value must be pinf, the result is pinf or a propagated nan. + if (KnownLHS.isKnownAlways(fcPosInf | fcNan) && + KnownRHS.isKnownNever(fcNan)) + return CI->getArgOperand(0); + + if (KnownRHS.isKnownAlways(fcPosInf | fcNan) && + KnownLHS.isKnownNever(fcNan)) + return CI->getArgOperand(1); + + // If one value must be ninf, the other value must be returned or a + // propagated nan. + if (KnownLHS.isKnownAlways(fcNegInf | fcNan) && + KnownRHS.isKnownNever(fcNan)) + return CI->getArgOperand(1); + + if (KnownRHS.isKnownAlways(fcNegInf | fcNan) && + KnownLHS.isKnownNever(fcNan)) + return CI->getArgOperand(0); + } else { + // If one operand is known to be negative, and the other positive, the + // result must be the negative (unless the other operand may be + // propagating a nan). + if (KnownLHS.isKnownNever(fcPositive) && + KnownRHS.isKnownNever(fcNegative | fcNan)) + return CI->getArgOperand(0); + + if (KnownRHS.isKnownNever(fcPositive) && + KnownLHS.isKnownNever(fcNegative | fcNan)) + return CI->getArgOperand(1); + + // If one value must be ninf, the result is ninf or a propagated nan. + if (KnownLHS.isKnownAlways(fcNegInf | fcNan) && + KnownRHS.isKnownNever(fcNan)) + return CI->getArgOperand(0); + + if (KnownRHS.isKnownAlways(fcNegInf | fcNan) && + KnownLHS.isKnownNever(fcNan)) + return CI->getArgOperand(1); + + // If one value must be pinf, the other value must be returned or a + // propagated nan. + if (KnownLHS.isKnownAlways(fcPosInf | fcNan) && + KnownRHS.isKnownNever(fcNan)) + return CI->getArgOperand(1); + + if (KnownRHS.isKnownAlways(fcPosInf | fcNan) && + KnownLHS.isKnownNever(fcNan)) + return CI->getArgOperand(0); + } + + Type *EltTy = VTy->getScalarType(); + DenormalMode Mode = F.getDenormalMode(EltTy->getFltSemantics()); + Known = KnownFPClass::minMaxLike(KnownLHS, KnownRHS, + IID == Intrinsic::maximum + ? KnownFPClass::MinMaxKind::maximum + : KnownFPClass::MinMaxKind::minimum, + Mode); + + FPClassTest ValidResults = DemandedMask & Known.KnownFPClasses; + + if (Constant *SingleVal = + getFPClassConstant(VTy, ValidResults, /*IsCanonicalizing=*/true)) + return SingleVal; + + auto *FPOp = cast<FPMathOperator>(CI); + + bool ChangedFlags = false; + + // TODO: Add NSZ flag if we know the result will not be sensitive on the + // sign of 0. + if (!FPOp->hasNoNaNs() && (ValidResults & fcNan) == fcNone) { + CI->setHasNoNaNs(true); + ChangedFlags = true; + } + + if (ChangedFlags) + return FPOp; + + return nullptr; + } case Intrinsic::exp: case Intrinsic::exp2: case Intrinsic::exp10: { diff --git a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll index 99e03f6b12e5c..b7c41ff6c3c64 100644 --- a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll +++ b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll @@ -25,8 +25,7 @@ declare nofpclass(inf norm sub zero) float @returns_nan() define nofpclass(inf norm sub zero) float @ret_only_nan(float %x, float %y) { ; CHECK-LABEL: define nofpclass(inf zero sub norm) float @ret_only_nan( ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) { -; CHECK-NEXT: [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]]) -; CHECK-NEXT: ret float [[RESULT]] +; CHECK-NEXT: ret float 0x7FF8000000000000 ; %result = call float @llvm.maximum.f32(float %x, float %y) ret float %result @@ -55,7 +54,7 @@ define nofpclass(inf norm sub zero snan) float @ret_only_qnan(float %x, float %y define nofpclass(nan norm sub zero) float @ret_only_inf(float %x, float %y) { ; CHECK-LABEL: define nofpclass(nan zero sub norm) float @ret_only_inf( ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) { -; CHECK-NEXT: [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]]) +; CHECK-NEXT: [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[X]], float [[Y]]) ; CHECK-NEXT: ret float [[RESULT]] ; %result = call float @llvm.maximum.f32(float %x, float %y) @@ -83,7 +82,7 @@ define nofpclass(nan ninf norm sub zero) float @ret_only_pinf(float %x, float %y define nofpclass(inf nan norm sub) float @ret_only_zero(float %x, float %y) { ; CHECK-LABEL: define nofpclass(nan inf sub norm) float @ret_only_zero( ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) { -; CHECK-NEXT: [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]]) +; CHECK-NEXT: [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[X]], float [[Y]]) ; CHECK-NEXT: ret float [[RESULT]] ; %result = call float @llvm.maximum.f32(float %x, float %y) @@ -111,7 +110,7 @@ define nofpclass(inf nan norm sub pzero) float @ret_only_nzero(float %x, float % define nofpclass(nan) float @ret_no_nans(float %x, float %y) { ; CHECK-LABEL: define nofpclass(nan) float @ret_no_nans( ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) { -; CHECK-NEXT: [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]]) +; CHECK-NEXT: [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[X]], float [[Y]]) ; CHECK-NEXT: ret float [[RESULT]] ; %result = call float @llvm.maximum.f32(float %x, float %y) @@ -131,7 +130,7 @@ define nofpclass(inf) float @ret_no_infs(float %x, float %y) { define nofpclass(nan inf) float @ret_no_nans_no_infs(float %x, float %y) { ; CHECK-LABEL: define nofpclass(nan inf) float @ret_no_nans_no_infs( ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) { -; CHECK-NEXT: [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]]) +; CHECK-NEXT: [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[X]], float [[Y]]) ; CHECK-NEXT: ret float [[RESULT]] ; %result = call float @llvm.maximum.f32(float %x, float %y) @@ -143,8 +142,7 @@ define nofpclass(ninf nnorm nsub nzero) float @ret_known_positive_or_nan__maximu ; CHECK-LABEL: define nofpclass(ninf nzero nsub nnorm) float @ret_known_positive_or_nan__maximum__negative_or_nan___negative_or_nan() { ; CHECK-NEXT: [[MUST_BE_NEGATIVE_OR_NAN0:%.*]] = call float @returns_negative_or_nan() ; CHECK-NEXT: [[MUST_BE_NEGATIVE_OR_NAN1:%.*]] = call float @returns_negative_or_nan() -; CHECK-NEXT: ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/173897 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
