Skip to content

Commit fa983e5

Browse files
authored
Merge pull request #210 from tcbrindle/pr/failfast
Add fail_fast runtime error policy
2 parents 291df6d + c08e6d6 commit fa983e5

File tree

4 files changed

+91
-28
lines changed

4 files changed

+91
-28
lines changed

docs/reference/config.rst

+30-11
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,30 @@ These must be set before ``#include`` -ing any Flux headers.
1717
Runtime Error Policy
1818
=====================
1919

20-
When normal execution of a program cannot continue, Flux will raise a *runtime error*. Typically this happens because the library has detected a situation that would otherwise lead to undefined behaviour -- for example, an out-of-bounds read of a sequence or a dereference of an empty :type:`flux::optional`. The library can be configured to handle runtime errors in one of two ways: either by terminating, or by unwinding.
20+
When normal execution of a program cannot continue, Flux will raise a *runtime error*. Typically this happens because the library has detected a situation that would otherwise lead to undefined behaviour -- for example, an out-of-bounds read of a sequence or a dereference of an empty :type:`flux::optional`. A runtime error will be handled according to the configured error policy: one of *terminate*, *fail fast* or *unwind*. The default error policy is *terminate*.
2121

22-
Termination
23-
-----------
22+
Terminate
23+
---------
2424

2525
.. c:macro:: FLUX_TERMINATE_ON_ERROR
2626
.. c:macro:: FLUX_PRINT_ERROR_ON_TERMINATE
2727
28-
If :c:macro:`FLUX_TERMINATE_ON_ERROR` is defined, a Flux runtime error will result in a call to :func:`std::terminate`.
28+
If :c:macro:`FLUX_TERMINATE_ON_ERROR` is defined, a Flux runtime error will result in a call to :func:`std::terminate`. This will in turn run the currently set terminate handler before halting the process.
2929

3030
By default, the library will attempt to print a short message to ``stdout`` describing the error before terminating. This can be disabled by setting :c:macro:`FLUX_PRINT_ERROR_ON_TERMINATE` to ``0``.
3131

32-
Unwinding
32+
Fail Fast
3333
---------
3434

35+
.. c:macro:: FLUX_FAIL_FAST_ON_ERROR
36+
37+
Alternatively :c:macro:`FLUX_FAIL_FAST_ON_ERROR` is defined, the library will attempt to halt the running process in the fastest way possible, typically by executing an illegal CPU instruction. No cleanup will occur, no debug info will be printed to the console, and no exit handlers will be called.
38+
39+
Using the fail fast policy typically results in the smallest binary code size.
40+
41+
Unwind
42+
------
43+
3544
.. c:macro:: FLUX_UNWIND_ON_ERROR
3645
3746
.. struct:: unrecoverable_error : std::logic_error
@@ -46,7 +55,7 @@ If :c:macro:`FLUX_UNWIND_ON_ERROR` is defined, a runtime error will result in an
4655

4756
.. note::
4857

49-
According to the C++ standard, it is unspecified whether stack unwinding will occur if an exception is not caught -- an implementation may choose to immediately call :func:`std::terminate` without performing unwinding.
58+
According to the C++ standard, it is unspecified whether stack unwinding occurs if an exception is not caught -- an implementation may choose to immediately call :func:`std::terminate` without performing unwinding if there is no matching catch clause anywhere in the call stack.
5059

5160
If using the "unwind" policy, you may also wish to wrap your :func:`main` in an appropriate try-catch block to ensure unwinding occurs on all platforms.
5261

@@ -86,7 +95,7 @@ A custom :c:macro:`FLUX_INT_TYPE` must be a built-in signed integer type at leas
8695
Numeric Error Policies
8796
======================
8897

89-
Flux provides a selection of checked integer functions, which are used internally by the library when performing operations on signed ints. The behaviour of these functions can be customised by setting the overflow policy and divide by zero policies as desired.
98+
Flux provides a selection of checked integer functions, which are used internally by the library when performing operations on ints. The behaviour of these functions can be customised by setting the overflow, divide by zero and integer cast policies as desired.
9099

91100
Overflow policy
92101
---------------
@@ -95,9 +104,9 @@ Overflow policy
95104
.. c:macro:: FLUX_WRAP_ON_OVERFLOW
96105
.. c:macro:: FLUX_IGNORE_OVERFLOW
97106
98-
If :c:macro:`FLUX_ERROR_ON_OVERFLOW` is set, a signed integer operation which would overflow will instead raise a runtime error. This is the default in debug builds (i.e. ``NDEBUG`` is not set).
107+
If :c:macro:`FLUX_ERROR_ON_OVERFLOW` is set, an integer operation which would overflow will instead raise a runtime error. This is the default in debug builds (i.e. ``NDEBUG`` is not set).
99108

100-
Alternatively, if :c:macro:`FLUX_WRAP_ON_OVERFLOW` is set, signed integer operations are performed as if by casting to the equivalent unsigned type, performing the operation, and then casting back to the original signed type. This avoids undefined behaviour (since overflow is well defined on unsigned ints) and avoids needing to generate error handing code, at the cost of giving numerically incorrect answers if overflow occurs. This is the default in release builds (i.e. ``NDEBUG`` is set).
109+
Alternatively, if :c:macro:`FLUX_WRAP_ON_OVERFLOW` is set, integer operations are performed as if by casting to the equivalent unsigned type, performing the operation, and then casting back to the original type. This avoids undefined behaviour (since overflow is well defined on unsigned ints) and avoids needing to generate error handing code, at the cost of giving numerically incorrect answers if overflow occurs. This is the default in release builds (i.e. ``NDEBUG`` is set).
101110

102111
Finally, if :c:macro:`FLUX_IGNORE_OVERFLOW` is set, the standard built-in integer operations will be used. This means that an operation which overflows will result in undefined behaviour. Use this setting if you are already handling signed integer UB by some other means (for example compiling with ``-ftrapv`` or using UB Sanitizer) and wish to avoid "double checking".
103112

@@ -107,6 +116,16 @@ Divide by zero policy
107116
.. c:macro:: FLUX_ERROR_ON_DIVIDE_BY_ZERO
108117
.. c:macro:: FLUX_IGNORE_DIVIDE_BY_ZERO
109118
110-
If :c:macro:`FLUX_ERROR_ON_DIVIDE_BY_ZERO` is set then a runtime error will be raised if zero is passed as the second argument to :func:`flux::checked_div` or :func:`flux::checked_mod`. This is the default in debug builds.
119+
If :c:macro:`FLUX_ERROR_ON_DIVIDE_BY_ZERO` is set then a runtime error will be raised if zero is passed as the second argument to :func:`flux::num::div` or :func:`flux::num::mod`. This is the default in debug builds.
120+
121+
Alternatively, if :c:macro:`FLUX_IGNORE_DIVIDE_BY_ZERO` is set then no extra zero check will be used in :func:`flux::num::div` or :func:`flux::num::mod`. This is the default for release builds.
122+
123+
Integer cast policy
124+
-------------------
125+
126+
.. c:macro:: FLUX_INTEGER_CAST_POLICY_CHECKED
127+
.. c:macro:: FLUX_INTEGER_CAST_POLICY_UNCHECKED
128+
129+
If :c:macro:`FLUX_INTEGER_CAST_POLICY_CHECKED` is defined, then :expr:`flux::num::cast<To>(from)` will (if necessary) perform a runtime check to ensure that the source value is within the bounds of the destination type -- that is, that the cast is not lossy. This is the default for debug builds.
111130

112-
Alternatively, if :c:macro:`FLUX_IGNORE_DIVIDE_BY_ZERO` is set then no extra zero check will be used in :func:`flux::checked_div` or :func:`flux::checked_mod`. This is the default for release builds.
131+
Alternatively :c:macro:`FLUX_INTEGER_CAST_POLICY_UNCHECKED` is defined then no runtime check will occur, and :expr:`flux::num::cast<To>(from)` is equivalent to a plain :expr:`static_cast<To>(from)`. This is the default in release builds.

include/flux/core/assert.hpp

+50-12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@
1414
#include <stdexcept>
1515
#include <type_traits>
1616

17+
#if defined(__has_builtin)
18+
# if __has_builtin(__builtin_trap)
19+
# define FLUX_HAS_BUILTIN_TRAP 1
20+
# endif
21+
#elif defined(_MSC_VER)
22+
# include <intrin.h>
23+
# define FLUX_HAS_FASTFAIL 1
24+
#endif
25+
1726
namespace flux {
1827

1928
FLUX_EXPORT
@@ -24,21 +33,51 @@ struct unrecoverable_error : std::logic_error {
2433
namespace detail {
2534

2635
struct runtime_error_fn {
36+
private:
2737
[[noreturn]]
38+
FLUX_ALWAYS_INLINE
39+
static void fail_fast()
40+
{
41+
#if FLUX_HAS_BUILTIN_TRAP
42+
__builtin_trap();
43+
#elif FLUX_HAS_FASTFAIL
44+
__fastfail(7); // FAST_FAIL_FATAL_APP_EXIT
45+
#else
46+
std::abort();
47+
#endif
48+
}
49+
50+
[[noreturn]]
51+
static void unwind(const char* msg, std::source_location loc)
52+
{
53+
char buf[1024];
54+
std::snprintf(buf, 1024, "%s:%u: Fatal error: %s",
55+
loc.file_name(), loc.line(), msg);
56+
throw unrecoverable_error(buf);
57+
}
58+
59+
[[noreturn]]
60+
static void terminate(const char* msg, std::source_location loc)
61+
{
62+
if constexpr (config::print_error_on_terminate) {
63+
std::fprintf(stderr, "%s:%u: Fatal error: %s\n",
64+
loc.file_name(), loc.line(), msg);
65+
}
66+
std::terminate();
67+
}
68+
69+
public:
70+
[[noreturn]]
71+
FLUX_ALWAYS_INLINE
2872
void operator()(char const* msg,
2973
std::source_location loc = std::source_location::current()) const
3074
{
31-
if constexpr (config::on_error == error_policy::unwind) {
32-
char buf[1024];
33-
std::snprintf(buf, 1024, "%s:%u: Fatal error: %s",
34-
loc.file_name(), loc.line(), msg);
35-
throw unrecoverable_error(buf);
75+
if constexpr (config::on_error == error_policy::fail_fast) {
76+
fail_fast();
77+
} else if constexpr (config::on_error == error_policy::unwind) {
78+
unwind(msg, loc);
3679
} else {
37-
if constexpr (config::print_error_on_terminate) {
38-
std::fprintf(stderr, "%s:%u: Fatal error: %s\n",
39-
loc.file_name(), loc.line(), msg);
40-
}
41-
std::terminate();
80+
terminate(msg, loc);
4281
}
4382
}
4483
};
@@ -88,8 +127,7 @@ struct indexed_bounds_check_fn {
88127
}
89128
}
90129
#endif
91-
assert_fn{}(idx >= T{0}, "index cannot be negative", loc);
92-
assert_fn{}(idx < limit, "out-of-bounds sequence access", loc);
130+
assert_fn{}(idx >= T{0} && idx < limit, "out-of-bounds sequence access", loc);
93131
}
94132
}
95133
};

include/flux/core/config.hpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
#include <type_traits>
1414

1515
#define FLUX_ERROR_POLICY_TERMINATE 1
16-
#define FLUX_ERROR_POLICY_UNWIND 2
16+
#define FLUX_ERROR_POLICY_UNWIND 2
17+
#define FLUX_ERROR_POLICY_FAIL_FAST 3
1718

1819
#define FLUX_OVERFLOW_POLICY_ERROR 10
1920
#define FLUX_OVERFLOW_POLICY_WRAP 11
@@ -47,6 +48,8 @@
4748
# define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_TERMINATE
4849
#elif defined(FLUX_UNWIND_ON_ERROR)
4950
# define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_UNWIND
51+
#elif defined(FLUX_FAIL_FAST_ON_ERROR)
52+
# define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_FAIL_FAST
5053
#else
5154
# define FLUX_ERROR_POLICY FLUX_DEFAULT_ERROR_POLICY
5255
#endif // FLUX_TERMINATE_ON_ERROR
@@ -123,7 +126,8 @@ namespace flux {
123126
FLUX_EXPORT
124127
enum class error_policy {
125128
terminate = FLUX_ERROR_POLICY_TERMINATE,
126-
unwind = FLUX_ERROR_POLICY_UNWIND
129+
unwind = FLUX_ERROR_POLICY_UNWIND,
130+
fail_fast = FLUX_ERROR_POLICY_FAIL_FAST
127131
};
128132

129133
FLUX_EXPORT

include/flux/macros.hpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323

2424
#define FLUX_DECLVAL(...) ((static_cast<__VA_ARGS__(*)()noexcept>(nullptr))())
2525

26-
#ifdef __GNUC__
27-
#define FLUX_ALWAYS_INLINE [[gnu::always_inline]]
26+
#if defined(__GNUC__)
27+
# define FLUX_ALWAYS_INLINE [[gnu::always_inline]] inline
28+
#elif defined(_MSC_VER)
29+
# define FLUX_ALWAYS_INLINE __forceinline
2830
#else
29-
#define FLUX_ALWAYS_INLINE
31+
# define FLUX_ALWAYS_INLINE inline
3032
#endif
3133

3234
#define FLUX_NO_UNIQUE_ADDRESS [[no_unique_address]]

0 commit comments

Comments
 (0)