89 changes: 89 additions & 0 deletions llvm/lib/AsmParser/LLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions llvm/lib/Bitcode/Reader/BitcodeReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/IR/AttributeImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
101 changes: 101 additions & 0 deletions llvm/lib/IR/Attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -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:
Expand All @@ -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 {};

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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) : "";
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/IR/Function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions llvm/lib/IR/Instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions llvm/lib/IR/Verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Transforms/Utils/CodeExtractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
133 changes: 133 additions & 0 deletions llvm/test/Assembler/nofpclass-invalid.ll
Original file line number Diff line number Diff line change
@@ -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
}
436 changes: 436 additions & 0 deletions llvm/test/Assembler/nofpclass.ll

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions llvm/test/Bitcode/compatibility.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
108 changes: 108 additions & 0 deletions llvm/test/Transforms/InstSimplify/known-never-nan.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
52 changes: 52 additions & 0 deletions llvm/test/Transforms/Util/nofpclass.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
; RUN: opt -S -passes=pgo-icall-prom -icp-total-percent-threshold=0 < %s 2>&1 | FileCheck %s

; Test that CallPromotionUtils will promote calls which require pointer cast
; safely, i.e. drop incompatible attributes.

@foo = common global ptr null, align 8

; correct type, preserve attributes
define double @func_double(double %a) {
ret double poison
}

; drop nofpclass attributes
define i64 @func_i64(i64 %a) {
ret i64 poison
}

define double @cast_scalar_fp(double %arg) {
%tmp = load ptr, ptr @foo, align 8

; Make sure callsite attributes are dropped on arguments and retval.
; CHECK: [[ARG:%[0-9]+]] = bitcast double %arg to i64
; CHECK-NEXT: call i64 @func_i64(i64 [[ARG]])

; Make sure callsite attributes are preserved on arguments and retval.
; CHECK: call nofpclass(inf) double @func_double(double nofpclass(nan)

; CHECK: call nofpclass(inf) double %tmp(double nofpclass(nan) %arg)
%call = call nofpclass(inf) double %tmp(double nofpclass(nan) %arg), !prof !0
ret double %call
}

; ; correct type, preserve attributes
define [2 x [2 x <2 x double>]] @func_array_vector_f64([2 x [2 x <2 x double>]] %a) {
ret [2 x [2 x <2 x double>]] poison
}

; drop nofpclass attributes
define [2 x [2 x <2 x i64>]] @func_array_vector_i64([2 x [2 x <2 x i64>]] %a) {
ret [2 x [2 x <2 x i64>]] poison
}

; FIXME: This is not promoted
; CHECK: %call = call nofpclass(inf) [2 x [2 x <2 x double>]] %tmp([2 x [2 x <2 x double>]] nofpclass(nan) %arg)
define [2 x [2 x <2 x double>]] @cast_array_vector([2 x [2 x <2 x double>]] %arg) {
%tmp = load ptr, ptr @foo, align 8
%call = call nofpclass(inf) [2 x [2 x <2 x double>]] %tmp([2 x [2 x <2 x double>]] nofpclass(nan) %arg), !prof !1
ret [2 x [2 x <2 x double>]] %call
}

!0 = !{!"VP", i32 0, i64 1440, i64 15573779287943805696, i64 1030, i64 16900752280434761561, i64 410}
!1 = !{!"VP", i32 0, i64 1440, i64 1124945363680759394, i64 1030, i64 16341336592352938424, i64 410}
66 changes: 66 additions & 0 deletions llvm/test/Verifier/nofpclass.ll
Original file line number Diff line number Diff line change
@@ -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
}
35 changes: 35 additions & 0 deletions llvm/unittests/IR/VerifierTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down