--- /dev/null
+//===-- Unittests for feraiseexcept 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/feclearexcept.h"
+#include "src/fenv/feraiseexcept.h"
+#include "src/fenv/fetestexcept.h"
+
+#include "utils/FPUtil/FEnv.h"
+#include "utils/UnitTest/Test.h"
+
+#include <fenv.h>
+#include <signal.h>
+
+// This test enables an exception and verifies that raising that exception
+// triggers SIGFPE.
+TEST(ExceptionStatusTest, RaiseAndCrash) {
+ // TODO: Install a floating point exception handler and verify that the
+ // the expected exception was raised. One will have to longjmp back from
+ // that exception handler, so such a testing can be done after we have
+ // longjmp implemented.
+
+ int excepts[] = {FE_DIVBYZERO, FE_INVALID, FE_INEXACT, FE_OVERFLOW,
+ FE_UNDERFLOW};
+
+ for (int e : excepts) {
+ ASSERT_DEATH(
+ [=] {
+ __llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT);
+ __llvm_libc::fputil::enableExcept(e);
+ ASSERT_EQ(__llvm_libc::feclearexcept(FE_ALL_EXCEPT), 0);
+ // Raising all exceptions except |e| should not call the
+ // SIGFPE handler. They should set the exception flag though,
+ // so we verify that.
+ int others = FE_ALL_EXCEPT & ~e;
+ ASSERT_EQ(__llvm_libc::feraiseexcept(others), 0);
+ ASSERT_EQ(__llvm_libc::fetestexcept(others), others);
+
+ // We don't check the return value when raising |e| as
+ // feraiseexcept will not return when it raises an enabled
+ // exception.
+ __llvm_libc::feraiseexcept(e);
+ },
+ SIGFPE);
+ }
+}
static constexpr uint16_t Inexact = 0x20;
};
+// The exception control bits occupy six bits, one bit for each exception.
+// In the x87 control word, they occupy the first 6 bits. In the MXCSR
+// register, they occupy bits 7 to 12.
+static constexpr uint16_t X87ExceptionControlBitPosition = 0;
+static constexpr uint16_t MXCSRExceptionContolBitPoistion = 7;
+
// 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) {
(status & ExceptionFlags::Inexact ? FE_INEXACT : 0);
}
+struct X87State {
+ uint16_t ControlWord;
+ uint16_t Unused1;
+ uint16_t StatusWord;
+ uint16_t Unused2;
+ // TODO: Elaborate the remaining 20 bytes as required.
+ uint32_t _[5];
+};
+
static inline uint16_t getX87ControlWord() {
uint16_t w;
__asm__ __volatile__("fnstcw %0" : "=m"(w)::);
__asm__ __volatile__("ldmxcsr %0" : : "m"(w) :);
}
+static inline void getX87State(X87State &s) {
+ __asm__ __volatile__("fnstenv %0" : "=m"(s));
+}
+
+static inline void writeX87State(const X87State &s) {
+ __asm__ __volatile__("fldenv %0" : : "m"(s) :);
+}
+
+static inline void fwait() { __asm__ __volatile__("fwait"); }
+
} // namespace internal
+static inline int enableExcept(int excepts) {
+ // In the x87 control word and in MXCSR, an exception is blocked
+ // if the corresponding bit is set. That is the reason for all the
+ // bit-flip operations below as we need to turn the bits to zero
+ // to enable them.
+
+ uint16_t bitMask = internal::getStatusValueForExcept(excepts);
+
+ uint16_t x87CW = internal::getX87ControlWord();
+ uint16_t oldExcepts = ~x87CW & 0x3F; // Save previously enabled exceptions.
+ x87CW &= ~bitMask;
+ internal::writeX87ControlWord(x87CW);
+
+ // Enabling SSE exceptions via MXCSR is a nice thing to do but
+ // might not be of much use practically as SSE exceptions and the x87
+ // exceptions are independent of each other.
+ uint32_t mxcsr = internal::getMXCSR();
+ mxcsr &= ~(bitMask << internal::MXCSRExceptionContolBitPoistion);
+ internal::writeMXCSR(mxcsr);
+
+ // Since the x87 exceptions and SSE exceptions are independent of each,
+ // it doesn't make much sence to report both in the return value. Most
+ // often, the standard floating point functions deal with FPU operations
+ // so we will retrun only the old x87 exceptions.
+ return internal::exceptionStatusToMacro(oldExcepts);
+}
+
+static inline int disableExcept(int excepts) {
+ // In the x87 control word and in MXCSR, an exception is blocked
+ // if the corresponding bit is set.
+
+ uint16_t bitMask = internal::getStatusValueForExcept(excepts);
+
+ uint16_t x87CW = internal::getX87ControlWord();
+ uint16_t oldExcepts = ~x87CW & 0x3F; // Save previously enabled exceptions.
+ x87CW |= bitMask;
+ internal::writeX87ControlWord(x87CW);
+
+ // Just like in enableExcept, it is not clear if disabling SSE exceptions
+ // is required. But, we will still do it only as a "nice thing to do".
+ uint32_t mxcsr = internal::getMXCSR();
+ mxcsr |= (bitMask << internal::MXCSRExceptionContolBitPoistion);
+ internal::writeMXCSR(mxcsr);
+
+ return internal::exceptionStatusToMacro(oldExcepts);
+}
+
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.
}
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 mxcsr = internal::getMXCSR();
- mxcsr = mxcsr | statusValue;
- internal::writeMXCSR(mxcsr);
+
+ // We set the status flag for exception one at a time and call the
+ // fwait instruction to actually get the processor to raise the
+ // exception by calling the exception handler. This scheme is per
+ // the description in in "8.6 X87 FPU EXCEPTION SYNCHRONIZATION"
+ // of the "Intel 64 and IA-32 Architectures Software Developer's
+ // Manual, Vol 1".
+
+ // FPU status word is read for each exception seperately as the
+ // exception handler can potentially write to it (typically to clear
+ // the corresponding exception flag). By reading it separately, we
+ // ensure that the writes by the exception handler are maintained
+ // when raising the next exception.
+
+ if (statusValue & internal::ExceptionFlags::Invalid) {
+ internal::X87State state;
+ internal::getX87State(state);
+ state.StatusWord |= internal::ExceptionFlags::Invalid;
+ internal::writeX87State(state);
+ internal::fwait();
+ }
+ if (statusValue & internal::ExceptionFlags::DivByZero) {
+ internal::X87State state;
+ internal::getX87State(state);
+ state.StatusWord |= internal::ExceptionFlags::DivByZero;
+ internal::writeX87State(state);
+ internal::fwait();
+ }
+ if (statusValue & internal::ExceptionFlags::Overflow) {
+ internal::X87State state;
+ internal::getX87State(state);
+ state.StatusWord |= internal::ExceptionFlags::Overflow;
+ internal::writeX87State(state);
+ internal::fwait();
+ }
+ if (statusValue & internal::ExceptionFlags::Underflow) {
+ internal::X87State state;
+ internal::getX87State(state);
+ state.StatusWord |= internal::ExceptionFlags::Underflow;
+ internal::writeX87State(state);
+ internal::fwait();
+ }
+ if (statusValue & internal::ExceptionFlags::Inexact) {
+ internal::X87State state;
+ internal::getX87State(state);
+ state.StatusWord |= internal::ExceptionFlags::Inexact;
+ internal::writeX87State(state);
+ internal::fwait();
+ }
+
+ // There is no special synchronization scheme available to
+ // raise SEE exceptions. So, we will ignore that for now.
+ // Just plain writing to the MXCSR register does not guarantee
+ // the exception handler will be called.
+
return 0;
}