debug/fpe: add POSIX FPE controls
This commit is contained in:
parent
41f444a943
commit
5cedd22d7a
@ -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
0
debug/fpe.cpp
Normal file
40
debug/fpe.hpp
Normal file
40
debug/fpe.hpp
Normal 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
68
debug/fpe_posix.cpp
Normal 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
0
debug/fpe_win32.cpp
Normal file
121
test/debug/fpe.cpp
Normal file
121
test/debug/fpe.cpp
Normal 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 ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user