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
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
These functions get properties of floating-point values.
+.. _llvm.is.fpclass:
+
'``llvm.is.fpclass``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* 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
------------------------
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);
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,
ATTR_KIND_FNRETTHUNK_EXTERN = 84,
ATTR_KIND_SKIP_PROFILE = 85,
ATTR_KIND_MEMORY = 86,
+ ATTR_KIND_NOFPCLASS = 87,
};
enum ComdatSelectionKindCodes {
/// 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;
class MemoryEffects;
class Type;
class raw_ostream;
+enum FPClassTest : unsigned;
enum class AllocFnKind : uint64_t {
Unknown = 0,
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.
/// 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;
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.
/// 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;
/// 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;
/// 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]>;
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);
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).
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();
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();
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);
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();
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
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:
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;
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:
UWTableKind getUWTableKind() const;
AllocFnKind getAllocKind() const;
MemoryEffects getMemoryEffects() const;
+ FPClassTest getNoFPClass() const;
std::string getAsString(bool InAttrGrp) const;
Type *getAttributeType(Attribute::AttrKind Kind) const;
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) {
.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
//===----------------------------------------------------------------------===//
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:
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 {};
return Result;
}
+ if (hasAttribute(Attribute::NoFPClass))
+ return getNoFPClassAttrAsString(getValueAsInt());
+
// Convert target-dependent attributes to strings of the form:
//
// "kind"
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) : "";
}
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) {
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();
}
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));
}
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)
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);
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;
}
}
}
+
+ 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,
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:
--- /dev/null
+; 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
+}
--- /dev/null
+; 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
+}
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 }
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
+}
--- /dev/null
+; 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}
--- /dev/null
+; 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
+}
"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);