27 changes: 27 additions & 0 deletions libc/src/fenv/fesetexceptflag.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===-- Implementation of fesetexceptflag 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/fenv/fesetexceptflag.h"
#include "src/__support/common.h"
#include "utils/FPUtil/FEnv.h"

#include <fenv.h>

namespace __llvm_libc {

LLVM_LIBC_FUNCTION(int, fesetexceptflag,
(const fexcept_t *flagp, int excepts)) {
// Since the return type of fetestexcept is int, we ensure that fexcept_t
// matches in size.
static_assert(sizeof(int) == sizeof(fexcept_t),
"sizeof(fexcept_t) != sizeof(int)");
int excepts_to_set = *reinterpret_cast<const int *>(flagp) & excepts;
return fputil::setExcept(excepts_to_set);
}

} // namespace __llvm_libc
20 changes: 20 additions & 0 deletions libc/src/fenv/fesetexceptflag.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//===-- Implementation header for fesetexceptflag ---------------*- 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_FESETEXCEPTFLAG_H
#define LLVM_LIBC_SRC_FENV_FESETEXCEPTFLAG_H

#include <fenv.h>

namespace __llvm_libc {

int fesetexceptflag(const fexcept_t *, int excepts);

} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_FENV_FESETEXCEPTFLAG_H
24 changes: 24 additions & 0 deletions libc/src/fenv/feupdateenv.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//===-- Implementation of feupdateenv 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/fenv/feupdateenv.h"
#include "src/__support/common.h"
#include "utils/FPUtil/FEnv.h"

#include <fenv.h>

namespace __llvm_libc {

LLVM_LIBC_FUNCTION(int, feupdateenv, (const fenv_t *envp)) {
int current_excepts = fputil::testExcept(FE_ALL_EXCEPT);
if (fputil::setEnv(envp) != 0)
return -1;
return fputil::raiseExcept(current_excepts);
}

} // namespace __llvm_libc
20 changes: 20 additions & 0 deletions libc/src/fenv/feupdateenv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//===-- Implementation header for feupdateenv -------------------*- 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_FEUPDATEENV_H
#define LLVM_LIBC_SRC_FENV_FEUPDATEENV_H

#include <fenv.h>

namespace __llvm_libc {

int feupdateenv(const fenv_t *);

} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_FENV_FEUPDATEENV_H
48 changes: 48 additions & 0 deletions libc/test/src/fenv/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@ add_libc_unittest(
libc.utils.FPUtil.fputil
)

add_libc_unittest(
getenv_and_setenv_test
SUITE
libc_fenv_unittests
SRCS
getenv_and_setenv_test.cpp
DEPENDS
libc.src.fenv.fegetenv
libc.src.fenv.fesetenv
libc.utils.FPUtil.fputil
)

add_libc_unittest(
exception_flags_test
SUITE
libc_fenv_unittests
SRCS
exception_flags_test.cpp
DEPENDS
libc.src.fenv.fegetexceptflag
libc.src.fenv.fesetexceptflag
libc.utils.FPUtil.fputil
)

add_libc_unittest(
feupdateenv_test
SUITE
libc_fenv_unittests
SRCS
feupdateenv_test.cpp
DEPENDS
libc.include.signal
libc.src.fenv.feupdateenv
libc.utils.FPUtil.fputil
)

if (NOT LLVM_USE_SANITIZER)
# Sanitizers don't like SIGFPE. So, we will run the
# tests which raise SIGFPE only in non-sanitizer builds.
Expand All @@ -40,4 +76,16 @@ if (NOT LLVM_USE_SANITIZER)
libc.src.fenv.fetestexcept
libc.utils.FPUtil.fputil
)

add_libc_unittest(
feholdexcept_test
SUITE
libc_fenv_unittests
SRCS
feholdexcept_test.cpp
DEPENDS
libc.include.signal
libc.src.fenv.feholdexcept
libc.utils.FPUtil.fputil
)
endif()
45 changes: 45 additions & 0 deletions libc/test/src/fenv/exception_flags_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//===-- Unittests for fegetexceptflag and fesetexceptflag -----------------===//
//
// 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/fegetexceptflag.h"
#include "src/fenv/fesetexceptflag.h"

#include "utils/FPUtil/FEnv.h"
#include "utils/UnitTest/Test.h"

#include <fenv.h>

TEST(LlvmLibcFenvTest, GetExceptFlagAndSetExceptFlag) {
// 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) {
// The overall idea is to raise an except and save the exception flags.
// Next, clear the flags and then set the saved exception flags. This
// should set the flag corresponding to the previously raised exception.
__llvm_libc::fputil::raiseExcept(e);
// Make sure that the exception flag is set.
ASSERT_NE(__llvm_libc::fputil::testExcept(FE_ALL_EXCEPT) & e, 0);

fexcept_t eflags;
ASSERT_EQ(__llvm_libc::fegetexceptflag(&eflags, FE_ALL_EXCEPT), 0);

__llvm_libc::fputil::clearExcept(e);
ASSERT_EQ(__llvm_libc::fputil::testExcept(FE_ALL_EXCEPT) & e, 0);

ASSERT_EQ(__llvm_libc::fesetexceptflag(&eflags, FE_ALL_EXCEPT), 0);
ASSERT_NE(__llvm_libc::fputil::testExcept(FE_ALL_EXCEPT) & e, 0);

// Cleanup
__llvm_libc::fputil::clearExcept(e);
}
}
37 changes: 37 additions & 0 deletions libc/test/src/fenv/feholdexcept_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//===-- Unittests for feholdexcept with exceptions enabled ----------------===//
//
// 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/feholdexcept.h"

#include "utils/FPUtil/FEnv.h"
#include "utils/UnitTest/Test.h"

#include <fenv.h>
#include <signal.h>

TEST(LlvmLibcFEnvTest, RaiseAndCrash) {
int excepts[] = {FE_DIVBYZERO, FE_INVALID, FE_INEXACT, FE_OVERFLOW,
FE_UNDERFLOW};

for (int e : excepts) {
fenv_t env;
__llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT);
__llvm_libc::fputil::enableExcept(e);
ASSERT_EQ(__llvm_libc::fputil::clearExcept(FE_ALL_EXCEPT), 0);
ASSERT_EQ(__llvm_libc::feholdexcept(&env), 0);
// feholdexcept should disable all excepts so raising an exception
// should not crash/invoke the exception handler.
ASSERT_EQ(__llvm_libc::fputil::raiseExcept(e), 0);

// When we put back the saved env which has the exception enabled, it
// should crash with SIGFPE.
__llvm_libc::fputil::setEnv(&env);
ASSERT_DEATH([=] { __llvm_libc::fputil::raiseExcept(e); },
WITH_SIGNAL(SIGFPE));
}
}
27 changes: 27 additions & 0 deletions libc/test/src/fenv/feupdateenv_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===-- Unittests for feupdateenv -----------------------------------------===//
//
// 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/feupdateenv.h"

#include "utils/FPUtil/FEnv.h"
#include "utils/UnitTest/Test.h"

#include <fenv.h>
#include <signal.h>

TEST(LlvmLibcFEnvTest, UpdateEnvTest) {
__llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT);
__llvm_libc::fputil::clearExcept(FE_ALL_EXCEPT);

fenv_t env;
ASSERT_EQ(__llvm_libc::fputil::getEnv(&env), 0);
__llvm_libc::fputil::setExcept(FE_INVALID | FE_INEXACT);
ASSERT_EQ(__llvm_libc::feupdateenv(&env), 0);
ASSERT_EQ(__llvm_libc::fputil::testExcept(FE_INVALID | FE_INEXACT),
FE_INVALID | FE_INEXACT);
}
39 changes: 39 additions & 0 deletions libc/test/src/fenv/getenv_and_setenv_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===-- Unittests for fegetenv and fesetenv -------------------------------===//
//
// 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/fegetenv.h"
#include "src/fenv/fesetenv.h"

#include "utils/FPUtil/FEnv.h"
#include "utils/UnitTest/Test.h"

#include <fenv.h>

TEST(LlvmLibcFenvTest, GetEnvAndSetEnv) {
// 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(&env), 0);
ASSERT_EQ(__llvm_libc::fputil::testExcept(FE_ALL_EXCEPT) & e, 0);
}
}
6 changes: 6 additions & 0 deletions libc/utils/FPUtil/DummyFEnv.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ static inline int clearExcept(int) { return 0; }

static inline int testExcept(int) { return 0; }

static inline int setExcept(int) { return 0; }

static inline int raiseExcept(int) { return 0; }

static inline int getRound() { return FE_TONEAREST; }

static inline int setRound(int) { return 0; }

static inline int getEnv(fenv_t *) { return 0; }

static inline int setEnv(const fenv_t *) { return 0; }

} // namespace fputil
} // namespace __llvm_libc

Expand Down
31 changes: 31 additions & 0 deletions libc/utils/FPUtil/aarch64/FEnv.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ namespace __llvm_libc {
namespace fputil {

struct FEnv {
struct FPState {
uint32_t ControlWord;
uint32_t StatusWord;
};

static_assert(
sizeof(fenv_t) == sizeof(FPState),
"Internal floating point state does not match the public fenv_t type.");

static constexpr uint32_t ToNearest = 0x0;
static constexpr uint32_t Upward = 0x1;
static constexpr uint32_t Downward = 0x2;
Expand Down Expand Up @@ -95,6 +104,14 @@ static inline int testExcept(int excepts) {
(statusWord >> FEnv::ExceptionStatusFlagsBitPosition) & toTest);
}

static inline int setExcept(int excepts) {
uint32_t statusWord = FEnv::getControlWord();
uint32_t statusValue = FEnv::getStatusValueForExcept(excepts);
statusWord |= (statusValue << FEnv::ExceptionStatusFlagsBitPosition);
FEnv::writeStatusWord(statusWord);
return 0;
}

static inline int raiseExcept(int excepts) {
float zero = 0.0f;
float one = 1.0f;
Expand Down Expand Up @@ -198,6 +215,20 @@ static inline int setRound(int mode) {
return 0;
}

static inline int getEnv(fenv_t *envp) {
FEnv::FPState *state = reinterpret_cast<FEnv::FPState *>(envp);
state->ControlWord = FEnv::getControlWord();
state->StatusWord = FEnv::getStatusWord();
return 0;
}

static inline int setEnv(const fenv_t *envp) {
const FEnv::FPState *state = reinterpret_cast<const FEnv::FPState *>(envp);
FEnv::writeControlWord(state->ControlWord);
FEnv::writeStatusWord(state->StatusWord);
return 0;
}

} // namespace fputil
} // namespace __llvm_libc

Expand Down
75 changes: 57 additions & 18 deletions libc/utils/FPUtil/x86_64/FEnv.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ static inline int exceptionStatusToMacro(uint16_t status) {
(status & ExceptionFlags::Inexact ? FE_INEXACT : 0);
}

struct X87State {
struct X87StateDescriptor {
uint16_t ControlWord;
uint16_t Unused1;
uint16_t StatusWord;
Expand All @@ -83,6 +83,15 @@ struct X87State {
uint32_t _[5];
};

struct FPState {
X87StateDescriptor X87Status;
uint32_t MXCSR;
};

static_assert(
sizeof(fenv_t) == sizeof(FPState),
"Internal floating point state does not match the public fenv_t type.");

static inline uint16_t getX87ControlWord() {
uint16_t w;
__asm__ __volatile__("fnstcw %0" : "=m"(w)::);
Expand Down Expand Up @@ -113,11 +122,11 @@ static inline void writeMXCSR(uint32_t w) {
__asm__ __volatile__("ldmxcsr %0" : : "m"(w) :);
}

static inline void getX87State(X87State &s) {
static inline void getX87StateDescriptor(X87StateDescriptor &s) {
__asm__ __volatile__("fnstenv %0" : "=m"(s));
}

static inline void writeX87State(const X87State &s) {
static inline void writeX87StateDescriptor(const X87StateDescriptor &s) {
__asm__ __volatile__("fldenv %0" : : "m"(s) :);
}

Expand Down Expand Up @@ -194,6 +203,21 @@ static inline int testExcept(int excepts) {
(statusValue & internal::getMXCSR()));
}

// Sets the exception flags but does not trigger the exception handler.
static inline int setExcept(int excepts) {
uint16_t statusValue = internal::getStatusValueForExcept(excepts);
internal::X87StateDescriptor state;
internal::getX87StateDescriptor(state);
state.StatusWord |= statusValue;
internal::writeX87StateDescriptor(state);

uint32_t mxcsr = internal::getMXCSR();
mxcsr |= statusValue;
internal::writeMXCSR(mxcsr);

return 0;
}

static inline int raiseExcept(int excepts) {
uint16_t statusValue = internal::getStatusValueForExcept(excepts);

Expand All @@ -211,38 +235,38 @@ static inline int raiseExcept(int excepts) {
// when raising the next exception.

if (statusValue & internal::ExceptionFlags::Invalid) {
internal::X87State state;
internal::getX87State(state);
internal::X87StateDescriptor state;
internal::getX87StateDescriptor(state);
state.StatusWord |= internal::ExceptionFlags::Invalid;
internal::writeX87State(state);
internal::writeX87StateDescriptor(state);
internal::fwait();
}
if (statusValue & internal::ExceptionFlags::DivByZero) {
internal::X87State state;
internal::getX87State(state);
internal::X87StateDescriptor state;
internal::getX87StateDescriptor(state);
state.StatusWord |= internal::ExceptionFlags::DivByZero;
internal::writeX87State(state);
internal::writeX87StateDescriptor(state);
internal::fwait();
}
if (statusValue & internal::ExceptionFlags::Overflow) {
internal::X87State state;
internal::getX87State(state);
internal::X87StateDescriptor state;
internal::getX87StateDescriptor(state);
state.StatusWord |= internal::ExceptionFlags::Overflow;
internal::writeX87State(state);
internal::writeX87StateDescriptor(state);
internal::fwait();
}
if (statusValue & internal::ExceptionFlags::Underflow) {
internal::X87State state;
internal::getX87State(state);
internal::X87StateDescriptor state;
internal::getX87StateDescriptor(state);
state.StatusWord |= internal::ExceptionFlags::Underflow;
internal::writeX87State(state);
internal::writeX87StateDescriptor(state);
internal::fwait();
}
if (statusValue & internal::ExceptionFlags::Inexact) {
internal::X87State state;
internal::getX87State(state);
internal::X87StateDescriptor state;
internal::getX87StateDescriptor(state);
state.StatusWord |= internal::ExceptionFlags::Inexact;
internal::writeX87State(state);
internal::writeX87StateDescriptor(state);
internal::fwait();
}

Expand Down Expand Up @@ -309,6 +333,21 @@ static inline int setRound(int mode) {
return 0;
}

static inline int getEnv(fenv_t *envp) {
internal::FPState *state = reinterpret_cast<internal::FPState *>(envp);
internal::getX87StateDescriptor(state->X87Status);
state->MXCSR = internal::getMXCSR();
return 0;
}

static inline int setEnv(const fenv_t *envp) {
const internal::FPState *state =
reinterpret_cast<const internal::FPState *>(envp);
internal::writeX87StateDescriptor(state->X87Status);
internal::writeMXCSR(state->MXCSR);
return 0;
}

} // namespace fputil
} // namespace __llvm_libc

Expand Down