Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libc/config/linux/x86_64/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ endif()
if(LIBC_TYPES_HAS_FLOAT16)
list(APPEND TARGET_LIBM_ENTRYPOINTS
# math.h C23 _Float16 entrypoints
libc.src.math.asinf16
libc.src.math.canonicalizef16
libc.src.math.ceilf16
libc.src.math.copysignf16
Expand Down
2 changes: 1 addition & 1 deletion libc/docs/headers/math/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ Higher Math Functions
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| acospi | | | | | | 7.12.4.8 | F.10.1.8 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| asin | |check| | | | | | 7.12.4.2 | F.10.1.2 |
| asin | |check| | | | |check| | | 7.12.4.2 | F.10.1.2 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| asinh | |check| | | | | | 7.12.5.2 | F.10.2.2 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
Expand Down
7 changes: 7 additions & 0 deletions libc/include/math.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ functions:
return_type: float
arguments:
- type: float
- name: asinf16
standards:
- stdc
return_type: _Float16
arguments:
- type: _Float16
guard: LIBC_TYPES_HAS_FLOAT16
- name: asinhf
standards:
- stdc
Expand Down
1 change: 1 addition & 0 deletions libc/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ add_math_entrypoint_object(asin)
add_math_entrypoint_object(asinf)
add_math_entrypoint_object(asinh)
add_math_entrypoint_object(asinhf)
add_math_entrypoint_object(asinf16)

add_math_entrypoint_object(atan)
add_math_entrypoint_object(atanf)
Expand Down
21 changes: 21 additions & 0 deletions libc/src/math/asinf16.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===-- Implementation header for asinf16 -----------------------*- 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_ASINF16_H
#define LLVM_LIBC_SRC_MATH_ASINF16_H

#include "src/__support/macros/config.h"
#include "src/__support/macros/properties/types.h"

namespace LIBC_NAMESPACE_DECL {

float16 asinf16(float16 x);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_MATH_ASINF16_H
19 changes: 19 additions & 0 deletions libc/src/math/generic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4598,6 +4598,25 @@ add_entrypoint_object(
${libc_opt_high_flag}
)

add_entrypoint_object(
asinf16
SRCS
asinf16.cpp
HDRS
../asinf16.h
DEPENDS
libc.hdr.errno_macros
libc.hdr.fenv_macros
libc.src.__support.FPUtil.cast
libc.src.__support.FPUtil.fenv_impl
libc.src.__support.FPUtil.fp_bits
libc.src.__support.FPUtil.multiply_add
libc.src.__support.FPUtil.polyeval
libc.src.__support.FPUtil.sqrt
libc.src.__support.macros.optimization
libc.src.__support.macros.properties.types
)

add_entrypoint_object(
acosf
SRCS
Expand Down
135 changes: 135 additions & 0 deletions libc/src/math/generic/asinf16.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//===-- Half-precision asinf16(x) 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/math/asinf16.h"
#include "hdr/errno_macros.h"
#include "hdr/fenv_macros.h"
#include "src/__support/FPUtil/FEnvImpl.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/PolyEval.h"
#include "src/__support/FPUtil/cast.h"
#include "src/__support/FPUtil/multiply_add.h"
#include "src/__support/FPUtil/sqrt.h"
#include "src/__support/macros/optimization.h"

namespace LIBC_NAMESPACE_DECL {

// Generated by Sollya using the following command:
// > round(pi/2, D, RN);
static constexpr float PI_2 = 0x1.921fb54442d18p0f;

LLVM_LIBC_FUNCTION(float16, asinf16, (float16 x)) {
using FPBits = fputil::FPBits<float16>;
FPBits xbits(x);

uint16_t x_u = xbits.uintval();
uint16_t x_abs = x_u & 0x7fff;
float xf = x;

float xsq = xf * xf;

// |x| <= 0x1p-1, |x| <= 0.5
if (x_abs <= 0x3800) {
// asinf16(NaN) = NaN
if (xbits.is_nan()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still computes xf * xf before checking that x is not NaN, and xbits.is_nan() is always false here since it implies x_abs > 0x7c00.

Suggested change
float xsq = xf * xf;
// |x| <= 0x1p-1, |x| <= 0.5
if (x_abs <= 0x3800) {
// asinf16(NaN) = NaN
if (xbits.is_nan()) {
// |x| <= 0x1p-1, |x| <= 0.5, or x is NaN.
if (LIBC_UNLIKELY(x_abs <= 0x3800 || x_abs > 0x7c00)) {
// asinf16(NaN) = NaN
if (xbits.is_nan()) {
// ...
}
// ...
}
// ...
float xsq = xf * xf;

Copy link
Contributor Author

@wldfngrs wldfngrs Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about directly checking xbits.is_nan(), like so?

...
float xf = x;

if (xbits.is_nan()) {
  if (xbits.is_signaling_nan()) {
    fputil::raise_except_if_required(FE_INVALID);
    return FPBits::quiet_nan().get_val();
  }

  return x;
}

float xsq = xf * xf;
...

I mean, that'd be equivalent to checking if x_abs > 0x7c00, right?

Also, why include the x_abs <= 0x3800 in the NaN condition? If it falls within the domain of the function, it couldn't be NaN, right?

Copy link
Member

@overmighty overmighty Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about directly checking xbits.is_nan(), like so?

It might generate more instructions than x_abs > 0x7c00, although I haven't verified it in this case, and I haven't benchmarked it.

Also, why include the x_abs <= 0x3800 in the NaN condition? If it falls within the domain of the function, it couldn't be NaN, right?

The point is to group all unlikely cases under a single if (LIBC_UNLIKELY(...)) statement so that the compiler generates better code for the likely case: https://godbolt.org/z/bjzM5fv1z.

Copy link
Contributor Author

@wldfngrs wldfngrs Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might generate more instructions than x_abs > 0x7c00, although I haven't verified it in this case, and I haven't benchmarked it.

How can I quickly verify the instructions generated?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

llvm-objdump -Cdr path/to/*.o

Copy link
Contributor Author

@wldfngrs wldfngrs Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@overmighty

// |x| > 0x1p0, |x| > 1, or x is NaN.
if (LIBC_UNLIKELY(x_abs > 0x3c00)) {
  // asinf16(NaN) = NaN
  if (xbits.is_nan()) {
    if (xbits.is_signaling_nan()) {
      fputil::raise_except_if_required(FE_INVALID);
      return FPBits::quiet_nan().get_val();
    }

    return x;
  }

  // 1 < |x| <= +/-inf
  fputil::raise_except_if_required(FE_INVALID);
  fputil::set_errno_if_required(EDOM);

  return FPBits::quiet_nan().get_val();
}

It might generate more instructions than x_abs > 0x7c00, although I haven't verified it in this case, and I haven't benchmarked it

xbits.is_nan() and x_abs > 0x7c00 generate the same instructions. Also, what do you think of having the final check for inf values in the same conditional scope as the check for NaN-ness like above?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what do you think of having the final check for inf values in the same conditional scope as the check for NaN-ness like above?

It's what I would do. I would even move the x_abs <= 0x3800 case under the same if (LIBC_UNLIKELY(...)) statement, but I guess it's fine either way.

if (xbits.is_signaling_nan()) {
fputil::raise_except_if_required(FE_INVALID);
return FPBits::quiet_nan().get_val();
}

return x;
}

// asinf16(+/-0) = +/-0
if (LIBC_UNLIKELY(x_abs == 0))
return x;

// Exhaustive tests show that,
// for |x| <= 0x1.878p-9, when:
// x > 0, and rounding upward, or
// x < 0, and rounding downward, then,
// asin(x) = x * 2^-11 + x
// else, in other rounding modes,
// asin(x) = x
if (LIBC_UNLIKELY(x_abs <= 0x1a1e)) {
int rounding = fputil::quick_get_round();

if ((xbits.is_pos() && rounding == FE_UPWARD) ||
(xbits.is_neg() && rounding == FE_DOWNWARD))
return fputil::cast<float16>(fputil::multiply_add(xf, 0x1.0p-11f, xf));
return x;
}

// Degree-6 minimax odd polynomial of asin(x) generated by Sollya with:
// > P = fpminimax(asin(x)/x, [|0, 2, 4, 6, 8|], [|SG...|], [0, 0.5]);
float result =
fputil::polyeval(xsq, 0x1.000002p0f, 0x1.554c2ap-3f, 0x1.3541ccp-4f,
0x1.43b2d6p-5f, 0x1.a0d73ep-5f);
return fputil::cast<float16>(xf * result);
}

// |x| > 1, asinf16(x) = NaN
if (LIBC_UNLIKELY(x_abs > 0x3c00)) {
// |x| <= +/-inf
if (LIBC_UNLIKELY(x_abs <= 0x7c00)) {
fputil::set_errno_if_required(EDOM);
fputil::raise_except_if_required(FE_INVALID);
}

return FPBits::quiet_nan().get_val();
}

// When |x| > 0.5, assume that 0.5 < |x| <= 1,
//
// Step-by-step range-reduction proof:
// 1: Let y = asin(x), such that, x = sin(y)
// 2: From complimentary angle identity:
// x = sin(y) = cos(pi/2 - y)
// 3: Let z = pi/2 - y, such that x = cos(z)
// 4: From double angle formula; cos(2A) = 1 - sin^2(A):
// z = 2A, z/2 = A
// cos(z) = 1 - 2 * sin^2(z/2)
// 5: Make sin(z/2) subject of the formula:
// sin(z/2) = sqrt((1 - cos(z))/2)
// 6: Recall [3]; x = cos(z). Therefore:
// sin(z/2) = sqrt((1 - x)/2)
// 7: Let u = (1 - x)/2
// 8: Therefore:
// asin(sqrt(u)) = z/2
// 2 * asin(sqrt(u)) = z
// 9: Recall [3], z = pi/2 - y. Therefore:
// y = pi/2 - z
// y = pi/2 - 2 * asin(sqrt(u))
// 10: Recall [1], y = asin(x). Therefore:
// asin(x) = pi/2 - 2 * asin(sqrt(u))
//
// WHY?
// 11: Recall [7], u = (1 - x)/2
// 12: Since 0.5 < x <= 1, therefore:
// 0 <= u <= 0.25 and 0 <= sqrt(u) <= 0.5
//
// Hence, we can reuse the same [0, 0.5] domain polynomial approximation for
// Step [10] as `sqrt(u)` is in range.

// 0x1p-1 < |x| <= 0x1p0, 0.5 < |x| <= 1.0
float xf_abs = (xf < 0 ? -xf : xf);
float sign = (xbits.uintval() >> 15 == 1 ? -1.0 : 1.0);
float u = fputil::multiply_add(-0.5f, xf_abs, 0.5f);
float u_sqrt = fputil::sqrt<float>(u);

// Degree-6 minimax odd polynomial of asin(x) generated by Sollya with:
// > P = fpminimax(asin(x)/x, [|0, 2, 4, 6, 8|], [|SG...|], [0, 0.5]);
float asin_sqrt_u =
u_sqrt * fputil::polyeval(u, 0x1.000002p0f, 0x1.554c2ap-3f,
0x1.3541ccp-4f, 0x1.43b2d6p-5f, 0x1.a0d73ep-5f);

return fputil::cast<float16>(sign *
fputil::multiply_add(-2.0f, asin_sqrt_u, PI_2));
}

} // namespace LIBC_NAMESPACE_DECL
11 changes: 11 additions & 0 deletions libc/test/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2186,6 +2186,17 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
asinf16_test
NEED_MPFR
SUITE
libc-math-unittests
SRCS
asinf16_test.cpp
DEPENDS
libc.src.math.asinf16
)

add_fp_unittest(
acosf_test
NEED_MPFR
Expand Down
42 changes: 42 additions & 0 deletions libc/test/src/math/asinf16_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===-- Exhaustive test for asinf16 ---------------------------------------===//
//
// 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/math/asinf16.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"
#include "utils/MPFRWrapper/MPFRUtils.h"

using LlvmLibcAsinf16Test = LIBC_NAMESPACE::testing::FPTest<float16>;

namespace mpfr = LIBC_NAMESPACE::testing::mpfr;

// Range: [0, Inf]
static constexpr uint16_t POS_START = 0x0000U;
static constexpr uint16_t POS_STOP = 0x7c00U;

// Range: [-Inf, 0]
static constexpr uint16_t NEG_START = 0x8000U;
static constexpr uint16_t NEG_STOP = 0xfc00U;

TEST_F(LlvmLibcAsinf16Test, PositiveRange) {
for (uint16_t v = POS_START; v <= POS_STOP; ++v) {
float16 x = FPBits(v).get_val();

EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Asin, x,
LIBC_NAMESPACE::asinf16(x), 0.5);
}
}

TEST_F(LlvmLibcAsinf16Test, NegativeRange) {
for (uint16_t v = NEG_START; v <= NEG_STOP; ++v) {
float16 x = FPBits(v).get_val();

EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Asin, x,
LIBC_NAMESPACE::asinf16(x), 0.5);
}
}
11 changes: 11 additions & 0 deletions libc/test/src/math/smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3954,6 +3954,17 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
asinf16_test
SUITE
libc-math-smoke-tests
SRCS
asinf16_test.cpp
DEPENDS
libc.src.errno.errno
libc.src.math.asinf16
)

add_fp_unittest(
acosf_test
SUITE
Expand Down
39 changes: 39 additions & 0 deletions libc/test/src/math/smoke/asinf16_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===-- Unittests for asinf16 ---------------------------------------------===//
//
//
// 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/errno/libc_errno.h"
#include "src/math/asinf16.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"

using LlvmLibcAsinf16Test = LIBC_NAMESPACE::testing::FPTest<float16>;

TEST_F(LlvmLibcAsinf16Test, SpecialNumbers) {
LIBC_NAMESPACE::libc_errno = 0;
EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::asinf16(aNaN));
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ(zero, LIBC_NAMESPACE::asinf16(zero));
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ(neg_zero, LIBC_NAMESPACE::asinf16(neg_zero));
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::asinf16(inf));
EXPECT_MATH_ERRNO(EDOM);

EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::asinf16(neg_inf));
EXPECT_MATH_ERRNO(EDOM);

EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::asinf16(2.0f));
EXPECT_MATH_ERRNO(EDOM);

EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::asinf16(-2.0f));
EXPECT_MATH_ERRNO(EDOM);
}