diff --git a/docs/reference/config.rst b/docs/reference/config.rst index ab8e7ebb..d75653eb 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -17,21 +17,30 @@ These must be set before ``#include`` -ing any Flux headers. Runtime Error Policy ===================== -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. +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*. -Termination ------------ +Terminate +--------- .. c:macro:: FLUX_TERMINATE_ON_ERROR .. c:macro:: FLUX_PRINT_ERROR_ON_TERMINATE -If :c:macro:`FLUX_TERMINATE_ON_ERROR` is defined, a Flux runtime error will result in a call to :func:`std::terminate`. +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. 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``. -Unwinding +Fail Fast --------- +.. c:macro:: FLUX_FAIL_FAST_ON_ERROR + +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. + +Using the fail fast policy typically results in the smallest binary code size. + +Unwind +------ + .. c:macro:: FLUX_UNWIND_ON_ERROR .. 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 .. note:: - 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. + 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. 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. @@ -86,7 +95,7 @@ A custom :c:macro:`FLUX_INT_TYPE` must be a built-in signed integer type at leas Numeric Error Policies ====================== -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. +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. Overflow policy --------------- @@ -95,9 +104,9 @@ Overflow policy .. c:macro:: FLUX_WRAP_ON_OVERFLOW .. c:macro:: FLUX_IGNORE_OVERFLOW -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). +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). -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). +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). 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". @@ -107,6 +116,16 @@ Divide by zero policy .. c:macro:: FLUX_ERROR_ON_DIVIDE_BY_ZERO .. c:macro:: FLUX_IGNORE_DIVIDE_BY_ZERO -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. +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. + +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. + +Integer cast policy +------------------- + +.. c:macro:: FLUX_INTEGER_CAST_POLICY_CHECKED +.. c:macro:: FLUX_INTEGER_CAST_POLICY_UNCHECKED + +If :c:macro:`FLUX_INTEGER_CAST_POLICY_CHECKED` is defined, then :expr:`flux::num::cast(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. -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. +Alternatively :c:macro:`FLUX_INTEGER_CAST_POLICY_UNCHECKED` is defined then no runtime check will occur, and :expr:`flux::num::cast(from)` is equivalent to a plain :expr:`static_cast(from)`. This is the default in release builds. diff --git a/include/flux/core/assert.hpp b/include/flux/core/assert.hpp index 5c1416be..88310f5a 100644 --- a/include/flux/core/assert.hpp +++ b/include/flux/core/assert.hpp @@ -14,6 +14,15 @@ #include #include +#if defined(__has_builtin) +# if __has_builtin(__builtin_trap) +# define FLUX_HAS_BUILTIN_TRAP 1 +# endif +#elif defined(_MSC_VER) +# include +# define FLUX_HAS_FASTFAIL 1 +#endif + namespace flux { FLUX_EXPORT @@ -24,21 +33,51 @@ struct unrecoverable_error : std::logic_error { namespace detail { struct runtime_error_fn { +private: [[noreturn]] + FLUX_ALWAYS_INLINE + static void fail_fast() + { +#if FLUX_HAS_BUILTIN_TRAP + __builtin_trap(); +#elif FLUX_HAS_FASTFAIL + __fastfail(7); // FAST_FAIL_FATAL_APP_EXIT +#else + std::abort(); +#endif + } + + [[noreturn]] + static void unwind(const char* msg, std::source_location loc) + { + char buf[1024]; + std::snprintf(buf, 1024, "%s:%u: Fatal error: %s", + loc.file_name(), loc.line(), msg); + throw unrecoverable_error(buf); + } + + [[noreturn]] + static void terminate(const char* msg, std::source_location loc) + { + if constexpr (config::print_error_on_terminate) { + std::fprintf(stderr, "%s:%u: Fatal error: %s\n", + loc.file_name(), loc.line(), msg); + } + std::terminate(); + } + +public: + [[noreturn]] + FLUX_ALWAYS_INLINE void operator()(char const* msg, std::source_location loc = std::source_location::current()) const { - if constexpr (config::on_error == error_policy::unwind) { - char buf[1024]; - std::snprintf(buf, 1024, "%s:%u: Fatal error: %s", - loc.file_name(), loc.line(), msg); - throw unrecoverable_error(buf); + if constexpr (config::on_error == error_policy::fail_fast) { + fail_fast(); + } else if constexpr (config::on_error == error_policy::unwind) { + unwind(msg, loc); } else { - if constexpr (config::print_error_on_terminate) { - std::fprintf(stderr, "%s:%u: Fatal error: %s\n", - loc.file_name(), loc.line(), msg); - } - std::terminate(); + terminate(msg, loc); } } }; @@ -88,8 +127,7 @@ struct indexed_bounds_check_fn { } } #endif - assert_fn{}(idx >= T{0}, "index cannot be negative", loc); - assert_fn{}(idx < limit, "out-of-bounds sequence access", loc); + assert_fn{}(idx >= T{0} && idx < limit, "out-of-bounds sequence access", loc); } } }; diff --git a/include/flux/core/config.hpp b/include/flux/core/config.hpp index 322b663e..92d7af72 100644 --- a/include/flux/core/config.hpp +++ b/include/flux/core/config.hpp @@ -13,7 +13,8 @@ #include #define FLUX_ERROR_POLICY_TERMINATE 1 -#define FLUX_ERROR_POLICY_UNWIND 2 +#define FLUX_ERROR_POLICY_UNWIND 2 +#define FLUX_ERROR_POLICY_FAIL_FAST 3 #define FLUX_OVERFLOW_POLICY_ERROR 10 #define FLUX_OVERFLOW_POLICY_WRAP 11 @@ -47,6 +48,8 @@ # define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_TERMINATE #elif defined(FLUX_UNWIND_ON_ERROR) # define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_UNWIND +#elif defined(FLUX_FAIL_FAST_ON_ERROR) +# define FLUX_ERROR_POLICY FLUX_ERROR_POLICY_FAIL_FAST #else # define FLUX_ERROR_POLICY FLUX_DEFAULT_ERROR_POLICY #endif // FLUX_TERMINATE_ON_ERROR @@ -123,7 +126,8 @@ namespace flux { FLUX_EXPORT enum class error_policy { terminate = FLUX_ERROR_POLICY_TERMINATE, - unwind = FLUX_ERROR_POLICY_UNWIND + unwind = FLUX_ERROR_POLICY_UNWIND, + fail_fast = FLUX_ERROR_POLICY_FAIL_FAST }; FLUX_EXPORT diff --git a/include/flux/macros.hpp b/include/flux/macros.hpp index 38faae19..72c6865b 100644 --- a/include/flux/macros.hpp +++ b/include/flux/macros.hpp @@ -23,10 +23,12 @@ #define FLUX_DECLVAL(...) ((static_cast<__VA_ARGS__(*)()noexcept>(nullptr))()) -#ifdef __GNUC__ -#define FLUX_ALWAYS_INLINE [[gnu::always_inline]] +#if defined(__GNUC__) +# define FLUX_ALWAYS_INLINE [[gnu::always_inline]] inline +#elif defined(_MSC_VER) +# define FLUX_ALWAYS_INLINE __forceinline #else -#define FLUX_ALWAYS_INLINE +# define FLUX_ALWAYS_INLINE inline #endif #define FLUX_NO_UNIQUE_ADDRESS [[no_unique_address]]