Skip to content

Commit

Permalink
[Intrinsic] Add llvm.minimum and llvm.maximum instrinsic functions
Browse files Browse the repository at this point in the history
Summary:
These new intrinsics have the semantics of the `minimum` and `maximum`
operations specified by the latest draft of IEEE 754-2018. Unlike
llvm.minnum and llvm.maxnum, these new intrinsics propagate NaNs and
always treat -0.0 as less than 0.0. `minimum` and `maximum` lower
directly to the existing `fminnan` and `fmaxnan` ISel DAG nodes. It is
safe to reuse these DAG nodes because before this patch were only
emitted in situations where there were known to be no NaN arguments or
where NaN propagation was correct and there were known to be no zero
arguments. I know of only four backends that lower fminnan and
fmaxnan: WebAssembly, ARM, AArch64, and SystemZ, and each of these
lowers fminnan and fmaxnan to instructions that are compatible with
the IEEE 754-2018 semantics.

Reviewers: aheejin, dschuff, sunfish, javed.absar

Subscribers: kristof.beyls, dexonsmith, kristina, llvm-commits

Differential Revision: https://reviews.llvm.org/D52764

llvm-svn: 344437
  • Loading branch information
tlively committed Oct 13, 2018
1 parent 0ff82ac commit 16c349d
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 2 deletions.
76 changes: 76 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11560,6 +11560,82 @@ NaN, the intrinsic lowering is responsible for quieting the inputs to
correctly return the non-NaN input (e.g. by using the equivalent of
``llvm.canonicalize``).

'``llvm.minimum.*``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Syntax:
"""""""

This is an overloaded intrinsic. You can use ``llvm.minimum`` on any
floating-point or vector of floating-point type. Not all targets support
all types however.

::

declare float @llvm.minimum.f32(float %Val0, float %Val1)
declare double @llvm.minimum.f64(double %Val0, double %Val1)
declare x86_fp80 @llvm.minimum.f80(x86_fp80 %Val0, x86_fp80 %Val1)
declare fp128 @llvm.minimum.f128(fp128 %Val0, fp128 %Val1)
declare ppc_fp128 @llvm.minimum.ppcf128(ppc_fp128 %Val0, ppc_fp128 %Val1)

Overview:
"""""""""

The '``llvm.minimum.*``' intrinsics return the minimum of the two
arguments, propagating NaNs and treating -0.0 as less than +0.0.


Arguments:
""""""""""

The arguments and return value are floating-point numbers of the same
type.

Semantics:
""""""""""
If either operand is a NaN, returns NaN. Otherwise returns the lesser
of the two arguments. -0.0 is considered to be less than +0.0 for this
intrinsic. Note that these are the semantics specified in the draft of
IEEE 754-2018.

'``llvm.maximum.*``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Syntax:
"""""""

This is an overloaded intrinsic. You can use ``llvm.maximum`` on any
floating-point or vector of floating-point type. Not all targets support
all types however.

::

declare float @llvm.maximum.f32(float %Val0, float %Val1)
declare double @llvm.maximum.f64(double %Val0, double %Val1)
declare x86_fp80 @llvm.maximum.f80(x86_fp80 %Val0, x86_fp80 %Val1)
declare fp128 @llvm.maximum.f128(fp128 %Val0, fp128 %Val1)
declare ppc_fp128 @llvm.maximum.ppcf128(ppc_fp128 %Val0, ppc_fp128 %Val1)

Overview:
"""""""""

The '``llvm.maximum.*``' intrinsics return the maximum of the two
arguments, propagating NaNs and treating -0.0 as less than +0.0.


Arguments:
""""""""""

The arguments and return value are floating-point numbers of the same
type.

Semantics:
""""""""""
If either operand is a NaN, returns NaN. Otherwise returns the greater
of the two arguments. -0.0 is considered to be less than +0.0 for this
intrinsic. Note that these are the semantics specified in the draft of
IEEE 754-2018.

'``llvm.copysign.*``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
26 changes: 26 additions & 0 deletions llvm/include/llvm/ADT/APFloat.h
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,32 @@ inline APFloat maxnum(const APFloat &A, const APFloat &B) {
return (A.compare(B) == APFloat::cmpLessThan) ? B : A;
}

/// Implements IEEE 754-2018 minimum semantics. Returns the smaller of 2
/// arguments, propagating NaNs and treating -0 as less than +0.
LLVM_READONLY
inline APFloat minimum(const APFloat &A, const APFloat &B) {
if (A.isNaN())
return A;
if (B.isNaN())
return B;
if (A.isZero() && B.isZero() && (A.isNegative() != B.isNegative()))
return A.isNegative() ? A : B;
return (B.compare(A) == APFloat::cmpLessThan) ? B : A;
}

/// Implements IEEE 754-2018 maximum semantics. Returns the larger of 2
/// arguments, propagating NaNs and treating -0 as less than +0.
LLVM_READONLY
inline APFloat maximum(const APFloat &A, const APFloat &B) {
if (A.isNaN())
return A;
if (B.isNaN())
return B;
if (A.isZero() && B.isZero() && (A.isNegative() != B.isNegative()))
return A.isNegative() ? B : A;
return (A.compare(B) == APFloat::cmpLessThan) ? B : A;
}

} // namespace llvm

#undef APFLOAT_DISPATCH_ON_SEMANTICS
Expand Down
5 changes: 3 additions & 2 deletions llvm/include/llvm/CodeGen/ISDOpcodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -560,8 +560,9 @@ namespace ISD {
///
/// The return value of (FMINNUM 0.0, -0.0) could be either 0.0 or -0.0.
FMINNUM, FMAXNUM,
/// FMINNAN/FMAXNAN - Behave identically to FMINNUM/FMAXNUM, except that
/// when a single input is NaN, NaN is returned.
/// FMINNAN/FMAXNAN - NaN-propagating minimum/maximum that also treat -0.0
/// as less than 0.0. While FMINNUM/FMAXNUM follow IEEE 754-2008 semantics,
/// FMINNAN/FMAXNAN follow IEEE 754-2018 draft semantics.
FMINNAN, FMAXNAN,

/// FSINCOS - Compute both fsin and fcos as a single operation.
Expand Down
10 changes: 10 additions & 0 deletions llvm/include/llvm/IR/IRBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,16 @@ class IRBuilderBase {
return CreateBinaryIntrinsic(Intrinsic::maxnum, LHS, RHS, nullptr, Name);
}

/// Create call to the minimum intrinsic.
CallInst *CreateMinimum(Value *LHS, Value *RHS, const Twine &Name = "") {
return CreateBinaryIntrinsic(Intrinsic::minimum, LHS, RHS, nullptr, Name);
}

/// Create call to the maximum intrinsic.
CallInst *CreateMaximum(Value *LHS, Value *RHS, const Twine &Name = "") {
return CreateBinaryIntrinsic(Intrinsic::maximum, LHS, RHS, nullptr, Name);
}

private:
/// Create a call to a masked intrinsic with given Id.
CallInst *CreateMaskedIntrinsic(Intrinsic::ID Id, ArrayRef<Value *> Ops,
Expand Down
8 changes: 8 additions & 0 deletions llvm/include/llvm/IR/Intrinsics.td
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,14 @@ def int_maxnum : Intrinsic<[llvm_anyfloat_ty],
[LLVMMatchType<0>, LLVMMatchType<0>],
[IntrNoMem, IntrSpeculatable, Commutative]
>;
def int_minimum : Intrinsic<[llvm_anyfloat_ty],
[LLVMMatchType<0>, LLVMMatchType<0>],
[IntrNoMem, IntrSpeculatable, Commutative]
>;
def int_maximum : Intrinsic<[llvm_anyfloat_ty],
[LLVMMatchType<0>, LLVMMatchType<0>],
[IntrNoMem, IntrSpeculatable, Commutative]
>;

// NOTE: these are internal interfaces.
def int_setjmp : Intrinsic<[llvm_i32_ty], [llvm_ptr_ty]>;
Expand Down
12 changes: 12 additions & 0 deletions llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5584,6 +5584,18 @@ SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I, unsigned Intrinsic) {
getValue(I.getArgOperand(1))));
return nullptr;
}
case Intrinsic::minimum:
setValue(&I, DAG.getNode(ISD::FMINNAN, sdl,
getValue(I.getArgOperand(0)).getValueType(),
getValue(I.getArgOperand(0)),
getValue(I.getArgOperand(1))));
return nullptr;
case Intrinsic::maximum:
setValue(&I, DAG.getNode(ISD::FMAXNAN, sdl,
getValue(I.getArgOperand(0)).getValueType(),
getValue(I.getArgOperand(0)),
getValue(I.getArgOperand(1))));
return nullptr;
case Intrinsic::copysign:
setValue(&I, DAG.getNode(ISD::FCOPYSIGN, sdl,
getValue(I.getArgOperand(0)).getValueType(),
Expand Down
30 changes: 30 additions & 0 deletions llvm/unittests/ADT/APFloatTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,36 @@ TEST(APFloatTest, MaxNum) {
EXPECT_EQ(1.0, maxnum(nan, f1).convertToDouble());
}

TEST(APFloatTest, Minimum) {
APFloat f1(1.0);
APFloat f2(2.0);
APFloat zp(0.0);
APFloat zn(-0.0);
APFloat nan = APFloat::getNaN(APFloat::IEEEdouble());

EXPECT_EQ(1.0, minimum(f1, f2).convertToDouble());
EXPECT_EQ(1.0, minimum(f2, f1).convertToDouble());
EXPECT_EQ(-0.0, minimum(zp, zn).convertToDouble());
EXPECT_EQ(-0.0, minimum(zn, zp).convertToDouble());
EXPECT_TRUE(std::isnan(minimum(f1, nan).convertToDouble()));
EXPECT_TRUE(std::isnan(minimum(nan, f1).convertToDouble()));
}

TEST(APFloatTest, Maximum) {
APFloat f1(1.0);
APFloat f2(2.0);
APFloat zp(0.0);
APFloat zn(-0.0);
APFloat nan = APFloat::getNaN(APFloat::IEEEdouble());

EXPECT_EQ(2.0, maximum(f1, f2).convertToDouble());
EXPECT_EQ(2.0, maximum(f2, f1).convertToDouble());
EXPECT_EQ(0.0, maximum(zp, zn).convertToDouble());
EXPECT_EQ(0.0, maximum(zn, zp).convertToDouble());
EXPECT_TRUE(std::isnan(maximum(f1, nan).convertToDouble()));
EXPECT_TRUE(std::isnan(maximum(nan, f1).convertToDouble()));
}

TEST(APFloatTest, Denormal) {
APFloat::roundingMode rdmd = APFloat::rmNearestTiesToEven;

Expand Down
8 changes: 8 additions & 0 deletions llvm/unittests/IR/IRBuilderTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ TEST_F(IRBuilderTest, Intrinsics) {
II = cast<IntrinsicInst>(Call);
EXPECT_EQ(II->getIntrinsicID(), Intrinsic::maxnum);

Call = Builder.CreateMinimum(V, V);
II = cast<IntrinsicInst>(Call);
EXPECT_EQ(II->getIntrinsicID(), Intrinsic::minimum);

Call = Builder.CreateMaximum(V, V);
II = cast<IntrinsicInst>(Call);
EXPECT_EQ(II->getIntrinsicID(), Intrinsic::maximum);

Call = Builder.CreateIntrinsic(Intrinsic::readcyclecounter, {}, {});
II = cast<IntrinsicInst>(Call);
EXPECT_EQ(II->getIntrinsicID(), Intrinsic::readcyclecounter);
Expand Down

0 comments on commit 16c349d

Please sign in to comment.