121 lines
3.7 KiB
C++
121 lines
3.7 KiB
C++
#include <cruft/util/tap.hpp>
|
|
#include <cruft/util/debug/fpe.hpp>
|
|
#include <cruft/util/debug/compiler.hpp>
|
|
|
|
#include <thread>
|
|
|
|
#include <csignal>
|
|
#include <csetjmp>
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
static int volatile fpe_value = 0;
|
|
|
|
static sigjmp_buf fpe_jump;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void handle_fpe (int, siginfo_t*, void *)
|
|
{
|
|
fpe_value = 1;
|
|
siglongjmp (fpe_jump, 1);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float volatile nan_val = NAN;
|
|
float volatile inf_val = INFINITY;
|
|
float volatile zero_val = 0.f;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int main ()
|
|
{
|
|
struct sigaction fpe_sigaction {};
|
|
fpe_sigaction.sa_flags = SA_RESTART;
|
|
fpe_sigaction.sa_sigaction = handle_fpe;
|
|
sigaction (SIGFPE, &fpe_sigaction, nullptr);
|
|
|
|
cruft::TAP::logger tap;
|
|
|
|
// Test that we don't receive any exceptions by default.
|
|
fpe_value = 0;
|
|
cruft::debug::escape (inf_val - inf_val);
|
|
tap.expect_eq (fpe_value, 0, "FE_INVALID is disabled by default");
|
|
|
|
fpe_value = 0;
|
|
cruft::debug::escape (1.f / zero_val);
|
|
tap.expect_eq (fpe_value, 0, "FE_DIVBYZERO is disabled by default");
|
|
|
|
// After the signal handler exits we'll immediate re-enter the handler as
|
|
// the faulting floating ops are re-executed.
|
|
//
|
|
// Ideally we'd modify the FPU state, but we can't access that from the
|
|
// handler.
|
|
//
|
|
// So we longjmp out of the handler and use a conditional to avoid
|
|
// re-executing.
|
|
//
|
|
// However this approach doesn't restore the FPU state (I think; it doesn't
|
|
// restore _something_) and so the exceptions are permanently disabled.
|
|
//
|
|
// But threads get their own state. So.. we spin up a thread for each
|
|
// test. It's nasty, but:
|
|
// * it works
|
|
// * I'm not familiar enough with the area to do it a cleaner way
|
|
// * I've spent too much time on just writing this test already.
|
|
std::thread ([] () {
|
|
cruft::debug::fpe::enable ();
|
|
fpe_value = 0;
|
|
if (!sigsetjmp (fpe_jump, 1))
|
|
cruft::debug::escape (inf_val - inf_val);
|
|
}).join ();
|
|
tap.expect_eq (fpe_value, 1, "FE_INVALID is enabled after request");
|
|
|
|
std::thread ([] () {
|
|
cruft::debug::fpe::enable ();
|
|
fpe_value = 0;
|
|
if (!sigsetjmp (fpe_jump, 1))
|
|
cruft::debug::escape (1.f / zero_val);
|
|
}).join ();
|
|
tap.expect_eq (fpe_value, 1, "FE_DIVBYZERO is enabled after request");
|
|
|
|
std::thread ([] () {
|
|
fpe_value = 0;
|
|
cruft::debug::fpe::scoped_reset resetter;
|
|
if (!sigsetjmp (fpe_jump, 1))
|
|
cruft::debug::escape (inf_val - inf_val);
|
|
}).join ();
|
|
tap.expect_eq (fpe_value, 0, "scoped_reset enables exceptions");
|
|
|
|
std::thread ([] () {
|
|
fpe_value = 0;
|
|
|
|
// Do a test beforehand just in case we've forgotten to reset the
|
|
// fpe_value variable properly.
|
|
if (!sigsetjmp (fpe_jump, 1))
|
|
cruft::debug::escape (inf_val - inf_val);
|
|
|
|
{
|
|
cruft::debug::fpe::scoped_reset resetter;
|
|
}
|
|
|
|
if (!sigsetjmp (fpe_jump, 1))
|
|
cruft::debug::escape (inf_val - inf_val);
|
|
}).join ();
|
|
tap.expect_eq (fpe_value, 0, "scoped_reset disables exceptions");
|
|
|
|
// Ensure that the scoped object actually disables state if it's
|
|
// previously been statically set.
|
|
std::thread ([] {
|
|
fpe_value = 0;
|
|
|
|
cruft::debug::fpe::enable ();
|
|
cruft::debug::fpe::scoped_reset resetter (false);
|
|
|
|
if (!sigsetjmp (fpe_jump, 1))
|
|
cruft::debug::escape (inf_val - inf_val);
|
|
}).join ();
|
|
|
|
return tap.status ();
|
|
} |