[llvm] 5da6744 - IR: Add nofpclass parameter attribute

Matt Arsenault via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 24 03:41:37 PST 2023


Author: Matt Arsenault
Date: 2023-02-24T07:41:29-04:00
New Revision: 5da674492a5acf8e08a58f611e39ff4cd6a16dfe

URL: https://github.com/llvm/llvm-project/commit/5da674492a5acf8e08a58f611e39ff4cd6a16dfe
DIFF: https://github.com/llvm/llvm-project/commit/5da674492a5acf8e08a58f611e39ff4cd6a16dfe.diff

LOG: IR: Add nofpclass parameter attribute

This carries a bitmask indicating forbidden floating-point value kinds
in the argument or return value. This will enable interprocedural
-ffinite-math-only optimizations. This is primarily to cover the
no-nans and no-infinities cases, but also covers the other floating
point classes for free. Textually, this provides a number of names
corresponding to bits in FPClassTest, e.g.

  call nofpclass(nan inf) @must_be_finite()
  call nofpclass(snan) @cannot_be_snan()

This is more expressive than the existing nnan and ninf fast math
flags. As an added bonus, you can represent fun things like nanf:

  declare nofpclass(inf zero sub norm) float @only_nans()

Compared to nnan/ninf:
  - Can be applied to individual call operands as well as the return value
  - Can distinguish signaling and quiet nans
  - Distinguishes the sign of infinities
  - Can be safely propagated since it doesn't imply anything about
    other operands.
  - Does not apply to FP instructions; it's not a flag

This is one step closer to being able to retire "no-nans-fp-math" and
"no-infs-fp-math". The one remaining situation where we have no way to
represent no-nans/infs is for loads (if we wanted to solve this we
could introduce !nofpclass metadata, following along with
noundef/!noundef).

This is to help simplify the GPU builtin math library
distribution. Currently the library code has explicit finite math only
checks, read from global constants the compiler driver needs to set
based on the compiler flags during linking. We end up having to
internalize the library into each translation unit in case different
linked modules have different math flags. By propagating known-not-nan
and known-not-infinity information, we can automatically prune the
edge case handling in most functions if the function is only reached
from fast math uses.

Added: 
    llvm/test/Assembler/nofpclass-invalid.ll
    llvm/test/Assembler/nofpclass.ll
    llvm/test/Transforms/Util/nofpclass.ll
    llvm/test/Verifier/nofpclass.ll

Modified: 
    llvm/docs/LangRef.rst
    llvm/docs/ReleaseNotes.rst
    llvm/include/llvm/AsmParser/LLParser.h
    llvm/include/llvm/AsmParser/LLToken.h
    llvm/include/llvm/Bitcode/LLVMBitCodes.h
    llvm/include/llvm/IR/Argument.h
    llvm/include/llvm/IR/Attributes.h
    llvm/include/llvm/IR/Attributes.td
    llvm/include/llvm/IR/Function.h
    llvm/include/llvm/IR/InstrTypes.h
    llvm/lib/Analysis/ValueTracking.cpp
    llvm/lib/AsmParser/LLLexer.cpp
    llvm/lib/AsmParser/LLParser.cpp
    llvm/lib/Bitcode/Reader/BitcodeReader.cpp
    llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
    llvm/lib/IR/AttributeImpl.h
    llvm/lib/IR/Attributes.cpp
    llvm/lib/IR/Function.cpp
    llvm/lib/IR/Instructions.cpp
    llvm/lib/IR/Verifier.cpp
    llvm/lib/Transforms/Utils/CodeExtractor.cpp
    llvm/test/Bitcode/compatibility.ll
    llvm/test/Transforms/InstSimplify/known-never-nan.ll
    llvm/unittests/IR/VerifierTest.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 2ba5fba140abc..f420896bf2700 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1396,6 +1396,79 @@ Currently, only the following parameter attributes are defined:
     undefined. Note that this does not refer to padding introduced by the
     type's storage representation.
 
+.. _nofpclass:
+
+``nofpclass(<test mask>)``
+    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 <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 <poisonvalues>` 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\" <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(<n>)``
     This indicates the alignment that should be considered by the backend when
     assigning this parameter to a stack slot during calling convention
@@ -2147,6 +2220,8 @@ example:
     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
@@ -24984,6 +25059,8 @@ Floating-Point Test Intrinsics
 These functions get properties of floating-point values.
 
 
+.. _llvm.is.fpclass:
+
 '``llvm.is.fpclass``' Intrinsic
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

diff  --git a/llvm/docs/ReleaseNotes.rst b/llvm/docs/ReleaseNotes.rst
index 5c8d3fcb4026f..e20a59bcbe121 100644
--- a/llvm/docs/ReleaseNotes.rst
+++ b/llvm/docs/ReleaseNotes.rst
@@ -53,6 +53,9 @@ Changes to the LLVM IR
 * Typed pointers are no longer supported. See the `opaque pointers
   <OpaquePointers.html>`__ documentation for migration instructions.
 
+* The ``nofpclass`` attribute was introduced. This allows more
+  optimizations around special floating point value comparisons.
+
 Changes to building LLVM
 ------------------------
 

diff  --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h
index b07e9fc9cc755..63cb61ae3a19d 100644
--- a/llvm/include/llvm/AsmParser/LLParser.h
+++ b/llvm/include/llvm/AsmParser/LLParser.h
@@ -294,6 +294,7 @@ namespace llvm {
     bool parseOptionalUWTableKind(UWTableKind &Kind);
     bool parseAllocKind(AllocFnKind &Kind);
     std::optional<MemoryEffects> parseMemoryAttr();
+    unsigned parseNoFPClassAttr();
     bool parseScopeAndOrdering(bool IsAtomic, SyncScope::ID &SSID,
                                AtomicOrdering &Ordering);
     bool parseScope(SyncScope::ID &SSID);

diff  --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h
index 60e25cefbd242..4f841c5f55b12 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -195,6 +195,24 @@ enum Kind {
   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,
 

diff  --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 62dc01877b274..52e76356a892e 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -712,6 +712,7 @@ enum AttributeKindCodes {
   ATTR_KIND_FNRETTHUNK_EXTERN = 84,
   ATTR_KIND_SKIP_PROFILE = 85,
   ATTR_KIND_MEMORY = 86,
+  ATTR_KIND_NOFPCLASS = 87,
 };
 
 enum ComdatSelectionKindCodes {

diff  --git a/llvm/include/llvm/IR/Argument.h b/llvm/include/llvm/IR/Argument.h
index dba1dbc26ae39..f0c0ce75d2b7e 100644
--- a/llvm/include/llvm/IR/Argument.h
+++ b/llvm/include/llvm/IR/Argument.h
@@ -63,6 +63,10 @@ class Argument final : public Value {
   /// 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;
 

diff  --git a/llvm/include/llvm/IR/Attributes.h b/llvm/include/llvm/IR/Attributes.h
index c4e12a673ed23..a77c4381b0fcf 100644
--- a/llvm/include/llvm/IR/Attributes.h
+++ b/llvm/include/llvm/IR/Attributes.h
@@ -45,6 +45,7 @@ class LLVMContext;
 class MemoryEffects;
 class Type;
 class raw_ostream;
+enum FPClassTest : unsigned;
 
 enum class AllocFnKind : uint64_t {
   Unknown = 0,
@@ -148,6 +149,7 @@ class Attribute {
   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, FPClassTest Mask);
 
   /// For a typed attribute, return the equivalent attribute with the type
   /// changed to \p ReplacementTy.
@@ -249,6 +251,9 @@ class Attribute {
   /// 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 @@ class AttributeSet {
   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 @@ class AttributeList {
   /// 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 ArgNo) const;
+
   /// Get the unwind table kind requested for the function.
   UWTableKind getUWTableKind() const;
 
@@ -1236,6 +1248,9 @@ class AttrBuilder {
   /// Add memory effect attribute.
   AttrBuilder &addMemoryAttr(MemoryEffects ME);
 
+  // Add nofpclass attribute
+  AttrBuilder &addNoFPClassAttr(FPClassTest NoFPClassMask);
+
   ArrayRef<Attribute> attrs() const { return Attrs; }
 
   bool operator==(const AttrBuilder &B) const;

diff  --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 75fe534ac61ea..e72054cfd475e 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -118,6 +118,9 @@ def JumpTable : EnumAttr<"jumptable", [FnAttr]>;
 /// 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]>;
 

diff  --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h
index aee0a9dd4fec8..86bdc241a5ffe 100644
--- a/llvm/include/llvm/IR/Function.h
+++ b/llvm/include/llvm/IR/Function.h
@@ -483,6 +483,11 @@ class LLVM_EXTERNAL_VISIBILITY Function : public GlobalObject,
     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);

diff  --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h
index 49b034c00143e..6c74842bf4886 100644
--- a/llvm/include/llvm/IR/InstrTypes.h
+++ b/llvm/include/llvm/IR/InstrTypes.h
@@ -1833,6 +1833,14 @@ class CallBase : public Instruction {
     return Attrs.getParamDereferenceableOrNullBytes(i);
   }
 
+  /// Extract a test mask for disallowed floating-point value classes for the
+  /// return value.
+  FPClassTest getRetNoFPClass() const;
+
+  /// Extract a test mask for disallowed floating-point value classes for the
+  /// parameter.
+  FPClassTest getParamNoFPClass(unsigned i) const;
+
   /// 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).

diff  --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 94ec3517bc0a2..1ad875bec6e77 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -3873,6 +3873,18 @@ bool llvm::isKnownNeverInfinity(const Value *V, const TargetLibraryInfo *TLI,
     if (FPMathOp->hasNoInfs())
       return true;
 
+  if (const auto *Arg = dyn_cast<Argument>(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<CallBase>(V)) {
+    if ((CB->getRetNoFPClass() & fcInf) == fcInf)
+      return true;
+  }
+
   // Handle scalar constants.
   if (auto *CFP = dyn_cast<ConstantFP>(V))
     return !CFP->isInfinity();
@@ -4001,6 +4013,19 @@ bool llvm::isKnownNeverNaN(const Value *V, const TargetLibraryInfo *TLI,
     if (FPMathOp->hasNoNaNs())
       return true;
 
+  if (const auto *Arg = dyn_cast<Argument>(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<CallBase>(V)) {
+    FPClassTest Mask = CB->getRetNoFPClass();
+    if ((Mask & fcNan) == fcNan)
+      return true;
+  }
+
   // Handle scalar constants.
   if (auto *CFP = dyn_cast<ConstantFP>(V))
     return !CFP->isNaN();

diff  --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index a9cac4de0c2fb..f24c4101e8374 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -653,6 +653,24 @@ lltok::Kind LLLexer::LexIdentifier() {
   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);
 

diff  --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 580fc3e7e90b4..cb5c82bb53657 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -1469,6 +1469,15 @@ bool LLParser::parseEnumAttribute(Attribute::AttrKind Attr, AttrBuilder &B,
     B.addMemoryAttr(*ME);
     return false;
   }
+  case Attribute::NoFPClass: {
+    if (FPClassTest NoFPClass =
+            static_cast<FPClassTest>(parseNoFPClassAttr())) {
+      B.addNoFPClassAttr(NoFPClass);
+      return false;
+    }
+
+    return true;
+  }
   default:
     B.addAttribute(Attr);
     Lex.Lex();
@@ -2340,6 +2349,86 @@ std::optional<MemoryEffects> LLParser::parseMemoryAttr() {
   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

diff  --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index 35607df04495b..2c1f195119bcc 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -1928,6 +1928,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
     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,9 @@ Error BitcodeReader::parseAttributeGroupBlock() {
             B.addAllocKindAttr(static_cast<AllocFnKind>(Record[++i]));
           else if (Kind == Attribute::Memory)
             B.addMemoryAttr(MemoryEffects::createFromIntValue(Record[++i]));
+          else if (Kind == Attribute::NoFPClass)
+            B.addNoFPClassAttr(
+                static_cast<FPClassTest>(Record[++i] & fcAllFlags));
         } else if (Record[i] == 3 || Record[i] == 4) { // String attribute
           bool HasValue = (Record[i++] == 4);
           SmallString<64> KindStr;

diff  --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 89996b4d53039..16d13993111c8 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -681,6 +681,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
     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:

diff  --git a/llvm/lib/IR/AttributeImpl.h b/llvm/lib/IR/AttributeImpl.h
index 071c75e693771..78496786b0ae9 100644
--- a/llvm/lib/IR/AttributeImpl.h
+++ b/llvm/lib/IR/AttributeImpl.h
@@ -266,6 +266,7 @@ class AttributeSetNode final
   UWTableKind getUWTableKind() const;
   AllocFnKind getAllocKind() const;
   MemoryEffects getMemoryEffects() const;
+  FPClassTest getNoFPClass() const;
   std::string getAsString(bool InAttrGrp) const;
   Type *getAttributeType(Attribute::AttrKind Kind) const;
 

diff  --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index 8c989c4645512..21f119da342c0 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -216,6 +216,11 @@ Attribute Attribute::getWithMemoryEffects(LLVMContext &Context,
   return get(Context, Memory, ME.toIntValue());
 }
 
+Attribute Attribute::getWithNoFPClass(LLVMContext &Context,
+                                      FPClassTest ClassMask) {
+  return get(Context, NoFPClass, ClassMask);
+}
+
 Attribute
 Attribute::getWithAllocSizeArgs(LLVMContext &Context, unsigned ElemSizeArg,
                                 const std::optional<unsigned> &NumElemsArg) {
@@ -261,6 +266,16 @@ bool Attribute::isExistingAttribute(StringRef Name) {
       .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<ArrayType>(Ty))
+    Ty = ArrTy->getElementType();
+  return Ty->isFPOrFPVectorTy();
+}
+
 //===----------------------------------------------------------------------===//
 // Attribute Accessor Methods
 //===----------------------------------------------------------------------===//
@@ -396,6 +411,12 @@ MemoryEffects Attribute::getMemoryEffects() const {
   return MemoryEffects::createFromIntValue(pImpl->getValueAsInt());
 }
 
+FPClassTest Attribute::getNoFPClass() const {
+  assert(hasAttribute(Attribute::NoFPClass) &&
+         "Can only call getNoFPClass() on nofpclass attribute");
+  return static_cast<FPClassTest>(pImpl->getValueAsInt());
+}
+
 static const char *getModRefStr(ModRefInfo MR) {
   switch (MR) {
   case ModRefInfo::NoModRef:
@@ -410,6 +431,56 @@ static const char *getModRefStr(ModRefInfo MR) {
   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<unsigned, StringLiteral> 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);
+
+  if (Mask == 0) {
+    OS << "none)";
+    return Result;
+  }
+
+  ListSeparator LS(" ");
+  for (auto [BitTest, Name] : NoFPClassName) {
+    if ((Mask & BitTest) == BitTest) {
+      OS << LS << Name;
+
+      // Clear the bits so we don't print any aliased names later.
+      Mask &= ~BitTest;
+    }
+  }
+
+  assert(Mask == 0 && "didn't print some mask bits");
+
+  OS << ')';
+  return Result;
+}
+
 std::string Attribute::getAsString(bool InAttrGrp) const {
   if (!pImpl) return {};
 
@@ -543,6 +614,9 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
     return Result;
   }
 
+  if (hasAttribute(Attribute::NoFPClass))
+    return getNoFPClassAttrAsString(getValueAsInt());
+
   // Convert target-dependent attributes to strings of the form:
   //
   //   "kind"
@@ -840,6 +914,10 @@ MemoryEffects AttributeSet::getMemoryEffects() const {
   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 +1102,12 @@ MemoryEffects AttributeSetNode::getMemoryEffects() const {
   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 +1644,14 @@ AttributeList::getParamDereferenceableOrNullBytes(unsigned Index) const {
   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 +1895,10 @@ AttrBuilder &AttrBuilder::addMemoryAttr(MemoryEffects ME) {
   return addRawIntAttr(Attribute::Memory, ME.toIntValue());
 }
 
+AttrBuilder &AttrBuilder::addNoFPClassAttr(FPClassTest Mask) {
+  return addRawIntAttr(Attribute::NoFPClass, Mask);
+}
+
 AttrBuilder &AttrBuilder::addAllocKindAttr(AllocFnKind Kind) {
   return addRawIntAttr(Attribute::AllocKind, static_cast<uint64_t>(Kind));
 }
@@ -1926,6 +2022,11 @@ AttributeMask AttributeFuncs::typeIncompatible(Type *Ty,
       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)

diff  --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp
index 677db46124e4a..3a214cc912093 100644
--- a/llvm/lib/IR/Function.cpp
+++ b/llvm/lib/IR/Function.cpp
@@ -229,6 +229,10 @@ uint64_t Argument::getDereferenceableOrNullBytes() const {
   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);

diff  --git a/llvm/lib/IR/Instructions.cpp b/llvm/lib/IR/Instructions.cpp
index 3746a3de7eed3..da5b02b491a44 100644
--- a/llvm/lib/IR/Instructions.cpp
+++ b/llvm/lib/IR/Instructions.cpp
@@ -325,6 +325,22 @@ Intrinsic::ID CallBase::getIntrinsicID() const {
   return Intrinsic::not_intrinsic;
 }
 
+FPClassTest CallBase::getRetNoFPClass() const {
+  FPClassTest Mask = Attrs.getRetNoFPClass();
+
+  if (const Function *F = getCalledFunction())
+    Mask |= F->getAttributes().getRetNoFPClass();
+  return Mask;
+}
+
+FPClassTest CallBase::getParamNoFPClass(unsigned i) const {
+  FPClassTest Mask = Attrs.getParamNoFPClass(i);
+
+  if (const Function *F = getCalledFunction())
+    Mask |= F->getAttributes().getParamNoFPClass(i);
+  return Mask;
+}
+
 bool CallBase::isReturnNonNull() const {
   if (hasRetAttr(Attribute::NonNull))
     return true;

diff  --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 877c7d4affe8b..262f2aebefafc 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1964,6 +1964,14 @@ void Verifier::verifyParameterAttrs(AttributeSet Attrs, Type *Ty,
       }
     }
   }
+
+  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,

diff  --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index 99d70389ca1fc..c390af351a694 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -918,6 +918,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs,
       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:

diff  --git a/llvm/test/Assembler/nofpclass-invalid.ll b/llvm/test/Assembler/nofpclass-invalid.ll
new file mode 100644
index 0000000000000..79a1f91ceeaca
--- /dev/null
+++ b/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
+}

diff  --git a/llvm/test/Assembler/nofpclass.ll b/llvm/test/Assembler/nofpclass.ll
new file mode 100644
index 0000000000000..45402164a7e6c
--- /dev/null
+++ b/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(<vscale x 2 x half> nofpclass(nan inf) %x) {
+; CHECK-LABEL: define {{[^@]+}}@nofpclass_nan_inf_scalable_v2f16
+; CHECK-SAME: (<vscale x 2 x half> 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
+}

diff  --git a/llvm/test/Bitcode/compatibility.ll b/llvm/test/Bitcode/compatibility.ll
index 3caf66ce7e3ea..78baa7edec4cb 100644
--- a/llvm/test/Bitcode/compatibility.ll
+++ b/llvm/test/Bitcode/compatibility.ll
@@ -1980,6 +1980,77 @@ declare void @f.nosanitize_bounds() nosanitize_bounds
 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 }

diff  --git a/llvm/test/Transforms/InstSimplify/known-never-nan.ll b/llvm/test/Transforms/InstSimplify/known-never-nan.ll
index bf81a8a1ed6b5..d5ab2eb6064f1 100644
--- a/llvm/test/Transforms/InstSimplify/known-never-nan.ll
+++ b/llvm/test/Transforms/InstSimplify/known-never-nan.ll
@@ -447,3 +447,111 @@ declare double @llvm.nearbyint.f64(double)
 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
+}

diff  --git a/llvm/test/Transforms/Util/nofpclass.ll b/llvm/test/Transforms/Util/nofpclass.ll
new file mode 100644
index 0000000000000..06ac752da4656
--- /dev/null
+++ b/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.
+
+ at 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}

diff  --git a/llvm/test/Verifier/nofpclass.ll b/llvm/test/Verifier/nofpclass.ll
new file mode 100644
index 0000000000000..ea140ac6d97ef
--- /dev/null
+++ b/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
+}

diff  --git a/llvm/unittests/IR/VerifierTest.cpp b/llvm/unittests/IR/VerifierTest.cpp
index cca29e84ce75e..2e14bfc42670e 100644
--- a/llvm/unittests/IR/VerifierTest.cpp
+++ b/llvm/unittests/IR/VerifierTest.cpp
@@ -109,6 +109,41 @@ TEST(VerifierTest, InvalidRetAttribute) {
       "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();
+
+    // Don't use getWithNoFPClass to avoid using out of bounds enum values here.
+    F->setAttributes(AS.addRetAttribute(
+        C, Attribute::get(C, Attribute::NoFPClass, 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);


        


More information about the llvm-commits mailing list