Index: llvm/docs/LangRef.rst =================================================================== --- llvm/docs/LangRef.rst +++ llvm/docs/LangRef.rst @@ -1393,6 +1393,79 @@ undefined. Note that this does not refer to padding introduced by the type's storage representation. +.. _nofpclass: + +``nofpclass()`` + This attribute applies to parameters and return values with + floating-point and vector of floating-point types, as well as + arrays of such types. The test mask has the same format as the + second argument to the :ref:`llvm.is.fpclass `, + and indicates which classes of floating-point values are not + permitted for the value. For example a bitmask of 3 indicates + the parameter may not be a NaN. + + If the value is a floating-point class indicated by the + ``nofpclass`` test mask, a :ref:`poison value ` is + passed or returned instead. + +.. code-block:: text + :caption: The following invariants hold + + @llvm.is.fpclass(nofpclass(test_mask) %x, test_mask) => false + @llvm.is.fpclass(nofpclass(test_mask) %x, ~test_mask) => true + nofpclass(all) => poison +.. + + In textual IR, various string names are supported for readability + and can be combined. For example ``nofpclass(nan pinf nzero)`` + evaluates to a mask of 547. + + This does not depend on the floating-point environment. For + example, a function parameter marked ``nofpclass(zero)`` indicates + no zero inputs. If this is applied to an argument in a function + marked with :ref:`\"denormal-fp-math\" ` + indicating zero treatment of input denormals, it does not imply the + value cannot be a denormal value which would compare equal to 0. + +.. table:: Recognized test mask names + + +-------+----------------------+---------------+ + | Name | floating-point class | Bitmask value | + +=======+======================+===============+ + | nan | Any NaN | 3 | + +-------+----------------------+---------------+ + | inf | +/- infinity | 516 | + +-------+----------------------+---------------+ + | norm | +/- normal | 26 | + +-------+----------------------+---------------+ + | sub | +/- subnormal | 144 | + +-------+----------------------+---------------+ + | zero | +/- 0 | 96 | + +-------+----------------------+---------------+ + | all | All values | 1023 | + +-------+----------------------+---------------+ + | snan | Signaling NaN | 1 | + +-------+----------------------+---------------+ + | qnan | Quiet NaN | 2 | + +-------+----------------------+---------------+ + | ninf | Negative infinity | 4 | + +-------+----------------------+---------------+ + | nnorm | Negative normal | 8 | + +-------+----------------------+---------------+ + | nsub | Negative subnormal | 16 | + +-------+----------------------+---------------+ + | nzero | Negative zero | 32 | + +-------+----------------------+---------------+ + | pzero | Positive zero | 64 | + +-------+----------------------+---------------+ + | psub | Positive subnormal | 128 | + +-------+----------------------+---------------+ + | pnorm | Positive normal | 256 | + +-------+----------------------+---------------+ + | pinf | Positive infinity | 512 | + +-------+----------------------+---------------+ + + ``alignstack()`` This indicates the alignment that should be considered by the backend when assigning this parameter to a stack slot during calling convention @@ -2144,6 +2217,8 @@ might otherwise be set or cleared by calling this function. LLVM will not introduce any new floating-point instructions that may trap. +.. _denormal_fp_math: + ``"denormal-fp-math"`` This indicates the denormal (subnormal) handling that may be assumed for the default floating-point environment. This is a @@ -24901,6 +24976,8 @@ These functions get properties of floating-point values. +.. _llvm.is.fpclass: + '``llvm.is.fpclass``' Intrinsic ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Index: llvm/docs/ReleaseNotes.rst =================================================================== --- llvm/docs/ReleaseNotes.rst +++ llvm/docs/ReleaseNotes.rst @@ -53,6 +53,9 @@ * Typed pointers are no longer supported. See the `opaque pointers `__ documentation for migration instructions. +* The ``nofpclass`` attribute was introduced. This allows more + optimizations around special floating point value comparisons. + Changes to building LLVM ------------------------ Index: llvm/include/llvm/ADT/FloatingPointMode.h =================================================================== --- llvm/include/llvm/ADT/FloatingPointMode.h +++ llvm/include/llvm/ADT/FloatingPointMode.h @@ -197,6 +197,7 @@ /// Floating-point class tests, supported by 'is_fpclass' intrinsic. Actual /// test may be an OR combination of basic tests. enum FPClassTest { + fcNone = 0x0000, fcSNan = 0x0001, fcQNan = 0x0002, fcNegInf = 0x0004, Index: llvm/include/llvm/AsmParser/LLParser.h =================================================================== --- llvm/include/llvm/AsmParser/LLParser.h +++ llvm/include/llvm/AsmParser/LLParser.h @@ -294,6 +294,7 @@ bool parseOptionalUWTableKind(UWTableKind &Kind); bool parseAllocKind(AllocFnKind &Kind); std::optional parseMemoryAttr(); + unsigned parseNoFPClassAttr(); bool parseScopeAndOrdering(bool IsAtomic, SyncScope::ID &SSID, AtomicOrdering &Ordering); bool parseScope(SyncScope::ID &SSID); Index: llvm/include/llvm/AsmParser/LLToken.h =================================================================== --- llvm/include/llvm/AsmParser/LLToken.h +++ llvm/include/llvm/AsmParser/LLToken.h @@ -195,6 +195,24 @@ kw_inaccessiblememonly, kw_inaccessiblemem_or_argmemonly, + // nofpclass attribute: + kw_all, + kw_nan, + kw_snan, + kw_qnan, + kw_inf, + // kw_ninf, - already an fmf + kw_pinf, + kw_norm, + kw_nnorm, + kw_pnorm, + // kw_sub, - already an instruction + kw_nsub, + kw_psub, + kw_zero, + kw_nzero, + kw_pzero, + kw_type, kw_opaque, Index: llvm/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -712,6 +712,7 @@ ATTR_KIND_FNRETTHUNK_EXTERN = 84, ATTR_KIND_SKIP_PROFILE = 85, ATTR_KIND_MEMORY = 86, + ATTR_KIND_NOFPCLASS = 87, }; enum ComdatSelectionKindCodes { Index: llvm/include/llvm/IR/Argument.h =================================================================== --- llvm/include/llvm/IR/Argument.h +++ llvm/include/llvm/IR/Argument.h @@ -63,6 +63,10 @@ /// number of bytes known to be dereferenceable. Otherwise, zero is returned. uint64_t getDereferenceableOrNullBytes() const; + /// If this argument has nofpclass attribute, return the mask representing + /// disallowed floating-point values. Otherwise, fcNone is returned. + FPClassTest getNoFPClass() const; + /// Return true if this argument has the byval attribute. bool hasByValAttr() const; Index: llvm/include/llvm/IR/Attributes.h =================================================================== --- llvm/include/llvm/IR/Attributes.h +++ llvm/include/llvm/IR/Attributes.h @@ -18,6 +18,7 @@ #include "llvm-c/Types.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/BitmaskEnum.h" +#include "llvm/ADT/FloatingPointMode.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Config/llvm-config.h" @@ -148,6 +149,7 @@ static Attribute getWithInAllocaType(LLVMContext &Context, Type *Ty); static Attribute getWithUWTableKind(LLVMContext &Context, UWTableKind Kind); static Attribute getWithMemoryEffects(LLVMContext &Context, MemoryEffects ME); + static Attribute getWithNoFPClass(LLVMContext &Context, unsigned Mask); /// For a typed attribute, return the equivalent attribute with the type /// changed to \p ReplacementTy. @@ -249,6 +251,9 @@ /// Returns memory effects. MemoryEffects getMemoryEffects() const; + // Return the FPClassTest for nofpclass + FPClassTest getNoFPClass() const; + /// The Attribute is converted to a string of equivalent mnemonic. This /// is, presumably, for writing out the mnemonics for the assembly writer. std::string getAsString(bool InAttrGrp = false) const; @@ -383,6 +388,7 @@ UWTableKind getUWTableKind() const; AllocFnKind getAllocKind() const; MemoryEffects getMemoryEffects() const; + FPClassTest getNoFPClass() const; std::string getAsString(bool InAttrGrp = false) const; /// Return true if this attribute set belongs to the LLVMContext. @@ -877,6 +883,12 @@ /// arg. uint64_t getParamDereferenceableOrNullBytes(unsigned ArgNo) const; + /// Get the disallowed floating-point classes of the return value. + FPClassTest getRetNoFPClass() const; + + /// Get the disallowed floating-point classes of the argument value. + FPClassTest getParamNoFPClass(unsigned Index) const; + /// Get the unwind table kind requested for the function. UWTableKind getUWTableKind() const; @@ -1236,6 +1248,9 @@ /// Add memory effect attribute. AttrBuilder &addMemoryAttr(MemoryEffects ME); + // Add nofpclass attribute + AttrBuilder &addNoFPClassAttr(unsigned NoFPClassMask); + ArrayRef attrs() const { return Attrs; } bool operator==(const AttrBuilder &B) const; Index: llvm/include/llvm/IR/Attributes.td =================================================================== --- llvm/include/llvm/IR/Attributes.td +++ llvm/include/llvm/IR/Attributes.td @@ -118,6 +118,9 @@ /// Memory effects of the function. def Memory : IntAttr<"memory", [FnAttr]>; +/// Forbidden floating-point classes. +def NoFPClass : IntAttr<"nofpclass", [ParamAttr, RetAttr]>; + /// Function must be optimized for size first. def MinSize : EnumAttr<"minsize", [FnAttr]>; Index: llvm/include/llvm/IR/Function.h =================================================================== --- llvm/include/llvm/IR/Function.h +++ llvm/include/llvm/IR/Function.h @@ -483,6 +483,11 @@ return AttributeSets.getParamDereferenceableOrNullBytes(ArgNo); } + /// Extract the nofpclass attribute for a parameter. + FPClassTest getParamNoFPClass(unsigned ArgNo) const { + return AttributeSets.getParamNoFPClass(ArgNo); + } + /// Determine if the function is presplit coroutine. bool isPresplitCoroutine() const { return hasFnAttribute(Attribute::PresplitCoroutine); Index: llvm/include/llvm/IR/InstrTypes.h =================================================================== --- llvm/include/llvm/IR/InstrTypes.h +++ llvm/include/llvm/IR/InstrTypes.h @@ -1833,6 +1833,28 @@ return Attrs.getParamDereferenceableOrNullBytes(i); } + /// Extract a test mask for disallowed floating-point value classes for the + /// return value. + FPClassTest getRetNoFPClass() const { + unsigned Mask = Attrs.getRetNoFPClass(); + + // FIXME: Use LLVM_MARK_AS_BITMASK_ENUM with FPClassTest + if (const Function *F = getCalledFunction()) + Mask |= static_cast(F->getAttributes().getRetNoFPClass()); + return static_cast(Mask); + } + + /// Extract a test mask for disallowed floating-point value classes for the + /// parameter. + FPClassTest getParamNoFPClass(unsigned i) const { + unsigned Mask = Attrs.getParamNoFPClass(i); + + // FIXME: Use LLVM_MARK_AS_BITMASK_ENUM with FPClassTest + if (const Function *F = getCalledFunction()) + Mask |= static_cast(F->getAttributes().getParamNoFPClass(i)); + return static_cast(Mask); + } + /// Return true if the return value is known to be not null. /// This may be because it has the nonnull attribute, or because at least /// one byte is dereferenceable and the pointer is in addrspace(0). Index: llvm/lib/Analysis/ValueTracking.cpp =================================================================== --- llvm/lib/Analysis/ValueTracking.cpp +++ llvm/lib/Analysis/ValueTracking.cpp @@ -3775,6 +3775,18 @@ if (FPMathOp->hasNoInfs()) return true; + if (const auto *Arg = dyn_cast(V)) { + if ((Arg->getNoFPClass() & fcInf) == fcInf) + return true; + } + + // TODO: Use fpclass like API for isKnown queries and distinguish +inf from + // -inf. + if (const auto *CB = dyn_cast(V)) { + if ((CB->getRetNoFPClass() & fcInf) == fcInf) + return true; + } + // Handle scalar constants. if (auto *CFP = dyn_cast(V)) return !CFP->isInfinity(); @@ -3903,6 +3915,19 @@ if (FPMathOp->hasNoNaNs()) return true; + if (const auto *Arg = dyn_cast(V)) { + if ((Arg->getNoFPClass() & fcNan) == fcNan) + return true; + } + + // TODO: Use fpclass like API for isKnown queries and distinguish snan from + // qnan. + if (const auto *CB = dyn_cast(V)) { + FPClassTest Mask = CB->getRetNoFPClass(); + if ((Mask & fcNan) == fcNan) + return true; + } + // Handle scalar constants. if (auto *CFP = dyn_cast(V)) return !CFP->isNaN(); Index: llvm/lib/AsmParser/LLLexer.cpp =================================================================== --- llvm/lib/AsmParser/LLLexer.cpp +++ llvm/lib/AsmParser/LLLexer.cpp @@ -653,6 +653,24 @@ KEYWORD(inaccessiblememonly); KEYWORD(inaccessiblemem_or_argmemonly); + // nofpclass attribute + KEYWORD(all); + KEYWORD(nan); + KEYWORD(snan); + KEYWORD(qnan); + KEYWORD(inf); + // ninf already a keyword + KEYWORD(pinf); + KEYWORD(norm); + KEYWORD(nnorm); + KEYWORD(pnorm); + // sub already a keyword + KEYWORD(nsub); + KEYWORD(psub); + KEYWORD(zero); + KEYWORD(nzero); + KEYWORD(pzero); + KEYWORD(type); KEYWORD(opaque); Index: llvm/lib/AsmParser/LLParser.cpp =================================================================== --- llvm/lib/AsmParser/LLParser.cpp +++ llvm/lib/AsmParser/LLParser.cpp @@ -1469,6 +1469,14 @@ B.addMemoryAttr(*ME); return false; } + case Attribute::NoFPClass: { + if (unsigned NoFPClass = parseNoFPClassAttr()) { + B.addNoFPClassAttr(NoFPClass); + return false; + } + + return true; + } default: B.addAttribute(Attr); Lex.Lex(); @@ -2336,6 +2344,86 @@ return std::nullopt; } +static unsigned keywordToFPClassTest(lltok::Kind Tok) { + switch (Tok) { + case lltok::kw_all: + return fcAllFlags; + case lltok::kw_nan: + return fcNan; + case lltok::kw_snan: + return fcSNan; + case lltok::kw_qnan: + return fcQNan; + case lltok::kw_inf: + return fcInf; + case lltok::kw_ninf: + return fcNegInf; + case lltok::kw_pinf: + return fcPosInf; + case lltok::kw_norm: + return fcNormal; + case lltok::kw_nnorm: + return fcNegNormal; + case lltok::kw_pnorm: + return fcPosNormal; + case lltok::kw_sub: + return fcSubnormal; + case lltok::kw_nsub: + return fcNegSubnormal; + case lltok::kw_psub: + return fcPosSubnormal; + case lltok::kw_zero: + return fcZero; + case lltok::kw_nzero: + return fcNegZero; + case lltok::kw_pzero: + return fcPosZero; + default: + return 0; + } +} + +unsigned LLParser::parseNoFPClassAttr() { + unsigned Mask = fcNone; + + Lex.Lex(); + if (!EatIfPresent(lltok::lparen)) { + tokError("expected '('"); + return 0; + } + + do { + uint64_t Value = 0; + unsigned TestMask = keywordToFPClassTest(Lex.getKind()); + if (TestMask != 0) { + Mask |= TestMask; + // TODO: Disallow overlapping masks to avoid copy paste errors + } else if (Mask == 0 && Lex.getKind() == lltok::APSInt && + !parseUInt64(Value)) { + if (Value == 0 || (Value & ~fcAllFlags) != 0) { + error(Lex.getLoc(), "invalid mask value for 'nofpclass'"); + return 0; + } + + if (!EatIfPresent(lltok::rparen)) { + error(Lex.getLoc(), "expected ')'"); + return 0; + } + + return Value; + } else { + error(Lex.getLoc(), "expected nofpclass test mask"); + return 0; + } + + Lex.Lex(); + if (EatIfPresent(lltok::rparen)) + return Mask; + } while (1); + + llvm_unreachable("unterminated nofpclass attribute"); +} + /// parseOptionalCommaAlign /// ::= /// ::= ',' align 4 Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1928,6 +1928,8 @@ return Attribute::JumpTable; case bitc::ATTR_KIND_MEMORY: return Attribute::Memory; + case bitc::ATTR_KIND_NOFPCLASS: + return Attribute::NoFPClass; case bitc::ATTR_KIND_MIN_SIZE: return Attribute::MinSize; case bitc::ATTR_KIND_NAKED: @@ -2205,6 +2207,8 @@ B.addAllocKindAttr(static_cast(Record[++i])); else if (Kind == Attribute::Memory) B.addMemoryAttr(MemoryEffects::createFromIntValue(Record[++i])); + else if (Kind == Attribute::NoFPClass) + B.addNoFPClassAttr(Record[++i] & fcAllFlags); } else if (Record[i] == 3 || Record[i] == 4) { // String attribute bool HasValue = (Record[i++] == 4); SmallString<64> KindStr; Index: llvm/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -681,6 +681,8 @@ return bitc::ATTR_KIND_ALLOC_KIND; case Attribute::Memory: return bitc::ATTR_KIND_MEMORY; + case Attribute::NoFPClass: + return bitc::ATTR_KIND_NOFPCLASS; case Attribute::Naked: return bitc::ATTR_KIND_NAKED; case Attribute::Nest: Index: llvm/lib/IR/AttributeImpl.h =================================================================== --- llvm/lib/IR/AttributeImpl.h +++ llvm/lib/IR/AttributeImpl.h @@ -266,6 +266,7 @@ UWTableKind getUWTableKind() const; AllocFnKind getAllocKind() const; MemoryEffects getMemoryEffects() const; + FPClassTest getNoFPClass() const; std::string getAsString(bool InAttrGrp) const; Type *getAttributeType(Attribute::AttrKind Kind) const; Index: llvm/lib/IR/Attributes.cpp =================================================================== --- llvm/lib/IR/Attributes.cpp +++ llvm/lib/IR/Attributes.cpp @@ -216,6 +216,11 @@ return get(Context, Memory, ME.toIntValue()); } +Attribute Attribute::getWithNoFPClass(LLVMContext &Context, + unsigned ClassMask) { + return get(Context, NoFPClass, ClassMask); +} + Attribute Attribute::getWithAllocSizeArgs(LLVMContext &Context, unsigned ElemSizeArg, const std::optional &NumElemsArg) { @@ -261,6 +266,16 @@ .Default(false); } +/// Returns true if this is a type legal for the 'nofpclass' attribute. This +/// follows the same type rules as FPMathOperator. +/// +/// TODO: Consider relaxing to any FP type struct fields. +static bool isNoFPClassCompatibleType(Type *Ty) { + while (ArrayType *ArrTy = dyn_cast(Ty)) + Ty = ArrTy->getElementType(); + return Ty->isFPOrFPVectorTy(); +} + //===----------------------------------------------------------------------===// // Attribute Accessor Methods //===----------------------------------------------------------------------===// @@ -396,6 +411,12 @@ return MemoryEffects::createFromIntValue(pImpl->getValueAsInt()); } +FPClassTest Attribute::getNoFPClass() const { + assert(hasAttribute(Attribute::NoFPClass) && + "Can only call getNoFPClass() on nofpclass attribute"); + return static_cast(pImpl->getValueAsInt()); +} + static const char *getModRefStr(ModRefInfo MR) { switch (MR) { case ModRefInfo::NoModRef: @@ -410,6 +431,59 @@ llvm_unreachable("Invalid ModRefInfo"); } +// Every bitfield has a unique name and one or more aliasing names that cover +// multiple bits. Names should be listed in order of preference, with higher +// popcounts listed first. +// +// Bits are consumed as printed. Each field should only be represented in one +// printed field. +static constexpr std::pair NoFPClassName[] = { + {fcAllFlags, "all"}, + {fcNan, "nan"}, + {fcSNan, "snan"}, + {fcQNan, "qnan"}, + {fcInf, "inf"}, + {fcNegInf, "ninf"}, + {fcPosInf, "pinf"}, + {fcZero, "zero"}, + {fcNegZero, "nzero"}, + {fcPosZero, "pzero"}, + {fcSubnormal, "sub"}, + {fcNegSubnormal, "nsub"}, + {fcPosSubnormal, "psub"}, + {fcNormal, "norm"}, + {fcNegNormal, "nnorm"}, + {fcPosNormal, "pnorm"} +}; + +static std::string getNoFPClassAttrAsString(unsigned Mask) { + std::string Result("nofpclass("); + raw_string_ostream OS(Result); + bool EmitSeparator = false; + + if (Mask == 0) { + OS << "none)"; + return Result; + } + + for (auto [BitTest, Name] : NoFPClassName) { + if ((Mask & BitTest) == BitTest) { + if (EmitSeparator) + OS << ' '; + OS << Name; + + // Clear the bits so we don't print any aliased names later. + Mask &= ~BitTest; + EmitSeparator = true; + } + } + + assert(Mask == 0 && "didn't print some mask bits"); + + OS << ')'; + return Result; +} + std::string Attribute::getAsString(bool InAttrGrp) const { if (!pImpl) return {}; @@ -543,6 +617,9 @@ return Result; } + if (hasAttribute(Attribute::NoFPClass)) + return getNoFPClassAttrAsString(getValueAsInt()); + // Convert target-dependent attributes to strings of the form: // // "kind" @@ -840,6 +917,10 @@ return SetNode ? SetNode->getMemoryEffects() : MemoryEffects::unknown(); } +FPClassTest AttributeSet::getNoFPClass() const { + return SetNode ? SetNode->getNoFPClass() : fcNone; +} + std::string AttributeSet::getAsString(bool InAttrGrp) const { return SetNode ? SetNode->getAsString(InAttrGrp) : ""; } @@ -1024,6 +1105,12 @@ return MemoryEffects::unknown(); } +FPClassTest AttributeSetNode::getNoFPClass() const { + if (auto A = findEnumAttribute(Attribute::NoFPClass)) + return A->getNoFPClass(); + return fcNone; +} + std::string AttributeSetNode::getAsString(bool InAttrGrp) const { std::string Str; for (iterator I = begin(), E = end(); I != E; ++I) { @@ -1560,6 +1647,14 @@ return getParamAttrs(Index).getDereferenceableOrNullBytes(); } +FPClassTest AttributeList::getRetNoFPClass() const { + return getRetAttrs().getNoFPClass(); +} + +FPClassTest AttributeList::getParamNoFPClass(unsigned Index) const { + return getParamAttrs(Index).getNoFPClass(); +} + UWTableKind AttributeList::getUWTableKind() const { return getFnAttrs().getUWTableKind(); } @@ -1803,6 +1898,10 @@ return addRawIntAttr(Attribute::Memory, ME.toIntValue()); } +AttrBuilder &AttrBuilder::addNoFPClassAttr(unsigned Mask) { + return addRawIntAttr(Attribute::NoFPClass, Mask); +} + AttrBuilder &AttrBuilder::addAllocKindAttr(AllocFnKind Kind) { return addRawIntAttr(Attribute::AllocKind, static_cast(Kind)); } @@ -1926,6 +2025,11 @@ Incompatible.addAttribute(Attribute::Alignment); } + if (ASK & ASK_SAFE_TO_DROP) { + if (!isNoFPClassCompatibleType(Ty)) + Incompatible.addAttribute(Attribute::NoFPClass); + } + // Some attributes can apply to all "values" but there are no `void` values. if (Ty->isVoidTy()) { if (ASK & ASK_SAFE_TO_DROP) Index: llvm/lib/IR/Function.cpp =================================================================== --- llvm/lib/IR/Function.cpp +++ llvm/lib/IR/Function.cpp @@ -229,6 +229,10 @@ return getParent()->getParamDereferenceableOrNullBytes(getArgNo()); } +FPClassTest Argument::getNoFPClass() const { + return getParent()->getParamNoFPClass(getArgNo()); +} + bool Argument::hasNestAttr() const { if (!getType()->isPointerTy()) return false; return hasAttribute(Attribute::Nest); Index: llvm/lib/IR/Verifier.cpp =================================================================== --- llvm/lib/IR/Verifier.cpp +++ llvm/lib/IR/Verifier.cpp @@ -1962,6 +1962,14 @@ } } } + + if (Attrs.hasAttribute(Attribute::NoFPClass)) { + uint64_t Val = Attrs.getAttribute(Attribute::NoFPClass).getValueAsInt(); + Check(Val != 0, "Attribute 'nofpclass' must have at least one test bit set", + V); + Check((Val & ~fcAllFlags) == 0, "Invalid value for 'nofpclass' test mask", + V); + } } void Verifier::checkUnsignedBaseTenFuncAttr(AttributeList Attrs, StringRef Attr, Index: llvm/lib/Transforms/Utils/CodeExtractor.cpp =================================================================== --- llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -918,6 +918,7 @@ case Attribute::AllocKind: case Attribute::PresplitCoroutine: case Attribute::Memory: + case Attribute::NoFPClass: continue; // Those attributes should be safe to propagate to the extracted function. case Attribute::AlwaysInline: Index: llvm/test/Assembler/nofpclass-invalid.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/nofpclass-invalid.ll @@ -0,0 +1,133 @@ +; RUN: rm -rf %t && split-file %s %t + +; RUN: not llvm-as %t/nofpclass_0.ll -o /dev/null 2>&1 | FileCheck -check-prefix=MASKVALUE0 %s +; RUN: not llvm-as %t/nofpclass_1024.ll -o /dev/null 2>&1 | FileCheck -check-prefix=MASKVALUE1024 %s +; RUN: not llvm-as %t/nofpclass_two_numbers.ll -o /dev/null 2>&1 | FileCheck -check-prefix=TWONUMBERS %s +; RUN: not llvm-as %t/nofpclass_two_numbers_bar.ll -o /dev/null 2>&1 | FileCheck -check-prefix=TWONUMBERSBAR %s +; RUN: not llvm-as %t/nofpclass_two_numbers_neg1.ll -o /dev/null 2>&1 | FileCheck -check-prefix=MASKVALUENEG1 %s +; RUN: not llvm-as %t/nofpclass_only_keyword.ll -o /dev/null 2>&1 | FileCheck -check-prefix=ONLYKEYWORD %s +; RUN: not llvm-as %t/nofpclass_openparen.ll -o /dev/null 2>&1 | FileCheck -check-prefix=OPENPAREN %s +; RUN: not llvm-as %t/nofpclass_closeparen.ll -o /dev/null 2>&1 | FileCheck -check-prefix=CLOSEPAREN %s +; RUN: not llvm-as %t/nofpclass_emptyparens.ll -o /dev/null 2>&1 | FileCheck -check-prefix=EMPTYPARENS %s +; RUN: not llvm-as %t/nofpclass_0_missingparen.ll -o /dev/null 2>&1 | FileCheck -check-prefix=MISSINGPAREN0 %s +; RUN: not llvm-as %t/nofpclass_0_noparens.ll -o /dev/null 2>&1 | FileCheck -check-prefix=NOPARENS0 %s +; RUN: not llvm-as %t/nofpclass_1024_missing_paren.ll -o /dev/null 2>&1 | FileCheck -check-prefix=MISSINGPAREN1024 %s +; RUN: not llvm-as %t/nofpclass_neg1_missing_paren.ll -o /dev/null 2>&1 | FileCheck -check-prefix=MISSINGPAREN-NEGONE %s +; RUN: not llvm-as %t/nofpclass_1_noparens.ll -o /dev/null 2>&1 | FileCheck -check-prefix=NOPARENS-ONE %s +; RUN: not llvm-as %t/nofpclass_nan_noparens.ll -o /dev/null 2>&1 | FileCheck -check-prefix=NOPARENS-NAN %s +; RUN: not llvm-as %t/nofpclass_nnan_noparens.ll -o /dev/null 2>&1 | FileCheck -check-prefix=NOPARENS-NNAN %s +; RUN: not llvm-as %t/nofpclass_name_plus_int.ll -o /dev/null 2>&1 | FileCheck -check-prefix=NAME-PLUS-INT %s +; RUN: not llvm-as %t/nofpclass_name_follows_int.ll -o /dev/null 2>&1 | FileCheck -check-prefix=NAME-FOLLOWS-INT %s + +;--- nofpclass_0.ll + +; MASKVALUE0: error: invalid mask value for 'nofpclass' +define void @nofpclass_0(float nofpclass(0) %x) { + ret void +} + +;--- nofpclass_1024.ll + +; MASKVALUE1024: error: invalid mask value for 'nofpclass' +define void @nofpclass_1024(float nofpclass(1024) %x) { + ret void +} + +;--- nofpclass_two_numbers.ll +; TWONUMBERS: error: expected ')' +define void @nofpclass_two_numbers(float nofpclass(2 4) %x) { + ret void +} + +;--- nofpclass_two_numbers_bar.ll +; TWONUMBERSBAR: error: expected ')' +define void @nofpclass_two_numbers_bar(float nofpclass(2|4) %x) { + ret void +} + +;--- nofpclass_two_numbers_neg1.ll +; MASKVALUENEG1: error: expected nofpclass test mask +define void @nofpclass_neg1(float nofpclass(-1) %x) { + ret void +} + +;--- nofpclass_only_keyword.ll +; ONLYKEYWORD: error: expected '(' +define void @nofpclass_only_keyword(float nofpclass %x) { + ret void +} + +; FIXME: Poor diagnostic +;--- nofpclass_openparen.ll +; OPENPAREN: error: expected nofpclass test mask +define void @nofpclass_openparen(float nofpclass( %x) { + ret void +} + +;--- nofpclass_closeparen.ll +; CLOSEPAREN: error: expected '(' +define void @nofpclass_closeparen(float nofpclass) %x) { + ret void +} + +;--- nofpclass_emptyparens.ll +; EMPTYPARENS: error: expected nofpclass test mask +define void @nofpclass_emptyparens(float nofpclass() %x) { + ret void +} + +; FIXME: Wrong error? +;--- nofpclass_0_missingparen.ll +; MISSINGPAREN0: error: invalid mask value for 'nofpclass' +define void @nofpclass_0_missingparen(float nofpclass(0 %x) { + ret void +} + +;--- nofpclass_0_noparens.ll +; NOPARENS0: error: expected '(' +define void @nofpclass_0_noparens(float nofpclass 0 %x) { + ret void +} + +; FIXME: Wrong error +;--- nofpclass_1024_missing_paren.ll +; MISSINGPAREN1024: error: invalid mask value for 'nofpclass' +define void @nofpclass_1024_missing_paren(float nofpclass(1024 %x) { + ret void +} + +;--- nofpclass_neg1_missing_paren.ll +; MISSINGPAREN-NEGONE: error: expected nofpclass test mask +define void @nofpclass_neg1_missing_paren(float nofpclass(-1 %x) { + ret void +} + +;--- nofpclass_1_noparens.ll +; NOPARENS-ONE: error: expected '(' +define void @nofpclass_1_noparens(float nofpclass 1 %x) { + ret void +} + +;--- nofpclass_nan_noparens.ll +; NOPARENS-NAN: error: expected '(' +define void @nofpclass_nan_noparens(float nofpclass nan %x) { + ret void +} + +;--- nofpclass_nnan_noparens.ll +; NOPARENS-NNAN: error: expected '(' +define void @nofpclass_nnan_noparens(float nofpclass nnan %x) { + ret void +} + +;--- nofpclass_name_plus_int.ll +; NAME-PLUS-INT: error: expected nofpclass test mask +define void @nofpclass_name_plus_int(float nofpclass(nan 42) %x) { + ret void +} + +;--- nofpclass_name_follows_int.ll +; NAME-FOLLOWS-INT: error: expected ')' +define void @nofpclass_name_plus_int(float nofpclass(42 nan) %x) { + ret void +} Index: llvm/test/Assembler/nofpclass.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/nofpclass.ll @@ -0,0 +1,436 @@ +; RUN: llvm-as < %s | llvm-dis | FileCheck %s + +; All fields with integer syntax +define void @nofpclass_1023(float nofpclass(1023) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_1023 +; CHECK-SAME: (float nofpclass(all) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; -------------------------------------------------------------------- +; Single field, integer syntax +; -------------------------------------------------------------------- + +define void @nofpclass_1(float nofpclass(1) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_1 +; CHECK-SAME: (float nofpclass(snan) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_2(float nofpclass(2) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_2 +; CHECK-SAME: (float nofpclass(qnan) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_3(float nofpclass(4) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_3 +; CHECK-SAME: (float nofpclass(ninf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_8(float nofpclass(8) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_8 +; CHECK-SAME: (float nofpclass(nnorm) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_16(float nofpclass(16) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_16 +; CHECK-SAME: (float nofpclass(nsub) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_32(float nofpclass(32) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_32 +; CHECK-SAME: (float nofpclass(nzero) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_64(float nofpclass(64) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_64 +; CHECK-SAME: (float nofpclass(pzero) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_128(float nofpclass(128) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_128 +; CHECK-SAME: (float nofpclass(psub) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_256(float nofpclass(256) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_256 +; CHECK-SAME: (float nofpclass(pnorm) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_512(float nofpclass(512) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_512 +; CHECK-SAME: (float nofpclass(pinf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_8_extra_space(float nofpclass( 8 ) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_8_extra_space +; CHECK-SAME: (float nofpclass(nnorm) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; -------------------------------------------------------------------- +; Canonical single field names +; -------------------------------------------------------------------- + +define void @nofpclass_snan(float nofpclass(snan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_snan +; CHECK-SAME: (float nofpclass(snan) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_qnan(float nofpclass(qnan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_qnan +; CHECK-SAME: (float nofpclass(qnan) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_ninf(float nofpclass(ninf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_ninf +; CHECK-SAME: (float nofpclass(ninf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_nnorm(float nofpclass(nnorm) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nnorm +; CHECK-SAME: (float nofpclass(nnorm) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_nsub(float nofpclass(nsub) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nsub +; CHECK-SAME: (float nofpclass(nsub) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_nzero(float nofpclass(nzero) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nzero +; CHECK-SAME: (float nofpclass(nzero) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_pzero(float nofpclass(pzero) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_pzero +; CHECK-SAME: (float nofpclass(pzero) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_psub(float nofpclass(psub) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_psub +; CHECK-SAME: (float nofpclass(psub) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_pnorm(float nofpclass(pnorm) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_pnorm +; CHECK-SAME: (float nofpclass(pnorm) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_pinf(float nofpclass(pinf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_pinf +; CHECK-SAME: (float nofpclass(pinf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; -------------------------------------------------------------------- +; Pretty printed pairs +; -------------------------------------------------------------------- + +define void @nofpclass_nan(float nofpclass(nan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan +; CHECK-SAME: (float nofpclass(nan) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_inf(float nofpclass(inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_inf +; CHECK-SAME: (float nofpclass(inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_norm(float nofpclass(norm) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_norm +; CHECK-SAME: (float nofpclass(norm) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_sub(float nofpclass(sub) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_sub +; CHECK-SAME: (float nofpclass(sub) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_zero(float nofpclass(zero) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_zero +; CHECK-SAME: (float nofpclass(zero) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; -------------------------------------------------------------------- +; Special helper names +; -------------------------------------------------------------------- + +define void @nofpclass_all(float nofpclass(all) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_all +; CHECK-SAME: (float nofpclass(all) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; -------------------------------------------------------------------- +; Return position +; -------------------------------------------------------------------- + +define nofpclass(nan) float @return_nan(float %arg) { +; CHECK-LABEL: define {{[^@]+}}@return_nan +; CHECK-SAME: (float [[ARG:%.*]]) { +; CHECK-NEXT: ret float [[ARG]] +; + ret float %arg +} + +; -------------------------------------------------------------------- +; Callsite positions +; -------------------------------------------------------------------- + +declare float @func(float) + +define float @callsite_nofpclass_arg(float %arg) { +; CHECK-LABEL: define {{[^@]+}}@callsite_nofpclass_arg +; CHECK-SAME: (float [[ARG:%.*]]) { +; CHECK-NEXT: [[CALL:%.*]] = call float @func(float nofpclass(nan) [[ARG]]) +; CHECK-NEXT: ret float [[CALL]] +; + %call = call float @func(float nofpclass(nan) %arg) + ret float %call +} + +define float @callsite_nofpclass_return(float %arg) { +; CHECK-LABEL: define {{[^@]+}}@callsite_nofpclass_return +; CHECK-SAME: (float [[ARG:%.*]]) { +; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) float @func(float [[ARG]]) +; CHECK-NEXT: ret float [[CALL]] +; + %call = call nofpclass(nan) float @func(float %arg) + ret float %call +} + +; -------------------------------------------------------------------- +; Declaration +; -------------------------------------------------------------------- + +declare nofpclass(inf) float @declaration(float nofpclass(zero)) + +; -------------------------------------------------------------------- +; Combinations of named values +; -------------------------------------------------------------------- + +define void @nofpclass_nan_inf(float nofpclass(nan inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_inf +; CHECK-SAME: (float nofpclass(nan inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_inf_nan(float nofpclass(inf nan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_inf_nan +; CHECK-SAME: (float nofpclass(nan inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_nan_qnan_snan(float nofpclass(nan qnan snan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_qnan_snan +; CHECK-SAME: (float nofpclass(nan) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_snan_qnan_nan(float nofpclass(snan qnan nan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_snan_qnan_nan +; CHECK-SAME: (float nofpclass(nan) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_all_pairs_named(float nofpclass(nan inf norm sub zero) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_all_pairs_named +; CHECK-SAME: (float nofpclass(all) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_all_pairs_named_reverse(float nofpclass(zero sub norm inf nan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_all_pairs_named_reverse +; CHECK-SAME: (float nofpclass(all) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_all_pairs_named_shuffle0(float nofpclass(sub nan norm zero inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_all_pairs_named_shuffle0 +; CHECK-SAME: (float nofpclass(all) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_all_fields_named(float nofpclass(snan qnan ninf pinf nnorm pnorm nsub psub nzero pzero) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_all_fields_named +; CHECK-SAME: (float nofpclass(all) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_all_fields_named_reverse(float nofpclass(pzero nzero psub nsub pnorm nnorm pinf ninf qnan snan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_all_fields_named_reverse +; CHECK-SAME: (float nofpclass(all) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_snan_ninf(float nofpclass(snan ninf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_snan_ninf +; CHECK-SAME: (float nofpclass(snan ninf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +define void @nofpclass_ninf_snan(float nofpclass(ninf snan) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_ninf_snan +; CHECK-SAME: (float nofpclass(snan ninf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; -------------------------------------------------------------------- +; Supported IR types +; -------------------------------------------------------------------- + +; Vector FP +define void @nofpclass_nan_inf_v2f16(<2 x half> nofpclass(nan inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_inf_v2f16 +; CHECK-SAME: (<2 x half> nofpclass(nan inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; Scalable Vector FP +define void @nofpclass_nan_inf_scalable_v2f16( nofpclass(nan inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_inf_scalable_v2f16 +; CHECK-SAME: ( nofpclass(nan inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; Array of scalar FP +define void @nofpclass_nan_inf_a4f64([4 x double] nofpclass(nan inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_inf_a4f64 +; CHECK-SAME: ([4 x double] nofpclass(nan inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; Array of vector FP +define void @nofpclass_nan_inf_a4v2f16([4 x <2 x half>] nofpclass(nan inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_inf_a4v2f16 +; CHECK-SAME: ([4 x <2 x half>] nofpclass(nan inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; Array of array of scalar FP +define void @nofpclass_nan_inf_a8a4f32([8 x [4 x float]] nofpclass(nan inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_inf_a8a4f32 +; CHECK-SAME: ([8 x [4 x float]] nofpclass(nan inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} + +; Array of array of vector FP +define void @nofpclass_nan_inf_a8a4v2f32([8 x [4 x <2 x float>]] nofpclass(nan inf) %x) { +; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_inf_a8a4v2f32 +; CHECK-SAME: ([8 x [4 x <2 x float>]] nofpclass(nan inf) [[X:%.*]]) { +; CHECK-NEXT: ret void +; + ret void +} Index: llvm/test/Bitcode/compatibility.ll =================================================================== --- llvm/test/Bitcode/compatibility.ll +++ llvm/test/Bitcode/compatibility.ll @@ -1980,6 +1980,77 @@ declare void @f.allockind() allockind("alloc,uninitialized") ; CHECK: declare void @f.allockind() #50 + +; CHECK: declare nofpclass(snan) float @nofpclass_snan(float nofpclass(snan)) +declare nofpclass(snan) float @nofpclass_snan(float nofpclass(snan)) + +; CHECK: declare nofpclass(qnan) float @nofpclass_qnan(float nofpclass(qnan)) +declare nofpclass(qnan) float @nofpclass_qnan(float nofpclass(qnan)) + +; CHECK: declare nofpclass(ninf) float @nofpclass_ninf(float nofpclass(ninf)) +declare nofpclass(ninf) float @nofpclass_ninf(float nofpclass(ninf)) + +; CHECK: declare nofpclass(nnorm) float @nofpclass_nnorm(float nofpclass(nnorm)) +declare nofpclass(nnorm) float @nofpclass_nnorm(float nofpclass(nnorm)) + +; CHECK: declare nofpclass(nsub) float @nofpclass_nsub(float nofpclass(nsub)) +declare nofpclass(nsub) float @nofpclass_nsub(float nofpclass(nsub)) + +; CHECK: declare nofpclass(nzero) float @nofpclass_nzero(float nofpclass(nzero)) +declare nofpclass(nzero) float @nofpclass_nzero(float nofpclass(nzero)) + +; CHECK: declare nofpclass(pzero) float @nofpclass_pzero(float nofpclass(pzero)) +declare nofpclass(pzero) float @nofpclass_pzero(float nofpclass(pzero)) + +; CHECK: declare nofpclass(psub) float @nofpclass_psub(float nofpclass(psub)) +declare nofpclass(psub) float @nofpclass_psub(float nofpclass(psub)) + +; CHECK: declare nofpclass(pnorm) float @nofpclass_pnorm(float nofpclass(pnorm)) +declare nofpclass(pnorm) float @nofpclass_pnorm(float nofpclass(pnorm)) + +; CHECK: declare nofpclass(pinf) float @nofpclass_pinf(float nofpclass(pinf)) +declare nofpclass(pinf) float @nofpclass_pinf(float nofpclass(pinf)) + +; CHECK: declare nofpclass(nan) float @nofpclass_nan(float nofpclass(nan)) +declare nofpclass(nan) float @nofpclass_nan(float nofpclass(nan)) + +; CHECK: declare nofpclass(inf) float @nofpclass_inf(float nofpclass(inf)) +declare nofpclass(inf) float @nofpclass_inf(float nofpclass(inf)) + +; CHECK: declare nofpclass(norm) float @nofpclass_norm(float nofpclass(norm)) +declare nofpclass(norm) float @nofpclass_norm(float nofpclass(norm)) + +; CHECK: declare nofpclass(zero) float @nofpclass_zero(float nofpclass(zero)) +declare nofpclass(zero) float @nofpclass_zero(float nofpclass(zero)) + +; CHECK: declare nofpclass(sub) float @nofpclass_sub(float nofpclass(sub)) +declare nofpclass(sub) float @nofpclass_sub(float nofpclass(sub)) + +; CHECK: declare nofpclass(all) float @nofpclass_all(float nofpclass(all)) +declare nofpclass(all) float @nofpclass_all(float nofpclass(all)) + +; CHECK: declare nofpclass(zero sub) float @nofpclass_sub_zero(float nofpclass(zero sub)) +declare nofpclass(sub zero) float @nofpclass_sub_zero(float nofpclass(sub zero)) + +; CHECK: declare nofpclass(inf sub) float @nofpclass_sub_inf(float nofpclass(inf sub)) +declare nofpclass(sub inf) float @nofpclass_sub_inf(float nofpclass(sub inf)) + +declare float @unknown_fpclass_func(float) + +define float @nofpclass_callsites(float %arg) { + ; CHECK: %call0 = call nofpclass(nan) float @unknown_fpclass_func(float nofpclass(ninf) %arg) + %call0 = call nofpclass(nan) float @unknown_fpclass_func(float nofpclass(ninf) %arg) + + ; CHECK: %call1 = call nofpclass(inf) float @unknown_fpclass_func(float nofpclass(inf) %arg) + %call1 = call nofpclass(inf) float @unknown_fpclass_func(float nofpclass(inf) %arg) + + ; CHECK: %call2 = call nofpclass(zero) float @unknown_fpclass_func(float nofpclass(norm) %arg) + %call2 = call nofpclass(zero) float @unknown_fpclass_func(float nofpclass(norm) %arg) + %add0 = fadd float %call0, %call1 + %add1 = fadd float %add0, %call2 + ret float %add1 +} + ; CHECK: attributes #0 = { alignstack=4 } ; CHECK: attributes #1 = { alignstack=8 } ; CHECK: attributes #2 = { alwaysinline } Index: llvm/test/Transforms/InstSimplify/known-never-nan.ll =================================================================== --- llvm/test/Transforms/InstSimplify/known-never-nan.ll +++ llvm/test/Transforms/InstSimplify/known-never-nan.ll @@ -447,3 +447,111 @@ declare double @llvm.round.f64(double) declare double @llvm.roundeven.f64(double) declare double @llvm.arithmetic.fence.f64(double) + + +define i1 @isKnownNeverNaN_nofpclass_nan_arg(double nofpclass(nan) %arg) { +; CHECK-LABEL: @isKnownNeverNaN_nofpclass_nan_arg( +; CHECK-NEXT: ret i1 true +; + %tmp = fcmp ord double %arg, %arg + ret i1 %tmp +} + +; Not enough nan tested +define i1 @isKnownNeverNaN_nofpclass_qnan_arg(double nofpclass(qnan) %arg) { +; CHECK-LABEL: @isKnownNeverNaN_nofpclass_qnan_arg( +; CHECK-NEXT: [[TMP:%.*]] = fcmp ord double [[ARG:%.*]], [[ARG]] +; CHECK-NEXT: ret i1 [[TMP]] +; + %tmp = fcmp ord double %arg, %arg + ret i1 %tmp +} + +; Not enough nan tested +define i1 @isKnownNeverNaN_nofpclass_snan_arg(double nofpclass(snan) %arg) { +; CHECK-LABEL: @isKnownNeverNaN_nofpclass_snan_arg( +; CHECK-NEXT: [[TMP:%.*]] = fcmp ord double [[ARG:%.*]], [[ARG]] +; CHECK-NEXT: ret i1 [[TMP]] +; + %tmp = fcmp ord double %arg, %arg + ret i1 %tmp +} + +; Wrong test +define i1 @isKnownNeverNaN_nofpclass_zero_arg(double nofpclass(zero) %arg) { +; CHECK-LABEL: @isKnownNeverNaN_nofpclass_zero_arg( +; CHECK-NEXT: [[TMP:%.*]] = fcmp ord double [[ARG:%.*]], [[ARG]] +; CHECK-NEXT: ret i1 [[TMP]] +; + %tmp = fcmp ord double %arg, %arg + ret i1 %tmp +} + +declare nofpclass(nan) double @declare_no_nan_return() +declare double @unknown_return() + +define i1 @isKnownNeverNaN_nofpclass_call_decl() { +; CHECK-LABEL: @isKnownNeverNaN_nofpclass_call_decl( +; CHECK-NEXT: [[CALL:%.*]] = call double @declare_no_nan_return() +; CHECK-NEXT: ret i1 true +; + %call = call double @declare_no_nan_return() + %tmp = fcmp ord double %call, %call + ret i1 %tmp +} + +define i1 @isKnownNeverNaN_nofpclass_callsite() { +; CHECK-LABEL: @isKnownNeverNaN_nofpclass_callsite( +; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) double @unknown_return() +; CHECK-NEXT: ret i1 true +; + %call = call nofpclass(nan) double @unknown_return() + %tmp = fcmp ord double %call, %call + ret i1 %tmp +} + +declare nofpclass(sub norm zero inf) double @only_nans() + +; TODO: Could simplify to false +define i1 @isKnownNeverNaN_only_nans() { +; CHECK-LABEL: @isKnownNeverNaN_only_nans( +; CHECK-NEXT: [[CALL:%.*]] = call double @only_nans() +; CHECK-NEXT: [[TMP:%.*]] = fcmp ord double [[CALL]], [[CALL]] +; CHECK-NEXT: ret i1 [[TMP]] +; + %call = call double @only_nans() + %tmp = fcmp ord double %call, %call + ret i1 %tmp +} + +define i1 @isKnownNeverNaN_nofpclass_indirect_callsite(ptr %fptr) { +; CHECK-LABEL: @isKnownNeverNaN_nofpclass_indirect_callsite( +; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) double [[FPTR:%.*]]() +; CHECK-NEXT: ret i1 true +; + %call = call nofpclass(nan) double %fptr() + %tmp = fcmp ord double %call, %call + ret i1 %tmp +} + +define i1 @isKnownNeverNaN_invoke_callsite(ptr %ptr) personality i8 1 { +; CHECK-LABEL: @isKnownNeverNaN_invoke_callsite( +; CHECK-NEXT: [[INVOKE:%.*]] = invoke nofpclass(nan) float [[PTR:%.*]]() +; CHECK-NEXT: to label [[NORMAL:%.*]] unwind label [[UNWIND:%.*]] +; CHECK: normal: +; CHECK-NEXT: ret i1 true +; CHECK: unwind: +; CHECK-NEXT: [[TMP1:%.*]] = landingpad ptr +; CHECK-NEXT: cleanup +; CHECK-NEXT: resume ptr null +; + %invoke = invoke nofpclass(nan) float %ptr() to label %normal unwind label %unwind + +normal: + %ord = fcmp ord float %invoke, 0.0 + ret i1 %ord + +unwind: + landingpad ptr cleanup + resume ptr null +} Index: llvm/test/Transforms/Util/nofpclass.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Util/nofpclass.ll @@ -0,0 +1,52 @@ +; RUN: opt -S -passes=pgo-icall-prom -icp-total-percent-threshold=0 < %s 2>&1 | FileCheck %s + +; Test that CallPromotionUtils will promote calls which require pointer cast +; safely, i.e. drop incompatible attributes. + +@foo = common global ptr null, align 8 + +; correct type, preserve attributes +define double @func_double(double %a) { + ret double poison +} + +; drop nofpclass attributes +define i64 @func_i64(i64 %a) { + ret i64 poison +} + +define double @cast_scalar_fp(double %arg) { + %tmp = load ptr, ptr @foo, align 8 + +; Make sure callsite attributes are dropped on arguments and retval. +; CHECK: [[ARG:%[0-9]+]] = bitcast double %arg to i64 +; CHECK-NEXT: call i64 @func_i64(i64 [[ARG]]) + +; Make sure callsite attributes are preserved on arguments and retval. +; CHECK: call nofpclass(inf) double @func_double(double nofpclass(nan) + +; CHECK: call nofpclass(inf) double %tmp(double nofpclass(nan) %arg) + %call = call nofpclass(inf) double %tmp(double nofpclass(nan) %arg), !prof !0 + ret double %call +} + +; ; correct type, preserve attributes +define [2 x [2 x <2 x double>]] @func_array_vector_f64([2 x [2 x <2 x double>]] %a) { + ret [2 x [2 x <2 x double>]] poison +} + +; drop nofpclass attributes +define [2 x [2 x <2 x i64>]] @func_array_vector_i64([2 x [2 x <2 x i64>]] %a) { + ret [2 x [2 x <2 x i64>]] poison +} + +; FIXME: This is not promoted +; CHECK: %call = call nofpclass(inf) [2 x [2 x <2 x double>]] %tmp([2 x [2 x <2 x double>]] nofpclass(nan) %arg) +define [2 x [2 x <2 x double>]] @cast_array_vector([2 x [2 x <2 x double>]] %arg) { + %tmp = load ptr, ptr @foo, align 8 + %call = call nofpclass(inf) [2 x [2 x <2 x double>]] %tmp([2 x [2 x <2 x double>]] nofpclass(nan) %arg), !prof !1 + ret [2 x [2 x <2 x double>]] %call +} + +!0 = !{!"VP", i32 0, i64 1440, i64 15573779287943805696, i64 1030, i64 16900752280434761561, i64 410} +!1 = !{!"VP", i32 0, i64 1440, i64 1124945363680759394, i64 1030, i64 16341336592352938424, i64 410} Index: llvm/test/Verifier/nofpclass.ll =================================================================== --- /dev/null +++ llvm/test/Verifier/nofpclass.ll @@ -0,0 +1,66 @@ +; RUN: not llvm-as -disable-output %s 2>&1 | FileCheck %s + +; CHECK: 'nofpclass(nan)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_int_return +define nofpclass(nan) i32 @nofpclass_int_return(i32 %arg) { + ret i32 %arg +} + +; CHECK: 'nofpclass(nan)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_int_param +define i32 @nofpclass_int_param(i32 nofpclass(nan) %arg) { + ret i32 %arg +} + +; CHECK: 'nofpclass(zero)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_int_ret_decl +declare nofpclass(zero) i32 @nofpclass_int_ret_decl() + +; CHECK: 'nofpclass(inf)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_int_arg_decl +declare i32 @nofpclass_int_arg_decl(i32 nofpclass(inf)) + + +; CHECK: 'nofpclass(nan)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_vector_int +; CHECK-NEXT: 'nofpclass(zero)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_vector_int +define nofpclass(nan) <4 x i32> @nofpclass_vector_int(<4 x i32> nofpclass(zero) %arg) { + ret <4 x i32> %arg +} + +; CHECK: 'nofpclass(nan)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_array_int +; CHECK-NEXT: 'nofpclass(zero)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_array_int +define nofpclass(nan) [4 x i32] @nofpclass_array_int([4 x i32] nofpclass(zero) %arg) { + ret [4 x i32] %arg +} + +; CHECK: 'nofpclass(nan)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_vector_array_int +; CHECK-NEXT: 'nofpclass(zero)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_vector_array_int +define nofpclass(nan) [4 x <8 x i32>] @nofpclass_vector_array_int([4 x <8 x i32>] nofpclass(zero) %arg) { + ret [4 x <8 x i32>] %arg +} + +%opaque = type opaque + +; CHECK: 'nofpclass(nan)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_opaque_type +; CHECK-NEXT: 'nofpclass(zero)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_opaque_type +define nofpclass(nan) %opaque @nofpclass_opaque_type(%opaque nofpclass(zero) %arg) { + ret %opaque %arg +} + +%struct = type { i32, float } + +; CHECK: 'nofpclass(nan)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_struct +; CHECK-NEXT: 'nofpclass(zero)' applied to incompatible type! +; CHECK-NEXT: ptr @nofpclass_struct +define nofpclass(nan) %struct @nofpclass_struct(%struct nofpclass(zero) %arg) { + ret %struct %arg +} Index: llvm/unittests/IR/VerifierTest.cpp =================================================================== --- llvm/unittests/IR/VerifierTest.cpp +++ llvm/unittests/IR/VerifierTest.cpp @@ -109,6 +109,39 @@ "Attribute 'uwtable' does not apply to function return values")); } +/// Test the verifier rejects invalid nofpclass values that the assembler may +/// also choose to reject. +TEST(VerifierTest, InvalidNoFPClassAttribute) { + LLVMContext C; + + const unsigned InvalidMasks[] = {0, fcAllFlags + 1}; + + for (unsigned InvalidMask : InvalidMasks) { + Module M("M", C); + FunctionType *FTy = + FunctionType::get(Type::getFloatTy(C), /*isVarArg=*/false); + Function *F = Function::Create(FTy, Function::ExternalLinkage, "foo", M); + AttributeList AS = F->getAttributes(); + F->setAttributes( + AS.addRetAttribute(C, Attribute::getWithNoFPClass(C, InvalidMask))); + + std::string Error; + raw_string_ostream ErrorOS(Error); + EXPECT_TRUE(verifyModule(M, &ErrorOS)); + + StringRef ErrMsg(ErrorOS.str()); + + if (InvalidMask == 0) { + EXPECT_TRUE(ErrMsg.startswith( + "Attribute 'nofpclass' must have at least one test bit set")) + << ErrMsg; + } else { + EXPECT_TRUE(ErrMsg.startswith("Invalid value for 'nofpclass' test mask")) + << ErrMsg; + } + } +} + TEST(VerifierTest, CrossModuleRef) { LLVMContext C; Module M1("M1", C);