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 @@ -230,6 +230,8 @@ SimpleMacroDef<"FE_TONEAREST", "2">, SimpleMacroDef<"FE_TOWARDZERO", "4">, SimpleMacroDef<"FE_UPWARD", "8">, + + SimpleMacroDef<"FE_DFL_ENV", "((fenv_t *)-1)">, ]; let TypeDeclarations = [ FEnvT, diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -120,7 +120,9 @@ Macro<"FE_DOWNWARD">, Macro<"FE_TONEAREST">, Macro<"FE_TOWARDZERO">, - Macro<"FE_UPWARD"> + Macro<"FE_UPWARD">, + + Macro<"FE_DFL_ENV"> ], [ NamedType<"fenv_t">, diff --git a/libc/src/__support/FPUtil/aarch64/FEnvImpl.h b/libc/src/__support/FPUtil/aarch64/FEnvImpl.h --- a/libc/src/__support/FPUtil/aarch64/FEnvImpl.h +++ b/libc/src/__support/FPUtil/aarch64/FEnvImpl.h @@ -230,6 +230,13 @@ } static inline int setEnv(const fenv_t *envp) { + if (envp == FE_DFL_ENV) { + // Default status and control words bits are all zeros so we just + // write zeros. + FEnv::writeStatusWord(0); + FEnv::writeControlWord(0); + return 0; + } const FEnv::FPState *state = reinterpret_cast(envp); FEnv::writeControlWord(state->ControlWord); FEnv::writeStatusWord(state->StatusWord); diff --git a/libc/src/__support/FPUtil/x86_64/FEnvImpl.h b/libc/src/__support/FPUtil/x86_64/FEnvImpl.h --- a/libc/src/__support/FPUtil/x86_64/FEnvImpl.h +++ b/libc/src/__support/FPUtil/x86_64/FEnvImpl.h @@ -381,10 +381,58 @@ } static inline int setEnv(const fenv_t *envp) { - const internal::FPState *state = + // envp contains everything including pieces like the current + // top of FPU stack. We cannot arbitrarily change them. So, we first + // read the current status and update only those pieces which are + // not disruptive. + internal::X87StateDescriptor x87Status; + internal::getX87StateDescriptor(x87Status); + + if (envp == FE_DFL_ENV) { + // Reset the exception flags in the status word. + x87Status.StatusWord &= ~uint16_t(0x3F); + // Reset other non-sensitive parts of the status word. + for (int i = 0; i < 5; i++) + x87Status._[i] = 0; + // In the control word, we do the following: + // 1. Mask all exceptions + // 2. Set rounding mode to round-to-nearest + // 3. Set the internal precision to double extended precision. + x87Status.ControlWord |= uint16_t(0x3F); // Mask all exceptions. + x87Status.ControlWord &= ~(uint16_t(0x3) << 10); // Round to nearest. + x87Status.ControlWord |= (uint16_t(0x3) << 8); // Extended precision. + internal::writeX87StateDescriptor(x87Status); + + // We take the exact same approach MXCSR register as well. + // MXCSR has two additional fields, "flush-to-zero" and + // "denormals-are-zero". We reset those bits. Also, MXCSR does not + // have a field which controls the precision of internal operations. + uint32_t mxcsr = internal::getMXCSR(); + mxcsr &= ~uint16_t(0x3F); // Clear exception flags. + mxcsr &= ~(uint16_t(0x1) << 6); // Reset denormals-are-zero + mxcsr |= (uint16_t(0x3F) << 7); // Mask exceptions + mxcsr &= ~(uint16_t(0x3) << 13); // Round to nearest. + mxcsr &= ~(uint16_t(0x1) << 15); // Reset flush-to-zero + internal::writeMXCSR(mxcsr); + + return 0; + } + + const internal::FPState *fpstate = reinterpret_cast(envp); - internal::writeX87StateDescriptor(state->X87Status); - internal::writeMXCSR(state->MXCSR); + + // Copy the exception status flags from envp. + x87Status.StatusWord &= ~uint16_t(0x3F); + x87Status.StatusWord |= (fpstate->X87Status.StatusWord & 0x3F); + // Copy other non-sensitive parts of the status word. + for (int i = 0; i < 5; i++) + x87Status._[i] = fpstate->X87Status._[i]; + // We can set the x87 control word as is as there no sensitive bits. + x87Status.ControlWord = fpstate->X87Status.ControlWord; + internal::writeX87StateDescriptor(x87Status); + + // We can write the MXCSR state as is as there are no sensitive bits. + internal::writeMXCSR(fpstate->MXCSR); return 0; } #endif diff --git a/libc/test/src/fenv/CMakeLists.txt b/libc/test/src/fenv/CMakeLists.txt --- a/libc/test/src/fenv/CMakeLists.txt +++ b/libc/test/src/fenv/CMakeLists.txt @@ -32,7 +32,9 @@ getenv_and_setenv_test.cpp DEPENDS libc.src.fenv.fegetenv + libc.src.fenv.fegetround libc.src.fenv.fesetenv + libc.src.fenv.fesetround libc.src.__support.FPUtil.fputil ) diff --git a/libc/test/src/fenv/getenv_and_setenv_test.cpp b/libc/test/src/fenv/getenv_and_setenv_test.cpp --- a/libc/test/src/fenv/getenv_and_setenv_test.cpp +++ b/libc/test/src/fenv/getenv_and_setenv_test.cpp @@ -7,7 +7,9 @@ //===----------------------------------------------------------------------===// #include "src/fenv/fegetenv.h" +#include "src/fenv/fegetround.h" #include "src/fenv/fesetenv.h" +#include "src/fenv/fesetround.h" #include "src/__support/FPUtil/FEnvUtils.h" #include "utils/UnitTest/Test.h" @@ -37,3 +39,34 @@ ASSERT_EQ(__llvm_libc::fputil::testExcept(FE_ALL_EXCEPT) & e, 0); } } + +TEST(LlvmLibcFenvTest, Set_FE_DFL_ENV) { + // We will disable all exceptions to prevent invocation of the exception + // handler. + __llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT); + + int excepts[] = {FE_DIVBYZERO, FE_INVALID, FE_INEXACT, FE_OVERFLOW, + FE_UNDERFLOW}; + + for (int e : excepts) { + __llvm_libc::fputil::clearExcept(FE_ALL_EXCEPT); + + // Save the cleared environment. + fenv_t env; + ASSERT_EQ(__llvm_libc::fegetenv(&env), 0); + + __llvm_libc::fputil::raiseExcept(e); + // Make sure that the exception is raised. + ASSERT_NE(__llvm_libc::fputil::testExcept(FE_ALL_EXCEPT) & e, 0); + + ASSERT_EQ(__llvm_libc::fesetenv(FE_DFL_ENV), 0); + // Setting the default env should clear all exceptions. + ASSERT_EQ(__llvm_libc::fputil::testExcept(FE_ALL_EXCEPT) & e, 0); + } + + ASSERT_EQ(__llvm_libc::fesetround(FE_DOWNWARD), 0); + ASSERT_EQ(__llvm_libc::fesetenv(FE_DFL_ENV), 0); + // Setting the default env should set rounding mode to FE_TONEAREST. + int rm = __llvm_libc::fegetround(); + EXPECT_EQ(rm, FE_TONEAREST); +}