[libc] Add FE_DFL_ENV and handle it in fesetenv.

Reviewed By: michaelrj

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

GitOrigin-RevId: 5c3c716bb1f5b209b42973790fc85ad241e7f86a
diff --git a/config/linux/api.td b/config/linux/api.td
index d4fec7a..d60d17b 100644
--- a/config/linux/api.td
+++ b/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/spec/stdc.td b/spec/stdc.td
index 2b64881..dd5ac0a 100644
--- a/spec/stdc.td
+++ b/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/src/__support/FPUtil/aarch64/FEnvImpl.h b/src/__support/FPUtil/aarch64/FEnvImpl.h
index 36d6caa..d59e8f0 100644
--- a/src/__support/FPUtil/aarch64/FEnvImpl.h
+++ b/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<const FEnv::FPState *>(envp);
   FEnv::writeControlWord(state->ControlWord);
   FEnv::writeStatusWord(state->StatusWord);
diff --git a/src/__support/FPUtil/x86_64/FEnvImpl.h b/src/__support/FPUtil/x86_64/FEnvImpl.h
index d5bb703..0a220a2 100644
--- a/src/__support/FPUtil/x86_64/FEnvImpl.h
+++ b/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<const internal::FPState *>(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/test/src/fenv/CMakeLists.txt b/test/src/fenv/CMakeLists.txt
index bab83c1..89b60e5 100644
--- a/test/src/fenv/CMakeLists.txt
+++ b/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/test/src/fenv/getenv_and_setenv_test.cpp b/test/src/fenv/getenv_and_setenv_test.cpp
index 4a5fbba..aa1b53d 100644
--- a/test/src/fenv/getenv_and_setenv_test.cpp
+++ b/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);
+}