[libc] Add x86_64 implementations of double precision cos, sin and tan.
authorSiva Chandra Reddy <sivachandra@google.com>
Wed, 12 May 2021 23:05:13 +0000 (23:05 +0000)
committerSiva Chandra Reddy <sivachandra@google.com>
Thu, 13 May 2021 19:02:00 +0000 (19:02 +0000)
The implementations use the x86_64 FPU instructions. These instructions
are extremely slow compared to a polynomial based software
implementation. Also, their accuracy falls drastically once the input
goes beyond 2PI. To improve both the speed and accuracy, we will be
taking the following approach going forward:
1. As a follow up to this CL, we will implement a range reduction algorithm
which will expand the accuracy to the entire double precision range.
2. After that, we will replace the HW instructions with a polynomial
implementation to improve the run time.

After step 2, the implementations will be accurate, performant and target
architecture independent.

Reviewed By: lntue

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

16 files changed:
libc/config/linux/x86_64/entrypoints.txt
libc/spec/stdc.td
libc/src/math/CMakeLists.txt
libc/src/math/cos.h [new file with mode: 0644]
libc/src/math/sin.h [new file with mode: 0644]
libc/src/math/tan.h [new file with mode: 0644]
libc/src/math/x86_64/CMakeLists.txt [new file with mode: 0644]
libc/src/math/x86_64/cos.cpp [new file with mode: 0644]
libc/src/math/x86_64/sin.cpp [new file with mode: 0644]
libc/src/math/x86_64/tan.cpp [new file with mode: 0644]
libc/test/src/math/CMakeLists.txt
libc/test/src/math/cos_test.cpp [new file with mode: 0644]
libc/test/src/math/sin_test.cpp [new file with mode: 0644]
libc/test/src/math/tan_test.cpp [new file with mode: 0644]
libc/utils/MPFRWrapper/MPFRUtils.cpp
libc/utils/MPFRWrapper/MPFRUtils.h

index 764059d6921279a550ff6750c37acad068b21d8d..ff7a298e7579a08881ef3f07b86fa2ee95503c55 100644 (file)
@@ -65,6 +65,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.ceil
     libc.src.math.ceilf
     libc.src.math.ceill
+    libc.src.math.cos
     libc.src.math.cosf
     libc.src.math.expf
     libc.src.math.exp2f
@@ -136,11 +137,13 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.round
     libc.src.math.roundf
     libc.src.math.roundl
+    libc.src.math.sin
     libc.src.math.sincosf
     libc.src.math.sinf
     libc.src.math.sqrt
     libc.src.math.sqrtf
     libc.src.math.sqrtl
+    libc.src.math.tan
     libc.src.math.trunc
     libc.src.math.truncf
     libc.src.math.truncl
index b50d2ed44d73b02769384b27d966195d673f0ab8..c2a90450dbd2a4b86c3c5b020ed9f1fed8523466 100644 (file)
@@ -388,8 +388,11 @@ def StdC : StandardSpec<"stdc"> {
           FunctionSpec<"modff", RetValSpec<FloatType>, [ArgSpec<FloatType>, ArgSpec<FloatPtr>]>,
           FunctionSpec<"modfl", RetValSpec<LongDoubleType>, [ArgSpec<LongDoubleType>, ArgSpec<LongDoublePtr>]>,
 
+          FunctionSpec<"cos", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
           FunctionSpec<"cosf", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
+          FunctionSpec<"sin", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
           FunctionSpec<"sinf", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
+          FunctionSpec<"tan", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
 
           FunctionSpec<"expf", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
           FunctionSpec<"exp2f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
index 50fc4a4325e9f4e1b76c25f1d2b4bb639fd186f0..e3bdb30503ca8bcb09cc85c598ea71ab97fde6c9 100644 (file)
@@ -64,6 +64,7 @@ add_math_entrypoint_object(copysign)
 add_math_entrypoint_object(copysignf)
 add_math_entrypoint_object(copysignl)
 
+add_math_entrypoint_object(cos)
 add_math_entrypoint_object(cosf)
 
 add_math_entrypoint_object(expf)
@@ -155,12 +156,15 @@ add_math_entrypoint_object(roundl)
 
 add_math_entrypoint_object(sincosf)
 
+add_math_entrypoint_object(sin)
 add_math_entrypoint_object(sinf)
 
 add_math_entrypoint_object(sqrt)
 add_math_entrypoint_object(sqrtf)
 add_math_entrypoint_object(sqrtl)
 
+add_math_entrypoint_object(tan)
+
 add_math_entrypoint_object(trunc)
 add_math_entrypoint_object(truncf)
 add_math_entrypoint_object(truncl)
diff --git a/libc/src/math/cos.h b/libc/src/math/cos.h
new file mode 100644 (file)
index 0000000..aca1d6d
--- /dev/null
@@ -0,0 +1,18 @@
+//===-- Implementation header for cos ---------------------------*- 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_COS_H
+#define LLVM_LIBC_SRC_MATH_COS_H
+
+namespace __llvm_libc {
+
+double cos(double x);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_MATH_COS_H
diff --git a/libc/src/math/sin.h b/libc/src/math/sin.h
new file mode 100644 (file)
index 0000000..f3919c4
--- /dev/null
@@ -0,0 +1,18 @@
+//===-- Implementation header for sin ---------------------------*- 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_SIN_H
+#define LLVM_LIBC_SRC_MATH_SIN_H
+
+namespace __llvm_libc {
+
+double sin(double x);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_MATH_SIN_H
diff --git a/libc/src/math/tan.h b/libc/src/math/tan.h
new file mode 100644 (file)
index 0000000..05366db
--- /dev/null
@@ -0,0 +1,18 @@
+//===-- Implementation header for tan ---------------------------*- 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_TAN_H
+#define LLVM_LIBC_SRC_MATH_TAN_H
+
+namespace __llvm_libc {
+
+double tan(double x);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_MATH_TAN_H
diff --git a/libc/src/math/x86_64/CMakeLists.txt b/libc/src/math/x86_64/CMakeLists.txt
new file mode 100644 (file)
index 0000000..cd129e3
--- /dev/null
@@ -0,0 +1,29 @@
+add_entrypoint_object(
+  cos
+  SRCS
+    cos.cpp
+  HDRS
+    ../cos.h
+  COMPILE_OPTIONS
+    -O2
+)
+
+add_entrypoint_object(
+  sin
+  SRCS
+    sin.cpp
+  HDRS
+    ../sin.h
+  COMPILE_OPTIONS
+    -O2
+)
+
+add_entrypoint_object(
+  tan
+  SRCS
+    tan.cpp
+  HDRS
+    ../tan.h
+  COMPILE_OPTIONS
+    -O2
+)
diff --git a/libc/src/math/x86_64/cos.cpp b/libc/src/math/x86_64/cos.cpp
new file mode 100644 (file)
index 0000000..9e7e722
--- /dev/null
@@ -0,0 +1,20 @@
+//===-- Implementation of the cos function for x86_64 ---------------------===//
+//
+// 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/cos.h"
+#include "src/__support/common.h"
+
+namespace __llvm_libc {
+
+LLVM_LIBC_FUNCTION(double, cos, (double x)) {
+  double result;
+  __asm__ __volatile__("fcos" : "=t"(result) : "f"(x));
+  return result;
+}
+
+} // namespace __llvm_libc
diff --git a/libc/src/math/x86_64/sin.cpp b/libc/src/math/x86_64/sin.cpp
new file mode 100644 (file)
index 0000000..22774f2
--- /dev/null
@@ -0,0 +1,20 @@
+//===-- Implementation of the sin function for x86_64 ---------------------===//
+//
+// 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/sin.h"
+#include "src/__support/common.h"
+
+namespace __llvm_libc {
+
+LLVM_LIBC_FUNCTION(double, sin, (double x)) {
+  double result;
+  __asm__ __volatile__("fsin" : "=t"(result) : "f"(x));
+  return result;
+}
+
+} // namespace __llvm_libc
diff --git a/libc/src/math/x86_64/tan.cpp b/libc/src/math/x86_64/tan.cpp
new file mode 100644 (file)
index 0000000..9146473
--- /dev/null
@@ -0,0 +1,24 @@
+//===-- Implementation of the tan function for x86_64 ---------------------===//
+//
+// 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/tan.h"
+#include "src/__support/common.h"
+
+namespace __llvm_libc {
+
+LLVM_LIBC_FUNCTION(double, tan, (double x)) {
+  double result;
+  double one;
+  // The fptan instruction pushes the number 1 on to the FP stack after
+  // computing tan. So, we read out the one before popping the actual result.
+  __asm__ __volatile__("fptan" : "=t"(one) : "f"(x));
+  __asm__ __volatile__("fstpl %0" : "=m"(result));
+  return result;
+}
+
+} // namespace __llvm_libc
index 22ff6d612c9ed34b2889ce1ab0682501c704a17d..49b529048c31e117255c80eefc71e973d60b4553 100644 (file)
@@ -43,6 +43,18 @@ add_fp_unittest(
     libc.utils.FPUtil.fputil
 )
 
+add_fp_unittest(
+  cos_test
+  NEED_MPFR
+  SUITE
+    libc_math_unittests
+  SRCS
+    cos_test.cpp
+  DEPENDS
+    libc.src.math.cos
+    libc.utils.FPUtil.fputil
+)
+
 add_fp_unittest(
   sinf_test
   NEED_MPFR
@@ -59,6 +71,18 @@ add_fp_unittest(
     libc.utils.FPUtil.fputil
 )
 
+add_fp_unittest(
+  sin_test
+  NEED_MPFR
+  SUITE
+    libc_math_unittests
+  SRCS
+    sin_test.cpp
+  DEPENDS
+    libc.src.math.sin
+    libc.utils.FPUtil.fputil
+)
+
 add_fp_unittest(
   sincosf_test
   NEED_MPFR
@@ -1132,6 +1156,18 @@ add_fp_unittest(
     libc.utils.FPUtil.fputil
 )
 
+add_fp_unittest(
+  tan_test
+  NEED_MPFR
+  SUITE
+    libc_math_unittests
+  SRCS
+    tan_test.cpp
+  DEPENDS
+    libc.src.math.tan
+    libc.utils.FPUtil.fputil
+)
+
 add_subdirectory(generic)
 add_subdirectory(exhaustive)
 add_subdirectory(differential_testing)
diff --git a/libc/test/src/math/cos_test.cpp b/libc/test/src/math/cos_test.cpp
new file mode 100644 (file)
index 0000000..36bb905
--- /dev/null
@@ -0,0 +1,32 @@
+//===-- Unittests for cos -------------------------------------------------===//
+//
+// 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/cos.h"
+#include "utils/FPUtil/TestHelpers.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+#include "utils/UnitTest/Test.h"
+
+#include <math.h>
+
+namespace mpfr = __llvm_libc::testing::mpfr;
+
+DECLARE_SPECIAL_CONSTANTS(double)
+
+TEST(LlvmLibccosTest, Range) {
+  static constexpr double _2pi = 6.283185307179586;
+  constexpr UIntType count = 10000000;
+  constexpr UIntType step = UIntType(-1) / count;
+  for (UIntType i = 0, v = 0; i <= count; ++i, v += step) {
+    double x = double(FPBits(v));
+    // TODO: Expand the range of testing after range reduction is implemented.
+    if (isnan(x) || isinf(x) || x > _2pi || x < -_2pi)
+      continue;
+
+    ASSERT_MPFR_MATCH(mpfr::Operation::Cos, x, __llvm_libc::cos(x), 1.0);
+  }
+}
diff --git a/libc/test/src/math/sin_test.cpp b/libc/test/src/math/sin_test.cpp
new file mode 100644 (file)
index 0000000..dd3fcb5
--- /dev/null
@@ -0,0 +1,32 @@
+//===-- Unittests for sin -------------------------------------------------===//
+//
+// 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/sin.h"
+#include "utils/FPUtil/TestHelpers.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+#include "utils/UnitTest/Test.h"
+
+#include <math.h>
+
+namespace mpfr = __llvm_libc::testing::mpfr;
+
+DECLARE_SPECIAL_CONSTANTS(double)
+
+TEST(LlvmLibcSinTest, Range) {
+  static constexpr double _2pi = 6.283185307179586;
+  constexpr UIntType count = 10000000;
+  constexpr UIntType step = UIntType(-1) / count;
+  for (UIntType i = 0, v = 0; i <= count; ++i, v += step) {
+    double x = double(FPBits(v));
+    // TODO: Expand the range of testing after range reduction is implemented.
+    if (isnan(x) || isinf(x) || x > _2pi || x < -_2pi)
+      continue;
+
+    ASSERT_MPFR_MATCH(mpfr::Operation::Sin, x, __llvm_libc::sin(x), 1.0);
+  }
+}
diff --git a/libc/test/src/math/tan_test.cpp b/libc/test/src/math/tan_test.cpp
new file mode 100644 (file)
index 0000000..fdd8962
--- /dev/null
@@ -0,0 +1,32 @@
+//===-- Unittests for tan -------------------------------------------------===//
+//
+// 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/tan.h"
+#include "utils/FPUtil/TestHelpers.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+#include "utils/UnitTest/Test.h"
+
+#include <math.h>
+
+namespace mpfr = __llvm_libc::testing::mpfr;
+
+DECLARE_SPECIAL_CONSTANTS(double)
+
+TEST(LlvmLibctanTest, Range) {
+  static constexpr double _2pi = 6.283185307179586;
+  constexpr UIntType count = 10000000;
+  constexpr UIntType step = UIntType(-1) / count;
+  for (UIntType i = 0, v = 0; i <= count; ++i, v += step) {
+    double x = double(FPBits(v));
+    // TODO: Expand the range of testing after range reduction is implemented.
+    if (isnan(x) || isinf(x) || x > _2pi || x < -_2pi)
+      continue;
+
+    ASSERT_MPFR_MATCH(mpfr::Operation::Tan, x, __llvm_libc::tan(x), 1.0);
+  }
+}
index 7faf75ccde593ea36ffd0f615acbfd8e5498ebd1..7eb2e4f5243f86fe2ae73e82da79b9caecfa12ae 100644 (file)
@@ -207,6 +207,12 @@ public:
     return result;
   }
 
+  MPFRNumber tan() const {
+    MPFRNumber result;
+    mpfr_tan(result.value, value, MPFR_RNDN);
+    return result;
+  }
+
   MPFRNumber trunc() const {
     MPFRNumber result;
     mpfr_trunc(result.value, value);
@@ -311,6 +317,8 @@ unaryOperation(Operation op, InputType input) {
     return mpfrInput.sin();
   case Operation::Sqrt:
     return mpfrInput.sqrt();
+  case Operation::Tan:
+    return mpfrInput.tan();
   case Operation::Trunc:
     return mpfrInput.trunc();
   default:
index 17f8a09e80baa4c17218b5cdf30808d50feed689..604a20d64c99a57c38cb23386013d2a080ff54db 100644 (file)
@@ -32,6 +32,7 @@ enum class Operation : int {
   Round,
   Sin,
   Sqrt,
+  Tan,
   Trunc,
   EndUnaryOperationsSingleOutput,