#include #include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// static int volatile fpe_value = 0; static jmp_buf fpe_jump; //----------------------------------------------------------------------------- static void handle_fpe (int) { fpe_value = 1; longjmp (fpe_jump, 1); } /////////////////////////////////////////////////////////////////////////////// float volatile nan_val = NAN; float volatile inf_val = INFINITY; float volatile zero_val = 0.f; //----------------------------------------------------------------------------- int main () { signal (SIGFPE, handle_fpe); 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 (!setjmp (fpe_jump)) 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 (!setjmp (fpe_jump)) 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 (!setjmp (fpe_jump)) 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 (!setjmp (fpe_jump)) cruft::debug::escape (inf_val - inf_val); { cruft::debug::fpe::scoped_reset resetter; } if (!setjmp (fpe_jump)) 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 (!setjmp (fpe_jump)) cruft::debug::escape (inf_val - inf_val); }).join (); return tap.status (); }