debug/fpe: add POSIX FPE controls

This commit is contained in:
Danny Robson 2021-05-13 13:13:22 +10:00
parent 41f444a943
commit 5cedd22d7a
6 changed files with 234 additions and 0 deletions

View File

@ -123,6 +123,7 @@ if (NOT WIN32)
memory/system.hpp
debug_posix.cpp
debug/crash_posix.cpp
debug/fpe_posix.cpp
debug/system_posix.cpp
io_posix.cpp
io_posix.hpp
@ -147,6 +148,7 @@ if (WIN32)
APPEND UTIL_FILES
debug_win32.cpp
debug/crash_win32.cpp
debug/fpe_win32.cpp
debug/system_win32.cpp
exe_win32.cpp
io_win32.cpp
@ -308,6 +310,8 @@ list (
debug/crash.hpp
debug/debugger.cpp
debug/debugger.hpp
debug/fpe.hpp
debug/fpe.cpp
debug/memory.cpp
debug/memory.hpp
debug/panic.cpp
@ -692,6 +696,7 @@ if (TESTS)
concepts
comparator
coord
debug/fpe
encode/number
encode/base
endian

0
debug/fpe.cpp Normal file
View File

40
debug/fpe.hpp Normal file
View File

@ -0,0 +1,40 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2021, Danny Robson <danny@nerdcruft.net>
*/
///////////////////////////////////////////////////////////////////////////////
namespace cruft::debug::fpe {
void enable (void);
void disable (void);
bool value (void);
bool value (bool);
/// Returns the FPE state to the value it was at construction time when the object falls out of scope.
///
/// Copy and move constructors are deliberately deleted to avoid accidentally leaking state.
class scoped_reset {
public:
/// An explict enablement state to enact at construction.
scoped_reset (bool enabled);
/// Caches the current state without changing anything.
scoped_reset ();
scoped_reset (scoped_reset const&) = delete;
scoped_reset& operator= (scoped_reset const&) = delete;
scoped_reset (scoped_reset &&) noexcept = delete;
scoped_reset& operator= (scoped_reset &&) noexcept = delete;
~scoped_reset ();
private:
bool m_prev;
};
}

68
debug/fpe_posix.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "./fpe.hpp"
#include <stdexcept>
#include <cassert>
#include <fenv.h>
using cruft::debug::fpe::scoped_reset;
///////////////////////////////////////////////////////////////////////////////
static constexpr int EXCEPTION_MASK = FE_INVALID | FE_DIVBYZERO;
//-----------------------------------------------------------------------------
void
cruft::debug::fpe::enable (void)
{
feenableexcept (EXCEPTION_MASK);
}
//-----------------------------------------------------------------------------
void
cruft::debug::fpe::disable (void)
{
fedisableexcept (EXCEPTION_MASK);
}
//-----------------------------------------------------------------------------
bool cruft::debug::fpe::value (void)
{
return feenableexcept (0) & EXCEPTION_MASK;
}
//-----------------------------------------------------------------------------
bool cruft::debug::fpe::value (bool val)
{
if (val)
enable ();
else
disable ();
return val;
}
///////////////////////////////////////////////////////////////////////////////
scoped_reset::scoped_reset ()
: m_prev (value ())
{ }
//-----------------------------------------------------------------------------
scoped_reset::scoped_reset (bool _value)
: scoped_reset ()
{
value (_value);
}
//-----------------------------------------------------------------------------
scoped_reset::~scoped_reset ()
{
value (m_prev);
}

0
debug/fpe_win32.cpp Normal file
View File

121
test/debug/fpe.cpp Normal file
View File

@ -0,0 +1,121 @@
#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 ();
}