[libc] Add implementations of nextafter[f|l] functions.

A differential fuzzer for these functions has also been added.
Along the way, a small correction has been done to the normal/subnormal
limits of x86 long double values.

Reviewed By: lntue

Differential Revision: https://reviews.llvm.org/D94109
This commit is contained in:
Siva Chandra Reddy 2021-01-03 22:33:48 -08:00
parent 993d8ac5cb
commit 7f7b0dc4e1
21 changed files with 618 additions and 1 deletions

View File

@ -88,6 +88,9 @@ set(TARGET_LIBM_ENTRYPOINTS
libc.src.math.modf
libc.src.math.modff
libc.src.math.modfl
libc.src.math.nextafter
libc.src.math.nextafterf
libc.src.math.nextafterl
libc.src.math.remainderf
libc.src.math.remainder
libc.src.math.remainderl

View File

@ -144,6 +144,9 @@ set(TARGET_LIBM_ENTRYPOINTS
libc.src.math.nearbyint
libc.src.math.nearbyintf
libc.src.math.nearbyintl
libc.src.math.nextafter
libc.src.math.nextafterf
libc.src.math.nextafterl
libc.src.math.remainderf
libc.src.math.remainder
libc.src.math.remainderl

View File

@ -48,3 +48,15 @@ add_libc_fuzzer(
libc.utils.FPUtil.fputil
libc.utils.CPP.standalone_cpp
)
add_libc_fuzzer(
nextafter_differential_fuzz
SRCS
nextafter_differential_fuzz.cpp
HDRS
TwoInputSingleOutputDiff.h
DEPENDS
libc.src.math.nextafter
libc.src.math.nextafterf
libc.src.math.nextafterl
)

View File

@ -10,6 +10,7 @@
#define LLVM_LIBC_FUZZING_MATH_COMPARE_H
#include "utils/CPP/TypeTraits.h"
#include "utils/FPUtil/FPBits.h"
template <typename T>
__llvm_libc::cpp::EnableIfType<__llvm_libc::cpp::IsFloatingPointType<T>::Value,

View File

@ -0,0 +1,26 @@
//===-- nextafter_differential_fuzz.cpp
//---------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// Differential fuzz test for llvm-libc nextafter implementation.
///
//===----------------------------------------------------------------------===//
#include "fuzzing/math/TwoInputSingleOutputDiff.h"
#include "src/math/nextafter.h"
#include "src/math/nextafterf.h"
#include "src/math/nextafterl.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
TwoInputSingleOutputDiff<float, float>(&__llvm_libc::nextafterf,
&::nextafterf, data, size);
TwoInputSingleOutputDiff<double, double>(&__llvm_libc::nextafter,
&::nextafter, data, size);
return 0;
}

View File

@ -394,6 +394,10 @@ def StdC : StandardSpec<"stdc"> {
FunctionSpec<"nearbyint", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
FunctionSpec<"nearbyintf", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
FunctionSpec<"nearbyintl", RetValSpec<LongDoubleType>, [ArgSpec<LongDoubleType>]>,
FunctionSpec<"nextafterf", RetValSpec<FloatType>, [ArgSpec<FloatType>, ArgSpec<FloatType>]>,
FunctionSpec<"nextafter", RetValSpec<DoubleType>, [ArgSpec<DoubleType>, ArgSpec<DoubleType>]>,
FunctionSpec<"nextafterl", RetValSpec<LongDoubleType>, [ArgSpec<LongDoubleType>, ArgSpec<LongDoubleType>]>,
]
>;

View File

@ -941,3 +941,40 @@ add_entrypoint_object(
COMPILE_OPTIONS
-O2
)
add_entrypoint_object(
nextafter
SRCS
nextafter.cpp
HDRS
nextafter.h
DEPENDS
libc.utils.FPUtil.fputil
COMPILE_OPTIONS
-O2
)
add_entrypoint_object(
nextafterf
SRCS
nextafterf.cpp
HDRS
nextafterf.h
DEPENDS
libc.utils.FPUtil.fputil
COMPILE_OPTIONS
-O2
)
add_entrypoint_object(
nextafterl
SRCS
nextafterl.cpp
HDRS
nextafterl.h
DEPENDS
libc.utils.FPUtil.fputil
COMPILE_OPTIONS
-O2
)

View File

@ -0,0 +1,18 @@
//===-- Implementation of nextafter function ------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/__support/common.h"
#include "utils/FPUtil/ManipulationFunctions.h"
namespace __llvm_libc {
double LLVM_LIBC_ENTRYPOINT(nextafter)(double x, double y) {
return fputil::nextafter(x, y);
}
} // namespace __llvm_libc

18
libc/src/math/nextafter.h Normal file
View File

@ -0,0 +1,18 @@
//===-- Implementation header for nextafter ---------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_MATH_NEXTAFTER_H
#define LLVM_LIBC_SRC_MATH_NEXTAFTER_H
namespace __llvm_libc {
double nextafter(double x, double y);
} // namespace __llvm_libc
#endif // LLVM_LIBC_SRC_MATH_NEXTAFTER_H

View File

@ -0,0 +1,18 @@
//===-- Implementation of nextafterf function -----------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/__support/common.h"
#include "utils/FPUtil/ManipulationFunctions.h"
namespace __llvm_libc {
float LLVM_LIBC_ENTRYPOINT(nextafterf)(float x, float y) {
return fputil::nextafter(x, y);
}
} // namespace __llvm_libc

View File

@ -0,0 +1,18 @@
//===-- Implementation header for nextafterf --------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_MATH_NEXTAFTERF_H
#define LLVM_LIBC_SRC_MATH_NEXTAFTERF_H
namespace __llvm_libc {
float nextafterf(float x, float y);
} // namespace __llvm_libc
#endif // LLVM_LIBC_SRC_MATH_NEXTAFTERF_H

View File

@ -0,0 +1,18 @@
//===-- Implementation of nextafterl function -----------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/__support/common.h"
#include "utils/FPUtil/ManipulationFunctions.h"
namespace __llvm_libc {
long double LLVM_LIBC_ENTRYPOINT(nextafterl)(long double x, long double y) {
return fputil::nextafter(x, y);
}
} // namespace __llvm_libc

View File

@ -0,0 +1,18 @@
//===-- Implementation header for nextafterl --------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_MATH_NEXTAFTERL_H
#define LLVM_LIBC_SRC_MATH_NEXTAFTERL_H
namespace __llvm_libc {
long double nextafterl(long double x, long double y);
} // namespace __llvm_libc
#endif // LLVM_LIBC_SRC_MATH_NEXTAFTERL_H

View File

@ -1007,3 +1007,45 @@ add_fp_unittest(
libc.src.math.hypot
libc.utils.FPUtil.fputil
)
add_fp_unittest(
nextafter_test
SUITE
libc_math_unittests
SRCS
nextafter_test.cpp
HDRS
NextAfterTest.h
DEPENDS
libc.include.math
libc.src.math.nextafter
libc.utils.FPUtil.fputil
)
add_fp_unittest(
nextafterf_test
SUITE
libc_math_unittests
SRCS
nextafterf_test.cpp
HDRS
NextAfterTest.h
DEPENDS
libc.include.math
libc.src.math.nextafterf
libc.utils.FPUtil.fputil
)
add_fp_unittest(
nextafterl_test
SUITE
libc_math_unittests
SRCS
nextafterl_test.cpp
HDRS
NextAfterTest.h
DEPENDS
libc.include.math
libc.src.math.nextafterl
libc.utils.FPUtil.fputil
)

View File

@ -0,0 +1,193 @@
//===-- Utility class to test different flavors of nextafter ----*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_TEST_SRC_MATH_NEXTAFTERTEST_H
#define LLVM_LIBC_TEST_SRC_MATH_NEXTAFTERTEST_H
#include "utils/CPP/TypeTraits.h"
#include "utils/FPUtil/BasicOperations.h"
#include "utils/FPUtil/FPBits.h"
#include "utils/FPUtil/TestHelpers.h"
#include "utils/UnitTest/Test.h"
#include <math.h>
template <typename T>
class NextAfterTestTemplate : public __llvm_libc::testing::Test {
using FPBits = __llvm_libc::fputil::FPBits<T>;
using MantissaWidth = __llvm_libc::fputil::MantissaWidth<T>;
using UIntType = typename FPBits::UIntType;
#if (defined(__x86_64__) || defined(__i386__))
static constexpr int bitWidthOfType =
__llvm_libc::cpp::IsSame<T, long double>::Value ? 80 : (sizeof(T) * 8);
#else
static constexpr int bitWidthOfType = sizeof(T) * 8;
#endif
const T zero = FPBits::zero();
const T negZero = FPBits::negZero();
const T inf = FPBits::inf();
const T negInf = FPBits::negInf();
const T nan = FPBits::buildNaN(1);
const UIntType minSubnormal = FPBits::minSubnormal;
const UIntType maxSubnormal = FPBits::maxSubnormal;
const UIntType minNormal = FPBits::minNormal;
const UIntType maxNormal = FPBits::maxNormal;
public:
typedef T (*NextAfterFunc)(T, T);
void testNaN(NextAfterFunc func) {
ASSERT_FP_EQ(func(nan, 0), nan);
ASSERT_FP_EQ(func(0, nan), nan);
}
void testBoundaries(NextAfterFunc func) {
ASSERT_FP_EQ(func(zero, negZero), negZero);
ASSERT_FP_EQ(func(negZero, zero), zero);
// 'from' is zero|negZero.
T x = zero;
T result = func(x, T(1));
UIntType expectedBits = 1;
T expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
result = func(x, T(-1));
expectedBits = (UIntType(1) << (bitWidthOfType - 1)) + 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
x = negZero;
result = func(x, 1);
expectedBits = 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
result = func(x, -1);
expectedBits = (UIntType(1) << (bitWidthOfType - 1)) + 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
// 'from' is max subnormal value.
x = *reinterpret_cast<const T *>(&maxSubnormal);
result = func(x, 1);
expected = *reinterpret_cast<const T *>(&minNormal);
ASSERT_FP_EQ(result, expected);
result = func(x, 0);
expectedBits = maxSubnormal - 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
x = -x;
result = func(x, -1);
expectedBits = (UIntType(1) << (bitWidthOfType - 1)) + minNormal;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
result = func(x, 0);
expectedBits = (UIntType(1) << (bitWidthOfType - 1)) + maxSubnormal - 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
// 'from' is min subnormal value.
x = *reinterpret_cast<const T *>(&minSubnormal);
result = func(x, 1);
expectedBits = minSubnormal + 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
ASSERT_FP_EQ(func(x, 0), 0);
x = -x;
result = func(x, -1);
expectedBits = (UIntType(1) << (bitWidthOfType - 1)) + minSubnormal + 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
ASSERT_FP_EQ(func(x, 0), T(-0.0));
// 'from' is min normal.
x = *reinterpret_cast<const T *>(&minNormal);
result = func(x, 0);
expectedBits = maxSubnormal;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
result = func(x, inf);
expectedBits = minNormal + 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
x = -x;
result = func(x, 0);
expectedBits = (UIntType(1) << (bitWidthOfType - 1)) + maxSubnormal;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
result = func(x, -inf);
expectedBits = (UIntType(1) << (bitWidthOfType - 1)) + minNormal + 1;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
// 'from' is max normal and 'to' is infinity.
x = *reinterpret_cast<const T *>(&maxNormal);
result = func(x, inf);
ASSERT_FP_EQ(result, inf);
result = func(-x, -inf);
ASSERT_FP_EQ(result, -inf);
// 'from' is infinity.
x = inf;
result = func(x, 0);
expectedBits = maxNormal;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
ASSERT_FP_EQ(func(x, inf), inf);
x = negInf;
result = func(x, 0);
expectedBits = (UIntType(1) << (bitWidthOfType - 1)) + maxNormal;
expected = *reinterpret_cast<T *>(&expectedBits);
ASSERT_FP_EQ(result, expected);
ASSERT_FP_EQ(func(x, negInf), negInf);
// 'from' is a power of 2.
x = T(32.0);
result = func(x, 0);
FPBits xBits = FPBits(x);
FPBits resultBits = FPBits(result);
ASSERT_EQ(resultBits.exponent, uint16_t(xBits.exponent - 1));
ASSERT_EQ(resultBits.mantissa, (UIntType(1) << MantissaWidth::value) - 1);
result = func(x, T(33.0));
resultBits = FPBits(result);
ASSERT_EQ(resultBits.exponent, xBits.exponent);
ASSERT_EQ(resultBits.mantissa, xBits.mantissa + UIntType(1));
x = -x;
result = func(x, 0);
resultBits = FPBits(result);
ASSERT_EQ(resultBits.exponent, uint16_t(xBits.exponent - 1));
ASSERT_EQ(resultBits.mantissa, (UIntType(1) << MantissaWidth::value) - 1);
result = func(x, T(-33.0));
resultBits = FPBits(result);
ASSERT_EQ(resultBits.exponent, xBits.exponent);
ASSERT_EQ(resultBits.mantissa, xBits.mantissa + UIntType(1));
}
};
#define LIST_NEXTAFTER_TESTS(T, func) \
using NextAfterTest = NextAfterTestTemplate<T>; \
TEST_F(NextAfterTest, TestNaN) { testNaN(&func); } \
TEST_F(NextAfterTest, TestBoundaries) { testBoundaries(&func); }
#endif // LLVM_LIBC_TEST_SRC_MATH_NEXTAFTERTEST_H

View File

@ -0,0 +1,13 @@
//===-- Unittests for nextafter -------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "NextAfterTest.h"
#include "src/math/nextafter.h"
LIST_NEXTAFTER_TESTS(double, __llvm_libc::nextafter)

View File

@ -0,0 +1,13 @@
//===-- Unittests for nextafterf ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "NextAfterTest.h"
#include "src/math/nextafterf.h"
LIST_NEXTAFTER_TESTS(float, __llvm_libc::nextafterf)

View File

@ -0,0 +1,13 @@
//===-- Unittests for nextafterl ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "NextAfterTest.h"
#include "src/math/nextafterl.h"
LIST_NEXTAFTER_TESTS(long double, __llvm_libc::nextafterl)

View File

@ -36,7 +36,7 @@ template <> struct __attribute__((packed)) FPBits<long double> {
static constexpr UIntType minSubnormal = UIntType(1);
// Subnormal numbers include the implicit bit in x86 long double formats.
static constexpr UIntType maxSubnormal =
(UIntType(1) << (MantissaWidth<long double>::value + 1)) - 1;
(UIntType(1) << (MantissaWidth<long double>::value)) - 1;
static constexpr UIntType minNormal =
(UIntType(3) << MantissaWidth<long double>::value);
static constexpr UIntType maxNormal =

View File

@ -143,7 +143,42 @@ static inline T ldexp(T x, int exp) {
return normal;
}
template <typename T,
cpp::EnableIfType<cpp::IsFloatingPointType<T>::Value, int> = 0>
static inline T nextafter(T from, T to) {
FPBits<T> fromBits(from);
if (fromBits.isNaN())
return from;
FPBits<T> toBits(to);
if (toBits.isNaN())
return to;
if (from == to)
return to;
using UIntType = typename FPBits<T>::UIntType;
auto intVal = fromBits.bitsAsUInt();
UIntType signMask = (UIntType(1) << (sizeof(T) * 8 - 1));
if (from != T(0.0)) {
if ((from < to) == (from > T(0.0))) {
++intVal;
} else {
--intVal;
}
} else {
intVal = (toBits.bitsAsUInt() & signMask) + UIntType(1);
}
return *reinterpret_cast<T *>(&intVal);
// TODO: Raise floating point exceptions as required by the standard.
}
} // namespace fputil
} // namespace __llvm_libc
#if (defined(__x86_64__) || defined(__i386__))
#include "NextAfterLongDoubleX86.h"
#endif // defined(__x86_64__) || defined(__i386__)
#endif // LLVM_LIBC_UTILS_FPUTIL_MANIPULATION_FUNCTIONS_H

View File

@ -0,0 +1,114 @@
//===-- nextafter implementation for x86 long double numbers ----*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_UTILS_FPUTIL_NEXT_AFTER_LONG_DOUBLE_X86_H
#define LLVM_LIBC_UTILS_FPUTIL_NEXT_AFTER_LONG_DOUBLE_X86_H
#include "FPBits.h"
#include <stdint.h>
namespace __llvm_libc {
namespace fputil {
static inline long double nextafter(long double from, long double to) {
using FPBits = FPBits<long double>;
FPBits fromBits(from);
if (fromBits.isNaN())
return from;
FPBits toBits(to);
if (toBits.isNaN())
return to;
if (from == to)
return to;
// Convert pseudo subnormal number to normal number.
if (fromBits.implicitBit == 1 && fromBits.exponent == 0) {
fromBits.exponent = 1;
}
using UIntType = FPBits::UIntType;
constexpr UIntType signVal = (UIntType(1) << 79);
constexpr UIntType mantissaMask =
(UIntType(1) << MantissaWidth<long double>::value) - 1;
auto intVal = fromBits.bitsAsUInt();
if (from < 0.0l) {
if (from > to) {
if (intVal == (signVal + FPBits::maxSubnormal)) {
// We deal with normal/subnormal boundary separately to avoid
// dealing with the implicit bit.
intVal = signVal + FPBits::minNormal;
} else if ((intVal & mantissaMask) == mantissaMask) {
fromBits.mantissa = 0;
// Incrementing exponent might overflow the value to infinity,
// which is what is expected. Since NaNs are handling separately,
// it will never overflow "beyond" infinity.
++fromBits.exponent;
return fromBits;
} else {
++intVal;
}
} else {
if (intVal == (signVal + FPBits::minNormal)) {
// We deal with normal/subnormal boundary separately to avoid
// dealing with the implicit bit.
intVal = signVal + FPBits::maxSubnormal;
} else if ((intVal & mantissaMask) == 0) {
fromBits.mantissa = mantissaMask;
// from == 0 is handled separately so decrementing the exponent will not
// lead to underflow.
--fromBits.exponent;
return fromBits;
} else {
--intVal;
}
}
} else if (from == 0.0l) {
if (from > to)
intVal = signVal + 1;
else
intVal = 1;
} else {
if (from > to) {
if (intVal == FPBits::minNormal) {
intVal = FPBits::maxSubnormal;
} else if ((intVal & mantissaMask) == 0) {
fromBits.mantissa = mantissaMask;
// from == 0 is handled separately so decrementing the exponent will not
// lead to underflow.
--fromBits.exponent;
return fromBits;
} else {
--intVal;
}
} else {
if (intVal == FPBits::maxSubnormal) {
intVal = FPBits::minNormal;
} else if ((intVal & mantissaMask) == mantissaMask) {
fromBits.mantissa = 0;
// Incrementing exponent might overflow the value to infinity,
// which is what is expected. Since NaNs are handling separately,
// it will never overflow "beyond" infinity.
++fromBits.exponent;
return fromBits;
} else {
++intVal;
}
}
}
return *reinterpret_cast<long double *>(&intVal);
// TODO: Raise floating point exceptions as required by the standard.
}
} // namespace fputil
} // namespace __llvm_libc
#endif // LLVM_LIBC_UTILS_FPUTIL_NEXT_AFTER_LONG_DOUBLE_X86_H