Index: llvm/include/llvm/Analysis/ValueTracking.h =================================================================== --- llvm/include/llvm/Analysis/ValueTracking.h +++ llvm/include/llvm/Analysis/ValueTracking.h @@ -219,8 +219,15 @@ /// Returns a pair of values, which if passed to llvm.is.fpclass, returns the /// same result as an fcmp with the given operands. +/// +/// If \p LookThroughSrc is true, consider the input value when computing the +/// mask. +/// +/// If \p LookThroughSrc is false, ignore the source value (i.e. the first pair +/// element will always be LHS. std::pair fcmpToClassTest(CmpInst::Predicate Pred, - Value *LHS, Value *RHS); + Value *LHS, Value *RHS, + bool LookThroughSrc = true); struct KnownFPClass { /// Floating-point classes the value could be one of. Index: llvm/lib/Analysis/AssumptionCache.cpp =================================================================== --- llvm/lib/Analysis/AssumptionCache.cpp +++ llvm/lib/Analysis/AssumptionCache.cpp @@ -87,7 +87,7 @@ AddAffected(Cond); CmpInst::Predicate Pred; - if (match(Cond, m_ICmp(Pred, m_Value(A), m_Value(B)))) { + if (match(Cond, m_Cmp(Pred, m_Value(A), m_Value(B)))) { AddAffected(A); AddAffected(B); @@ -128,7 +128,18 @@ if (match(A, m_Add(m_Value(X), m_ConstantInt())) && match(B, m_ConstantInt())) AddAffected(X); + } else if (CmpInst::isFPPredicate(Pred)) { + // fcmp fneg(x), y + // fcmp fabs(x), y + // fcmp fneg(fabs(x)), y + if (match(A, m_FNeg(m_Value(A)))) + AddAffected(A); + if (match(A, m_FAbs(m_Value(A)))) + AddAffected(A); } + } else if (match(Cond, m_Intrinsic(m_Value(A), + m_Value(B)))) { + AddAffected(A); } if (TTI) { Index: llvm/lib/Analysis/ValueTracking.cpp =================================================================== --- llvm/lib/Analysis/ValueTracking.cpp +++ llvm/lib/Analysis/ValueTracking.cpp @@ -4128,7 +4128,8 @@ /// Returns a pair of values, which if passed to llvm.is.fpclass, returns the /// same result as an fcmp with the given operands. std::pair llvm::fcmpToClassTest(FCmpInst::Predicate Pred, - Value *LHS, Value *RHS) { + Value *LHS, Value *RHS, + bool LookThroughSrc) { const APFloat *ConstRHS; if (!match(RHS, m_APFloat(ConstRHS))) return {nullptr, fcNone}; @@ -4150,7 +4151,7 @@ } Value *Src = LHS; - const bool IsFabs = match(LHS, m_FAbs(m_Value(Src))); + const bool IsFabs = LookThroughSrc && match(LHS, m_FAbs(m_Value(Src))); // Compute the test mask that would return true for the ordered comparisons. FPClassTest Mask; @@ -4277,6 +4278,48 @@ return {Src, Mask}; } +static FPClassTest computeKnownFPClassFromAssumes( + const Value *V, const Query &Q) { + FPClassTest KnownFromAssume = fcNone; + + // Try to restrict the range based on information from assumptions. + for (auto &AssumeVH : Q.AC->assumptionsFor(V)) { + if (!AssumeVH) + continue; + CallInst *I = cast(AssumeVH); + assert(I->getParent()->getParent() == Q.CxtI->getParent()->getParent() && + "Got assumption for the wrong function!"); + assert(I->getCalledFunction()->getIntrinsicID() == Intrinsic::assume && + "must be an assume intrinsic"); + + if (!isValidAssumeForContext(I, Q.CxtI, Q.DT)) + continue; + + CmpInst::Predicate Pred; + Value *LHS, *RHS; + uint64_t ClassVal = 0; + if (match(I->getArgOperand(0), m_FCmp(Pred, m_Value(LHS), m_Value(RHS)))) { + auto [TestedValue, TestedMask] = fcmpToClassTest(Pred, LHS, RHS, true); + // First see if we can fold in fabs/fneg into the test. + if (TestedValue == V) + KnownFromAssume |= TestedMask; + else { + // Try again without the lookthrough if we found a different source + // value. + auto [TestedValue, TestedMask] = fcmpToClassTest(Pred, LHS, RHS, false); + if (TestedValue == V) + KnownFromAssume |= TestedMask; + } + } else if (match(I->getArgOperand(0), + m_Intrinsic( + m_Value(LHS), m_ConstantInt(ClassVal)))) { + KnownFromAssume |= static_cast(ClassVal); + } + } + + return KnownFromAssume; +} + // TODO: Merge implementations of isKnownNeverNaN, isKnownNeverInfinity, // CannotBeNegativeZero, cannotBeOrderedLessThanZero into here. void computeKnownFPClass(const Value *V, const APInt &DemandedElts, @@ -4314,6 +4357,9 @@ KnownNotFromFlags |= fcInf; } + if (Q.AC) + KnownNotFromFlags |= computeKnownFPClassFromAssumes(V, Q); + // We no longer need to find out about these bits from inputs if we can // assume this from flags/attributes. InterestedClasses &= ~KnownNotFromFlags & fcAllFlags; Index: llvm/lib/Transforms/IPO/AttributorAttributes.cpp =================================================================== --- llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -10232,6 +10232,9 @@ KnownFPClass KnownFPClass = computeKnownFPClass(&V, DL); addKnownBits(~KnownFPClass.KnownFPClasses); } + + if (Instruction *CtxI = getCtxI()) + followUsesInMBEC(*this, A, getState(), *CtxI); } /// See followUsesInMBEC Index: llvm/test/Transforms/Attributor/nofpclass.ll =================================================================== --- llvm/test/Transforms/Attributor/nofpclass.ll +++ llvm/test/Transforms/Attributor/nofpclass.ll @@ -294,12 +294,12 @@ ; Should be able to infer nofpclass on both %arg uses define float @fcmp_ord_assume_callsite_arg_return(float %arg) { -; CHECK-LABEL: define float @fcmp_ord_assume_callsite_arg_return -; CHECK-SAME: (float returned [[ARG:%.*]]) { +; CHECK-LABEL: define nofpclass(inf zero sub norm) float @fcmp_ord_assume_callsite_arg_return +; CHECK-SAME: (float returned nofpclass(inf zero sub norm) [[ARG:%.*]]) { ; CHECK-NEXT: entry: ; CHECK-NEXT: [[IS_NOT_NAN:%.*]] = fcmp ord float [[ARG]], 0.000000e+00 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[IS_NOT_NAN]]) #[[ATTR4:[0-9]+]] -; CHECK-NEXT: call void @extern.use(float [[ARG]]) +; CHECK-NEXT: call void @extern.use(float nofpclass(inf zero sub norm) [[ARG]]) ; CHECK-NEXT: ret float [[ARG]] ; entry: @@ -451,13 +451,13 @@ } define half @fcmp_assume_issubnormal_callsite_arg_return(half %arg) { -; CHECK-LABEL: define half @fcmp_assume_issubnormal_callsite_arg_return -; CHECK-SAME: (half returned [[ARG:%.*]]) { +; CHECK-LABEL: define nofpclass(zero sub) half @fcmp_assume_issubnormal_callsite_arg_return +; CHECK-SAME: (half returned nofpclass(zero sub) [[ARG:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[FABS:%.*]] = call half @llvm.fabs.f16(half [[ARG]]) #[[ATTR4]] +; CHECK-NEXT: [[FABS:%.*]] = call half @llvm.fabs.f16(half nofpclass(zero sub) [[ARG]]) #[[ATTR4]] ; CHECK-NEXT: [[IS_SUBNORMAL:%.*]] = fcmp olt half [[FABS]], 0xH0400 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[IS_SUBNORMAL]]) #[[ATTR4]] -; CHECK-NEXT: call void @extern.use.f16(half [[ARG]]) +; CHECK-NEXT: call void @extern.use.f16(half nofpclass(zero sub) [[ARG]]) ; CHECK-NEXT: ret half [[ARG]] ; entry: @@ -487,15 +487,15 @@ ; Assume not subnormal or zero, and not infinity define half @fcmp_assume2_callsite_arg_return(half %arg) { -; CHECK-LABEL: define half @fcmp_assume2_callsite_arg_return -; CHECK-SAME: (half returned [[ARG:%.*]]) { +; CHECK-LABEL: define nofpclass(inf norm) half @fcmp_assume2_callsite_arg_return +; CHECK-SAME: (half returned nofpclass(inf norm) [[ARG:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[FABS:%.*]] = call half @llvm.fabs.f16(half [[ARG]]) #[[ATTR4]] +; CHECK-NEXT: [[FABS:%.*]] = call nofpclass(inf) half @llvm.fabs.f16(half nofpclass(inf norm) [[ARG]]) #[[ATTR4]] ; CHECK-NEXT: [[NOT_SUBNORMAL_OR_ZERO:%.*]] = fcmp oge half [[FABS]], 0xH0400 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[NOT_SUBNORMAL_OR_ZERO]]) #[[ATTR4]] ; CHECK-NEXT: [[NOT_INF:%.*]] = fcmp oeq half [[ARG]], 0xH7C00 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[NOT_INF]]) #[[ATTR4]] -; CHECK-NEXT: call void @extern.use.f16(half [[ARG]]) +; CHECK-NEXT: call void @extern.use.f16(half nofpclass(inf norm) [[ARG]]) ; CHECK-NEXT: ret half [[ARG]] ; entry: @@ -511,12 +511,12 @@ } define float @is_fpclass_assume_arg_return(float %arg) { -; CHECK-LABEL: define float @is_fpclass_assume_arg_return -; CHECK-SAME: (float returned [[ARG:%.*]]) { +; CHECK-LABEL: define nofpclass(ninf nzero pnorm) float @is_fpclass_assume_arg_return +; CHECK-SAME: (float returned nofpclass(ninf nzero pnorm) [[ARG:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[CLASS_TEST:%.*]] = call i1 @llvm.is.fpclass.f32(float [[ARG]], i32 noundef 292) #[[ATTR4]] +; CHECK-NEXT: [[CLASS_TEST:%.*]] = call i1 @llvm.is.fpclass.f32(float nofpclass(ninf nzero pnorm) [[ARG]], i32 noundef 292) #[[ATTR4]] ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[CLASS_TEST]]) #[[ATTR4]] -; CHECK-NEXT: call void @extern.use(float [[ARG]]) +; CHECK-NEXT: call void @extern.use(float nofpclass(ninf nzero pnorm) [[ARG]]) ; CHECK-NEXT: ret float [[ARG]] ; entry: @@ -529,16 +529,16 @@ ; Make sure we don't get confused by looking at an unrelated assume ; based on the fabs of the value. define half @assume_fcmp_fabs_with_other_fabs_assume(half %arg) { -; CHECK-LABEL: define half @assume_fcmp_fabs_with_other_fabs_assume -; CHECK-SAME: (half returned [[ARG:%.*]]) { +; CHECK-LABEL: define nofpclass(zero sub) half @assume_fcmp_fabs_with_other_fabs_assume +; CHECK-SAME: (half returned nofpclass(zero sub) [[ARG:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[FABS:%.*]] = call half @llvm.fabs.f16(half [[ARG]]) #[[ATTR4]] +; CHECK-NEXT: [[FABS:%.*]] = call nofpclass(ninf zero sub nnorm) half @llvm.fabs.f16(half nofpclass(zero sub) [[ARG]]) #[[ATTR4]] ; CHECK-NEXT: [[UNRELATED_FABS:%.*]] = fcmp one half [[FABS]], 0xH0000 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[UNRELATED_FABS]]) #[[ATTR4]] ; CHECK-NEXT: [[IS_SUBNORMAL:%.*]] = fcmp olt half [[FABS]], 0xH0400 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[IS_SUBNORMAL]]) #[[ATTR4]] -; CHECK-NEXT: call void @extern.use.f16(half [[ARG]]) -; CHECK-NEXT: call void @extern.use.f16(half [[FABS]]) +; CHECK-NEXT: call void @extern.use.f16(half nofpclass(zero sub) [[ARG]]) +; CHECK-NEXT: call void @extern.use.f16(half nofpclass(ninf zero sub nnorm) [[FABS]]) ; CHECK-NEXT: ret half [[ARG]] ; entry: @@ -556,18 +556,18 @@ ; Make sure if looking through the fabs finds a different source ; value, we still identify a test mask by ignoring the fabs define half @assume_fcmp_fabs_with_other_fabs_assume_fallback(half %arg) { -; CHECK-LABEL: define half @assume_fcmp_fabs_with_other_fabs_assume_fallback -; CHECK-SAME: (half returned [[ARG:%.*]]) { +; CHECK-LABEL: define nofpclass(pinf zero sub) half @assume_fcmp_fabs_with_other_fabs_assume_fallback +; CHECK-SAME: (half returned nofpclass(pinf zero sub) [[ARG:%.*]]) { ; CHECK-NEXT: entry: -; CHECK-NEXT: [[FABS:%.*]] = call half @llvm.fabs.f16(half [[ARG]]) #[[ATTR4]] +; CHECK-NEXT: [[FABS:%.*]] = call nofpclass(inf zero sub nnorm) half @llvm.fabs.f16(half nofpclass(pinf zero sub) [[ARG]]) #[[ATTR4]] ; CHECK-NEXT: [[ONE_INF:%.*]] = fcmp oeq half [[ARG]], 0xH7C00 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[ONE_INF]]) #[[ATTR4]] ; CHECK-NEXT: [[UNRELATED_FABS:%.*]] = fcmp oeq half [[FABS]], 0xH0000 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[UNRELATED_FABS]]) #[[ATTR4]] ; CHECK-NEXT: [[IS_SUBNORMAL:%.*]] = fcmp olt half [[FABS]], 0xH0400 ; CHECK-NEXT: call void @llvm.assume(i1 noundef [[IS_SUBNORMAL]]) #[[ATTR4]] -; CHECK-NEXT: call void @extern.use.f16(half [[ARG]]) -; CHECK-NEXT: call void @extern.use.f16(half [[FABS]]) +; CHECK-NEXT: call void @extern.use.f16(half nofpclass(pinf zero sub) [[ARG]]) +; CHECK-NEXT: call void @extern.use.f16(half nofpclass(inf zero sub nnorm) [[FABS]]) ; CHECK-NEXT: ret half [[ARG]] ; entry: