diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -179,6 +179,22 @@ ]; } +def FenvAPI: PublicAPI<"fenv.h"> { + let Macros = [ + SimpleMacroDef<"FE_DIVBYZERO", "1">, + SimpleMacroDef<"FE_INEXACT", "2">, + SimpleMacroDef<"FE_INVALID", "4">, + SimpleMacroDef<"FE_OVERFLOW", "8">, + SimpleMacroDef<"FE_UNDERFLOW", "16">, + SimpleMacroDef<"FE_ALL_EXCEPT", "(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW)">, + + SimpleMacroDef<"FE_DOWNWARD", "1">, + SimpleMacroDef<"FE_TONEAREST", "2">, + SimpleMacroDef<"FE_TOWARDZERO", "4">, + SimpleMacroDef<"FE_UPWARD", "8">, + ]; +} + def StringAPI : PublicAPI<"string.h"> { let TypeDeclarations = [ SizeT, diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -21,6 +21,13 @@ # errno.h entrypoints libc.src.errno.__errno_location + # fenv.h entrypoints + libc.src.fenv.feclearexcept + libc.src.fenv.fegetround + libc.src.fenv.fesetround + libc.src.fenv.feraiseexcept + libc.src.fenv.fetestexcept + # signal.h entrypoints libc.src.signal.raise libc.src.signal.sigaction diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -25,6 +25,14 @@ .llvm_libc_common_h ) +add_gen_header( + fenv + DEF_FILE fenv.h.def + GEN_HDR fenv.h + DEPENDS + .llvm_libc_common_h +) + add_gen_header( math DEF_FILE math.h.def diff --git a/libc/include/fenv.h.def b/libc/include/fenv.h.def new file mode 100644 --- /dev/null +++ b/libc/include/fenv.h.def @@ -0,0 +1,16 @@ +//===-- C standard library header fenv.h ----------------------------------===// +// +// 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_FENV_H +#define LLVM_LIBC_FENV_H + +#include <__llvm-libc-common.h> + +%%public_api() + +#endif // LLVM_LIBC_FENV_H diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -95,7 +95,53 @@ >, ] >; - + + HeaderSpec Fenv = HeaderSpec< + "fenv.h", + [ + Macro<"FE_DIVBYZERO">, + Macro<"FE_INEXACT">, + Macro<"FE_INVALID">, + Macro<"FE_OVERFLOW">, + Macro<"FE_UNDERFLOW">, + Macro<"FE_ALL_EXCEPT">, + + Macro<"FE_DOWNWARD">, + Macro<"FE_TONEAREST">, + Macro<"FE_TOWARDZERO">, + Macro<"FE_UPWARD"> + ], + [], // Types + [], // Enumerations + [ + FunctionSpec< + "feclearexcept", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "fetestexcept", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "feraiseexcept", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "fesetround", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "fegetround", + RetValSpec, + [] + >, + ] + >; + HeaderSpec String = HeaderSpec< "string.h", [ @@ -490,6 +536,7 @@ Assert, CType, Errno, + Fenv, Math, String, StdIO, diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt --- a/libc/src/CMakeLists.txt +++ b/libc/src/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(assert) add_subdirectory(ctype) add_subdirectory(errno) +add_subdirectory(fenv) add_subdirectory(math) add_subdirectory(signal) add_subdirectory(stdio) diff --git a/libc/src/fenv/CMakeLists.txt b/libc/src/fenv/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/fenv/CMakeLists.txt @@ -0,0 +1,64 @@ +add_entrypoint_object( + fegetround + SRCS + fegetround.cpp + HDRS + fegetround.h + DEPENDS + libc.include.fenv + libc.utils.FPUtil.fputil + COMPILE_OPTIONS + -O2 +) + +add_entrypoint_object( + fesetround + SRCS + fesetround.cpp + HDRS + fesetround.h + DEPENDS + libc.include.fenv + libc.utils.FPUtil.fputil + COMPILE_OPTIONS + -O2 +) + +add_entrypoint_object( + feclearexcept + SRCS + feclearexcept.cpp + HDRS + feclearexcept.h + DEPENDS + libc.include.fenv + libc.utils.FPUtil.fputil + COMPILE_OPTIONS + -O2 +) + +add_entrypoint_object( + feraiseexcept + SRCS + feraiseexcept.cpp + HDRS + feraiseexcept.h + DEPENDS + libc.include.fenv + libc.utils.FPUtil.fputil + COMPILE_OPTIONS + -O2 +) + +add_entrypoint_object( + fetestexcept + SRCS + fetestexcept.cpp + HDRS + fetestexcept.h + DEPENDS + libc.include.fenv + libc.utils.FPUtil.fputil + COMPILE_OPTIONS + -O2 +) diff --git a/libc/src/fenv/feclearexcept.h b/libc/src/fenv/feclearexcept.h new file mode 100644 --- /dev/null +++ b/libc/src/fenv/feclearexcept.h @@ -0,0 +1,18 @@ +//===-- Implementation header for feclearexcept -----------------*- 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_FENV_FECLEAREXCEPT_H +#define LLVM_LIBC_SRC_FENV_FECLEAREXCEPT_H + +namespace __llvm_libc { + +int feclearexcept(int); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_FENV_FECLEAREXCEPT_H diff --git a/libc/src/fenv/feclearexcept.cpp b/libc/src/fenv/feclearexcept.cpp new file mode 100644 --- /dev/null +++ b/libc/src/fenv/feclearexcept.cpp @@ -0,0 +1,18 @@ +//===-- Implementation of feclearexcept 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/FEnv.h" + +namespace __llvm_libc { + +int LLVM_LIBC_ENTRYPOINT(feclearexcept)(int e) { + return fputil::clearExcept(e); +} + +} // namespace __llvm_libc diff --git a/libc/src/fenv/fegetround.h b/libc/src/fenv/fegetround.h new file mode 100644 --- /dev/null +++ b/libc/src/fenv/fegetround.h @@ -0,0 +1,18 @@ +//===-- Implementation header for fegetround --------------------*- 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_FENV_FEGETROUND_H +#define LLVM_LIBC_SRC_FENV_FEGETROUND_H + +namespace __llvm_libc { + +int fegetround(); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_FENV_FEGETROUND_H diff --git a/libc/src/fenv/fegetround.cpp b/libc/src/fenv/fegetround.cpp new file mode 100644 --- /dev/null +++ b/libc/src/fenv/fegetround.cpp @@ -0,0 +1,16 @@ +//===-- Implementation of fegetround 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/FEnv.h" + +namespace __llvm_libc { + +int LLVM_LIBC_ENTRYPOINT(fegetround)() { return fputil::getRound(); } + +} // namespace __llvm_libc diff --git a/libc/src/fenv/feraiseexcept.h b/libc/src/fenv/feraiseexcept.h new file mode 100644 --- /dev/null +++ b/libc/src/fenv/feraiseexcept.h @@ -0,0 +1,18 @@ +//===-- Implementation header for feraiseexcept -----------------*- 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_FENV_FERAISEEXCEPT_H +#define LLVM_LIBC_SRC_FENV_FERAISEEXCEPT_H + +namespace __llvm_libc { + +int feraiseexcept(int); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_FENV_FERAISEEXCEPT_H diff --git a/libc/src/fenv/feraiseexcept.cpp b/libc/src/fenv/feraiseexcept.cpp new file mode 100644 --- /dev/null +++ b/libc/src/fenv/feraiseexcept.cpp @@ -0,0 +1,18 @@ +//===-- Implementation of feraiseexcept 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/FEnv.h" + +namespace __llvm_libc { + +int LLVM_LIBC_ENTRYPOINT(feraiseexcept)(int e) { + return fputil::raiseExcept(e); +} + +} // namespace __llvm_libc diff --git a/libc/src/fenv/fesetround.h b/libc/src/fenv/fesetround.h new file mode 100644 --- /dev/null +++ b/libc/src/fenv/fesetround.h @@ -0,0 +1,18 @@ +//===-- Implementation header for fesetround --------------------*- 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_FENV_FESETROUND_H +#define LLVM_LIBC_SRC_FENV_FESETROUND_H + +namespace __llvm_libc { + +int fesetround(int); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_FENV_FESETROUND_H diff --git a/libc/src/fenv/fesetround.cpp b/libc/src/fenv/fesetround.cpp new file mode 100644 --- /dev/null +++ b/libc/src/fenv/fesetround.cpp @@ -0,0 +1,16 @@ +//===-- Implementation of fesetround 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/FEnv.h" + +namespace __llvm_libc { + +int LLVM_LIBC_ENTRYPOINT(fesetround)(int m) { return fputil::setRound(m); } + +} // namespace __llvm_libc diff --git a/libc/src/fenv/fetestexcept.h b/libc/src/fenv/fetestexcept.h new file mode 100644 --- /dev/null +++ b/libc/src/fenv/fetestexcept.h @@ -0,0 +1,18 @@ +//===-- Implementation header for fetestexpect ------------------*- 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_FENV_FETESTEXCEPT_H +#define LLVM_LIBC_SRC_FENV_FETESTEXCEPT_H + +namespace __llvm_libc { + +int fetestexcept(int); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_FENV_FETESTEXCEPT_H diff --git a/libc/src/fenv/fetestexcept.cpp b/libc/src/fenv/fetestexcept.cpp new file mode 100644 --- /dev/null +++ b/libc/src/fenv/fetestexcept.cpp @@ -0,0 +1,16 @@ +//===-- Implementation of fetestexcept 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/FEnv.h" + +namespace __llvm_libc { + +int LLVM_LIBC_ENTRYPOINT(fetestexcept)(int e) { return fputil::testExcept(e); } + +} // namespace __llvm_libc diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(assert) add_subdirectory(ctype) add_subdirectory(errno) +add_subdirectory(fenv) add_subdirectory(math) add_subdirectory(signal) add_subdirectory(stdio) diff --git a/libc/test/src/fenv/CMakeLists.txt b/libc/test/src/fenv/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/src/fenv/CMakeLists.txt @@ -0,0 +1,24 @@ +add_libc_testsuite(libc_fenv_unittests) + +add_libc_unittest( + rounding_mode_test + SUITE + libc_fenv_unittests + SRCS + rounding_mode_test.cpp + DEPENDS + libc.src.fenv.fegetround + libc.src.fenv.fesetround +) + +add_libc_unittest( + exception_status_test + SUITE + libc_fenv_unittests + SRCS + exception_status_test.cpp + DEPENDS + libc.src.fenv.feclearexcept + libc.src.fenv.feraiseexcept + libc.src.fenv.fetestexcept +) diff --git a/libc/test/src/fenv/exception_status_test.cpp b/libc/test/src/fenv/exception_status_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/fenv/exception_status_test.cpp @@ -0,0 +1,108 @@ +//===-- Unittests for feclearexcept, feraiseexcept and fetestexpect -------===// +// +// 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/fenv/feclearexcept.h" +#include "src/fenv/feraiseexcept.h" +#include "src/fenv/fetestexcept.h" + +#include "utils/UnitTest/Test.h" + +#include + +TEST(ExceptionStatusTest, RaiseAndTest) { + int excepts[] = {FE_DIVBYZERO, FE_INVALID, FE_INEXACT, FE_OVERFLOW, + FE_UNDERFLOW}; + for (int e : excepts) { + int r = __llvm_libc::feraiseexcept(e); + EXPECT_EQ(r, 0); + int s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, e); + + r = __llvm_libc::feclearexcept(e); + EXPECT_EQ(r, 0); + s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, 0); + } + + for (int e1 : excepts) { + for (int e2 : excepts) { + int e = e1 | e2; + int r = __llvm_libc::feraiseexcept(e); + EXPECT_EQ(r, 0); + int s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, e); + + r = __llvm_libc::feclearexcept(e); + EXPECT_EQ(r, 0); + s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, 0); + } + } + + for (int e1 : excepts) { + for (int e2 : excepts) { + for (int e3 : excepts) { + int e = e1 | e2 | e3; + int r = __llvm_libc::feraiseexcept(e); + EXPECT_EQ(r, 0); + int s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, e); + + r = __llvm_libc::feclearexcept(e); + EXPECT_EQ(r, 0); + s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, 0); + } + } + } + + for (int e1 : excepts) { + for (int e2 : excepts) { + for (int e3 : excepts) { + for (int e4 : excepts) { + int e = e1 | e2 | e3 | e4; + int r = __llvm_libc::feraiseexcept(e); + EXPECT_EQ(r, 0); + int s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, e); + + r = __llvm_libc::feclearexcept(e); + EXPECT_EQ(r, 0); + s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, 0); + } + } + } + } + + for (int e1 : excepts) { + for (int e2 : excepts) { + for (int e3 : excepts) { + for (int e4 : excepts) { + for (int e5 : excepts) { + int e = e1 | e2 | e3 | e4 | e5; + int r = __llvm_libc::feraiseexcept(e); + EXPECT_EQ(r, 0); + int s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, e); + + r = __llvm_libc::feclearexcept(e); + EXPECT_EQ(r, 0); + s = __llvm_libc::fetestexcept(e); + EXPECT_EQ(s, 0); + } + } + } + } + } + + int r = __llvm_libc::feraiseexcept(FE_ALL_EXCEPT); + EXPECT_EQ(r, 0); + int s = __llvm_libc::fetestexcept(FE_ALL_EXCEPT); + EXPECT_EQ(s, FE_ALL_EXCEPT); +} diff --git a/libc/test/src/fenv/rounding_mode_test.cpp b/libc/test/src/fenv/rounding_mode_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/fenv/rounding_mode_test.cpp @@ -0,0 +1,36 @@ +//===-- Unittests for fegetround and fesetround ---------------------------===// +// +// 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/fenv/fegetround.h" +#include "src/fenv/fesetround.h" + +#include "utils/UnitTest/Test.h" + +#include + +TEST(RoundingModeTest, SetAndGet) { + int s = __llvm_libc::fesetround(FE_TONEAREST); + EXPECT_EQ(s, 0); + int rm = __llvm_libc::fegetround(); + EXPECT_EQ(rm, FE_TONEAREST); + + s = __llvm_libc::fesetround(FE_UPWARD); + EXPECT_EQ(s, 0); + rm = __llvm_libc::fegetround(); + EXPECT_EQ(rm, FE_UPWARD); + + s = __llvm_libc::fesetround(FE_DOWNWARD); + EXPECT_EQ(s, 0); + rm = __llvm_libc::fegetround(); + EXPECT_EQ(rm, FE_DOWNWARD); + + s = __llvm_libc::fesetround(FE_TOWARDZERO); + EXPECT_EQ(s, 0); + rm = __llvm_libc::fegetround(); + EXPECT_EQ(rm, FE_TOWARDZERO); +} diff --git a/libc/utils/FPUtil/CMakeLists.txt b/libc/utils/FPUtil/CMakeLists.txt --- a/libc/utils/FPUtil/CMakeLists.txt +++ b/libc/utils/FPUtil/CMakeLists.txt @@ -4,14 +4,22 @@ set(LONG_DOUBLE_HDR) endif() +if(EXISTS ${LIBC_TARGET_MACHINE}) + set(FENV_IMPL ${LIBC_TARGET_MACHINE}/FEnv.h) +else() + set(FENV_IMPL DummyFEnv.h) +endif() + add_header_library( fputil HDRS ${LONG_DOUBLE_HDR} + ${FENV_IMPL} BasicOperations.h BitPatterns.h ClassificationFunctions.h DivisionAndRemainderOperations.h + FEnv.h FloatOperations.h FloatProperties.h FPBits.h diff --git a/libc/utils/FPUtil/DummyFEnv.h b/libc/utils/FPUtil/DummyFEnv.h new file mode 100644 --- /dev/null +++ b/libc/utils/FPUtil/DummyFEnv.h @@ -0,0 +1,32 @@ +//===-- Dummy floating point environment manipulation functins --*- 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_DUMMY_FENV_H +#define LLVM_LIBC_UTILS_FPUTIL_DUMMY_FENV_H + +#include + +namespace __llvm_libc { +namespace fputil { + +// All dummy functions silently succeed. + +int clearExcept(int) { return 0; } + +int testExcept(int) { return 0; } + +int raiseExcept(int) { return 0; } + +int getRound() { return FE_TONEAREST; } + +int setRound(int) { return 0; } + +} // namespace fputil +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_FPUTIL_DUMMY_FENV_H diff --git a/libc/utils/FPUtil/FEnv.h b/libc/utils/FPUtil/FEnv.h new file mode 100644 --- /dev/null +++ b/libc/utils/FPUtil/FEnv.h @@ -0,0 +1,18 @@ +//===-- Utilities for manipulating floating point environment ---*- 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_FENV_H +#define LLVM_LIBC_UTILS_FPUTIL_FENV_H + +#ifdef __x86_64__ +#include "x86_64/FEnv.h" +#else +#include "DummyFEnv.h" +#endif + +#endif // LLVM_LIBC_UTILS_FPUTIL_FP_BITS_H diff --git a/libc/utils/FPUtil/x86_64/FEnv.h b/libc/utils/FPUtil/x86_64/FEnv.h new file mode 100644 --- /dev/null +++ b/libc/utils/FPUtil/x86_64/FEnv.h @@ -0,0 +1,184 @@ +//===-- x86_64 floating point env manipulation functions --------*- 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_X86_64_FENV_H +#define LLVM_LIBC_UTILS_FPUTIL_X86_64_FENV_H + +#include +#include +#include + +namespace __llvm_libc { +namespace fputil { + +namespace internal { + +// Normally, one should be able to define FE_* macros to the exact rounding mode +// encodings. However, since we want LLVM libc to be compiled against headers +// from other libcs, we cannot assume that FE_* macros are always defined in +// such a manner. So, we will define enums corresponding to the x86_64 bit +// encodings. The implementations can map from FE_* to the corresponding enum +// values. + +// The rounding control values in the x87 control register and the MXCSR +// register have the same 2-bit enoding but have different bit positions. +// See below for the bit positions. +struct RoundingControlValue { + static constexpr uint16_t ToNearest = 0x0; + static constexpr uint16_t Downward = 0x1; + static constexpr uint16_t Upward = 0x2; + static constexpr uint16_t TowardZero = 0x3; +}; + +static constexpr uint16_t X87RoundingControlBitPosition = 10; +static constexpr uint16_t MXCSRRoundingControlBitPosition = 13; + +// The exception flags in the x87 status register and the MXCSR have the same +// encoding as well as the same bit positions. +struct ExceptionFlags { + static constexpr uint16_t Invalid = 0x1; + static constexpr uint16_t Denormal = 0x2; // This flag is not used + static constexpr uint16_t DivByZero = 0x4; + static constexpr uint16_t Overflow = 0x8; + static constexpr uint16_t Underflow = 0x10; + static constexpr uint16_t Inexact = 0x20; +}; + +// Exception flags are individual bits in the corresponding registers. +// So, we just OR the bit values to get the full set of exceptions. +static inline uint16_t getStatusValueForExcept(int excepts) { + // We will make use of the fact that exception control bits are single + // bit flags in the control registers. + return (excepts & FE_INVALID ? ExceptionFlags::Invalid : 0) | + (excepts & FE_DIVBYZERO ? ExceptionFlags::DivByZero : 0) | + (excepts & FE_OVERFLOW ? ExceptionFlags::Overflow : 0) | + (excepts & FE_UNDERFLOW ? ExceptionFlags::Underflow : 0) | + (excepts & FE_INEXACT ? ExceptionFlags::Inexact : 0); +} + +static inline int exceptionStatusToMacro(uint16_t status) { + return (status & ExceptionFlags::Invalid ? FE_INVALID : 0) | + (status & ExceptionFlags::DivByZero ? FE_DIVBYZERO : 0) | + (status & ExceptionFlags::Overflow ? FE_OVERFLOW : 0) | + (status & ExceptionFlags::Underflow ? FE_UNDERFLOW : 0) | + (status & ExceptionFlags::Inexact ? FE_INEXACT : 0); +} + +static inline uint16_t getX87ControlWord() { + uint16_t w; + __asm__ __volatile__("fnstcw %0" : "=m"(w)::); + return w; +} + +static inline void writeX87ControlWord(uint16_t w) { + __asm__ __volatile__("fldcw %0" : : "m"(w) :); +} + +static inline uint16_t getX87StatusWord() { + uint16_t w; + __asm__ __volatile__("fnstsw %0" : "=m"(w)::); + return w; +} + +static inline void clearX87Exceptions() { + __asm__ __volatile__("fnclex" : : :); +} + +} // namespace internal + +static inline int clearExcept(int excepts) { + // An instruction to write to x87 status word ins't available. So, we + // just clear all of the x87 exceptions. + // TODO: One can potentially use fegetenv/fesetenv to clear only the + // listed exceptions in the x87 status word. We can do this if it is + // really required. + internal::clearX87Exceptions(); + + uint32_t mxcsr = _mm_getcsr(); + mxcsr &= ~internal::getStatusValueForExcept(excepts); + _mm_setcsr(mxcsr); + return 0; +} + +static inline int testExcept(int excepts) { + uint16_t statusValue = internal::getStatusValueForExcept(excepts); + // Check both x87 status word and MXCSR. + return internal::exceptionStatusToMacro( + (statusValue & internal::getX87StatusWord()) | + (statusValue & _mm_getcsr())); +} + +static inline int raiseExcept(int excepts) { + // It is enough to set the exception flags in MXCSR. + // TODO: Investigate if each exception has to be raised one at a time + // followed with an fwait instruction before writing the flag for the + // next exception. + uint16_t statusValue = internal::getStatusValueForExcept(excepts); + uint32_t sse = _mm_getcsr(); + sse = sse | statusValue; + _mm_setcsr(sse); + return 0; +} + +static inline int getRound() { + uint16_t bitValue = + (_mm_getcsr() >> internal::MXCSRRoundingControlBitPosition) & 0x3; + switch (bitValue) { + case internal::RoundingControlValue::ToNearest: + return FE_TONEAREST; + case internal::RoundingControlValue::Downward: + return FE_DOWNWARD; + case internal::RoundingControlValue::Upward: + return FE_UPWARD; + case internal::RoundingControlValue::TowardZero: + return FE_TOWARDZERO; + default: + return -1; // Error value. + } +} + +static inline int setRound(int mode) { + uint16_t bitValue; + switch (mode) { + case FE_TONEAREST: + bitValue = internal::RoundingControlValue::ToNearest; + break; + case FE_DOWNWARD: + bitValue = internal::RoundingControlValue::Downward; + break; + case FE_UPWARD: + bitValue = internal::RoundingControlValue::Upward; + break; + case FE_TOWARDZERO: + bitValue = internal::RoundingControlValue::TowardZero; + break; + default: + return 1; // To indicate failure + } + + uint16_t x87Value = bitValue << internal::X87RoundingControlBitPosition; + uint16_t x87Control = internal::getX87ControlWord(); + x87Control = + (x87Control & ~(0x3 << internal::X87RoundingControlBitPosition)) | + x87Value; + internal::writeX87ControlWord(x87Control); + + uint32_t mxcsrValue = bitValue << internal::MXCSRRoundingControlBitPosition; + uint32_t mxcsrControl = _mm_getcsr(); + mxcsrControl = + (mxcsrControl & ~(0x3 << internal::MXCSRRoundingControlBitPosition)) | + mxcsrValue; + _mm_setcsr(mxcsrControl); + + return 0; +} + +} // namespace fputil +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_FPUTIL_X86_64_FENV_H