diff --git a/CMakeLists.txt b/CMakeLists.txt index f331e798..b3073f86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ target_compile_options(flux-internal INTERFACE /W4 /wd4459 # local variable name hides global variable /wd4702 # unreachable code + /wd4100 # unreferenced formal parameter > ) diff --git a/include/flux/core/detail/jtckdint.h b/include/flux/core/detail/jtckdint.h new file mode 100644 index 00000000..62f44b80 --- /dev/null +++ b/include/flux/core/detail/jtckdint.h @@ -0,0 +1,684 @@ +/* + * Copyright 2023 Justine Alexandra Roberts Tunney + * + * Permission to use, copy, modify, and/or distribute this software for + * any purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Upstream repo: https://github.com/jart/jtckdint + * + * This file contains the following changes from upstream v0.2: + * - All functions are constexpr + * - `if` changed to `if constexpr` where appropriate + * - #include before to define the _Bool type for GCC in C++ mode + * - Use GNU builtins even if __STRICT_ANSI__ is defined + * - Use pragma to disable MSVC integer conversion warning + */ + +/** + * @fileoverview C23 Checked Arithmetic + * + * This header defines three type generic functions: + * + * - `bool ckd_add(res, a, b)` + * - `bool ckd_sub(res, a, b)` + * - `bool ckd_mul(res, a, b)` + * + * Which allow integer arithmetic errors to be detected. There are many + * kinds of integer errors, e.g. overflow, truncation, etc. These funcs + * catch them all. Here's an example of how it works: + * + * uint32_t c; + * int32_t a = 0x7fffffff; + * int32_t b = 2; + * assert(!ckd_add(&c, a, b)); + * assert(c == 0x80000001u); + * + * Experienced C / C++ users should find this example counter-intuitive + * because the expression `0x7fffffff + 2` not only overflows it's also + * undefined behavior. However here we see it's specified, and does not + * result in an error. That's because C23 checked arithmetic is not the + * arithmetic you're used to. The new standard changes the mathematics. + * + * C23 checked arithmetic is defined as performing the arithmetic using + * infinite precision and then checking if the resulting value will fit + * in the output type. Our example above did not result in an error due + * to `0x80000001` being a legal value for `uint32_t`. + * + * This implementation will use the GNU compiler builtins, when they're + * available, only if you don't use build flags like `-std=c11` because + * they define `__STRICT_ANSI__` and GCC extensions aren't really ANSI. + * Instead, you'll get a pretty good pure C11 and C++11 implementation. + * + * @see https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf + * @version 0.1 (2023-07-22) + */ + +#ifndef JTCKDINT_H_ +#define JTCKDINT_H_ + +#ifdef __has_include +#define __ckd_has_include(x) __has_include(x) +#else +#define __ckd_has_include(x) 0 +#endif + +#if __ckd_has_include() +#include +#include +#else + +#define __STDC_VERSION_STDCKDINT_H__ 202311L + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4146 4244) +#endif + +#if ((defined(__llvm__) || \ + (defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ >= 406)) && \ + !defined(__STRICT_ANSI__)) +#define __ckd_have_int128 +#define __ckd_intmax __int128 +#elif ((defined(__cplusplus) && __cplusplus >= 201103L) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)) +#define __ckd_intmax long long +#else +#define __ckd_intmax long +#endif + +typedef signed __ckd_intmax __ckd_intmax_t; +typedef unsigned __ckd_intmax __ckd_uintmax_t; + +#ifdef __has_builtin +#define __ckd_has_builtin(x) __has_builtin(x) +#else +#define __ckd_has_builtin(x) 0 +#endif + +#if ((defined(__GNUC__) && __GNUC__ >= 5 && !defined(__ICC)) || \ + (__ckd_has_builtin(__builtin_add_overflow) && \ + __ckd_has_builtin(__builtin_sub_overflow) && \ + __ckd_has_builtin(__builtin_mul_overflow))) +#define ckd_add(res, x, y) __builtin_add_overflow((x), (y), (res)) +#define ckd_sub(res, x, y) __builtin_sub_overflow((x), (y), (res)) +#define ckd_mul(res, x, y) __builtin_mul_overflow((x), (y), (res)) + +#elif (defined(__cplusplus) && \ + (__cplusplus >= 201103L || \ + (defined(_MSC_VER) && __cplusplus >= 199711L && \ + __ckd_has_include() && \ + __ckd_has_include()))) +#include +#include + +template +inline constexpr bool ckd_add(__T *__res, __U __a, __V __b) { + static_assert(std::is_integral<__T>::value && + std::is_integral<__U>::value && + std::is_integral<__V>::value, + "non-integral types not allowed"); + static_assert(!std::is_same<__T, bool>::value && + !std::is_same<__U, bool>::value && + !std::is_same<__V, bool>::value, + "checked booleans not supported"); + static_assert(!std::is_same<__T, char>::value && + !std::is_same<__U, char>::value && + !std::is_same<__V, char>::value, + "unqualified char type is ambiguous"); + __ckd_uintmax_t __x = __a; + __ckd_uintmax_t __y = __b; + __ckd_uintmax_t __z = __x + __y; + *__res = __z; + if constexpr (sizeof(__z) > sizeof(__U) && sizeof(__z) > sizeof(__V)) { + if constexpr (sizeof(__z) > sizeof(__T) || std::is_signed<__T>::value) { + return static_cast<__ckd_intmax_t>(__z) != static_cast<__T>(__z); + } else if (!std::is_same<__T, __ckd_uintmax_t>::value) { + return (__z != static_cast<__T>(__z) || + ((std::is_signed<__U>::value || + std::is_signed<__V>::value) && + static_cast<__ckd_intmax_t>(__z) < 0)); + } + } + bool __truncated = false; + if constexpr (sizeof(__T) < sizeof(__ckd_intmax_t)) { + __truncated = __z != static_cast<__ckd_uintmax_t>(static_cast<__T>(__z)); + } + switch (std::is_signed<__T>::value << 2 | // + std::is_signed<__U>::value << 1 | // + std::is_signed<__V>::value) { + case 0: // u = u + u + return __truncated | (__z < __x); + case 1: // u = u + s + __y ^= std::numeric_limits<__ckd_intmax_t>::min(); + return __truncated | + (static_cast<__ckd_intmax_t>((__z ^ __x) & + (__z ^ __y)) < 0); + case 2: // u = s + u + __x ^= std::numeric_limits<__ckd_intmax_t>::min(); + return __truncated | + (static_cast<__ckd_intmax_t>((__z ^ __x) & + (__z ^ __y)) < 0); + case 3: // u = s + s + return __truncated | + (static_cast<__ckd_intmax_t>(((__z | __x) & __y) | + ((__z & __x) & ~__y)) < 0); + case 4: // s = u + u + return __truncated | (__z < __x) | (static_cast<__ckd_intmax_t>(__z) < 0); + case 5: // s = u + s + __y ^= std::numeric_limits<__ckd_intmax_t>::min(); + return __truncated | (__x + __y < __y); + case 6: // s = s + u + __x ^= std::numeric_limits<__ckd_intmax_t>::min(); + return __truncated | (__x + __y < __x); + case 7: // s = s + s + return __truncated | + (static_cast<__ckd_intmax_t>((__z ^ __x) & + (__z ^ __y)) < 0); + default: + for (;;) (void)0; + } +} + +template +inline constexpr bool ckd_sub(__T *__res, __U __a, __V __b) { + static_assert(std::is_integral<__T>::value && + std::is_integral<__U>::value && + std::is_integral<__V>::value, + "non-integral types not allowed"); + static_assert(!std::is_same<__T, bool>::value && + !std::is_same<__U, bool>::value && + !std::is_same<__V, bool>::value, + "checked booleans not supported"); + static_assert(!std::is_same<__T, char>::value && + !std::is_same<__U, char>::value && + !std::is_same<__V, char>::value, + "unqualified char type is ambiguous"); + __ckd_uintmax_t __x = __a; + __ckd_uintmax_t __y = __b; + __ckd_uintmax_t __z = __x - __y; + *__res = __z; + if constexpr (sizeof(__z) > sizeof(__U) && sizeof(__z) > sizeof(__V)) { + if constexpr (sizeof(__z) > sizeof(__T) || std::is_signed<__T>::value) { + return static_cast<__ckd_intmax_t>(__z) != static_cast<__T>(__z); + } else if (!std::is_same<__T, __ckd_uintmax_t>::value) { + return (__z != static_cast<__T>(__z) || + ((std::is_signed<__U>::value || + std::is_signed<__V>::value) && + static_cast<__ckd_intmax_t>(__z) < 0)); + } + } + bool __truncated = false; + if constexpr (sizeof(__T) < sizeof(__ckd_intmax_t)) { + __truncated = __z != static_cast<__ckd_uintmax_t>(static_cast<__T>(__z)); + } + switch (std::is_signed<__T>::value << 2 | // + std::is_signed<__U>::value << 1 | // + std::is_signed<__V>::value) { + case 0: // u = u - u + return __truncated | (__x < __y); + case 1: // u = u - s + __y ^= std::numeric_limits<__ckd_intmax_t>::min(); + return __truncated | + (static_cast<__ckd_intmax_t>((__x ^ __y) & + (__z ^ __x)) < 0); + case 2: // u = s - u + return __truncated | (__y > __x) | (static_cast<__ckd_intmax_t>(__x) < 0); + case 3: // u = s - s + return __truncated | + (static_cast<__ckd_intmax_t>(((__z & __x) & __y) | + ((__z | __x) & ~__y)) < 0); + case 4: // s = u - u + return __truncated | + ((__x < __y) ^ (static_cast<__ckd_intmax_t>(__z) < 0)); + case 5: // s = u - s + __y ^= std::numeric_limits<__ckd_intmax_t>::min(); + return __truncated | (__x >= __y); + case 6: // s = s - u + __x ^= std::numeric_limits<__ckd_intmax_t>::min(); + return __truncated | (__x < __y); + case 7: // s = s - s + return __truncated | + (static_cast<__ckd_intmax_t>((__x ^ __y) & + (__z ^ __x)) < 0); + default: + for (;;) (void)0; + } +} + +template +inline constexpr bool ckd_mul(__T *__res, __U __a, __V __b) { + static_assert(std::is_integral<__T>::value && + std::is_integral<__U>::value && + std::is_integral<__V>::value, + "non-integral types not allowed"); + static_assert(!std::is_same<__T, bool>::value && + !std::is_same<__U, bool>::value && + !std::is_same<__V, bool>::value, + "checked booleans not supported"); + static_assert(!std::is_same<__T, char>::value && + !std::is_same<__U, char>::value && + !std::is_same<__V, char>::value, + "unqualified char type is ambiguous"); + __ckd_uintmax_t __x = __a; + __ckd_uintmax_t __y = __b; + if constexpr ((sizeof(__U) * 8 - std::is_signed<__U>::value) + + (sizeof(__V) * 8 - std::is_signed<__V>::value) <= + (sizeof(__T) * 8 - std::is_signed<__T>::value)) { + if constexpr (sizeof(__ckd_uintmax_t) > sizeof(__T) || std::is_signed<__T>::value) { + __ckd_intmax_t __z = __x * __y; + return __z != (*__res = __z); + } else if (!std::is_same<__T, __ckd_uintmax_t>::value) { + __ckd_uintmax_t __z = __x * __y; + *__res = __z; + return (__z != static_cast<__T>(__z) || + ((std::is_signed<__U>::value || + std::is_signed<__V>::value) && + static_cast<__ckd_intmax_t>(__z) < 0)); + } + } + switch (std::is_signed<__T>::value << 2 | // + std::is_signed<__U>::value << 1 | // + std::is_signed<__V>::value) { + case 0: { // u = u * u + __ckd_uintmax_t __z = __x * __y; + int __o = __x && __z / __x != __y; + *__res = __z; + return __o | (sizeof(__T) < sizeof(__z) && + __z != static_cast<__ckd_uintmax_t>(*__res)); + } + case 1: { // u = u * s + __ckd_uintmax_t __z = __x * __y; + int __o = __x && __z / __x != __y; + *__res = __z; + return (__o | ((static_cast<__ckd_intmax_t>(__y) < 0) & !!__x) | + (sizeof(__T) < sizeof(__z) && + __z != static_cast<__ckd_uintmax_t>(*__res))); + } + case 2: { // u = s * u + __ckd_uintmax_t __z = __x * __y; + int __o = __x && __z / __x != __y; + *__res = __z; + return (__o | ((static_cast<__ckd_intmax_t>(__x) < 0) & !!__y) | + (sizeof(__T) < sizeof(__z) && + __z != static_cast<__ckd_uintmax_t>(*__res))); + } + case 3: { // u = s * s + int __o = false; + if (static_cast<__ckd_intmax_t>(__x & __y) < 0) { + __x = -__x; + __y = -__y; + } else if (static_cast<__ckd_intmax_t>(__x ^ __y) < 0) { + __o = __x && __y; + } + __ckd_uintmax_t __z = __x * __y; + __o |= __x && __z / __x != __y; + *__res = __z; + return __o | (sizeof(__T) < sizeof(__z) && + __z != static_cast<__ckd_uintmax_t>(*__res)); + } + case 4: { // s = u * u + __ckd_uintmax_t __z = __x * __y; + int __o = __x && __z / __x != __y; + *__res = __z; + return (__o | (static_cast<__ckd_intmax_t>(__z) < 0) | + (sizeof(__T) < sizeof(__z) && + __z != static_cast<__ckd_uintmax_t>(*__res))); + } + case 5: { // s = u * s + __ckd_uintmax_t __t = -__y; + __t = static_cast<__ckd_intmax_t>(__t) < 0 ? __y : __t; + __ckd_uintmax_t __p = __t * __x; + int __o = __t && __p / __t != __x; + int __n = static_cast<__ckd_intmax_t>(__y) < 0; + __ckd_uintmax_t __z = __n ? -__p : __p; + *__res = __z; + __ckd_uintmax_t __m = std::numeric_limits<__ckd_intmax_t>::max(); + return (__o | (__p > __m + __n) | + (sizeof(__T) < sizeof(__z) && + __z != static_cast<__ckd_uintmax_t>(*__res))); + } + case 6: { // s = s * u + __ckd_uintmax_t __t = -__x; + __t = static_cast<__ckd_intmax_t>(__t) < 0 ? __x : __t; + __ckd_uintmax_t __p = __t * __y; + int __o = __t && __p / __t != __y; + int __n = static_cast<__ckd_intmax_t>(__x) < 0; + __ckd_uintmax_t __z = __n ? -__p : __p; + *__res = __z; + __ckd_uintmax_t __m = std::numeric_limits<__ckd_intmax_t>::max(); + return (__o | (__p > __m + __n) | + (sizeof(__T) < sizeof(__z) && + __z != static_cast<__ckd_uintmax_t>(*__res))); + } + case 7: { // s = s * s + __ckd_uintmax_t __z = __x * __y; + *__res = __z; + return ((((static_cast<__ckd_intmax_t>(__y) < 0) && + (static_cast<__ckd_intmax_t>(__x) == + std::numeric_limits<__ckd_intmax_t>::min())) || + (__y && ((static_cast<__ckd_intmax_t>(__z) / + static_cast<__ckd_intmax_t>(__y)) != + static_cast<__ckd_intmax_t>(__x)))) | + (sizeof(__T) < sizeof(__z) && + __z != static_cast<__ckd_uintmax_t>(*__res))); + } + default: + for (;;) (void)0; + } +} + +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + +#define ckd_add(res, a, b) __ckd_expr(add, (res), (a), (b)) +#define ckd_sub(res, a, b) __ckd_expr(sub, (res), (a), (b)) +#define ckd_mul(res, a, b) __ckd_expr(mul, (res), (a), (b)) + +#if defined(__GNUC__) || defined(__llvm__) +#define __ckd_inline \ + extern __inline __attribute__((__gnu_inline__, \ + __always_inline__, \ + __artificial__)) +#else +#define __ckd_inline static inline +#endif + +#ifdef __ckd_have_int128 +#define __ckd_generic_int128(x, y) , signed __int128: x, unsigned __int128: y +#else +#define __ckd_generic_int128(x, y) +#endif + +#define __ckd_sign(T) \ + ((T)1 << (sizeof(T) * 8 - 1)) + +#define __ckd_is_signed(x) \ + _Generic(x, \ + signed char: 1, \ + unsigned char: 0, \ + signed short: 1, \ + unsigned short: 0, \ + signed int: 1, \ + unsigned int: 0, \ + signed long: 1, \ + unsigned long: 0, \ + signed long long: 1, \ + unsigned long long: 0 \ + __ckd_generic_int128(1, 0)) + +#define __ckd_expr(op, res, a, b) \ + (_Generic(*res, \ + signed char: __ckd_##op##_schar, \ + unsigned char: __ckd_##op##_uchar, \ + signed short: __ckd_##op##_sshort, \ + unsigned short: __ckd_##op##_ushort, \ + signed int: __ckd_##op##_sint, \ + unsigned int: __ckd_##op##_uint, \ + signed long: __ckd_##op##_slong, \ + unsigned long: __ckd_##op##_ulong, \ + signed long long: __ckd_##op##_slonger, \ + unsigned long long: __ckd_##op##_ulonger \ + __ckd_generic_int128( \ + __ckd_##op##_sint128, \ + __ckd_##op##_uint128))( \ + res, a, b, \ + __ckd_is_signed(a), \ + __ckd_is_signed(b))) + +#define __ckd_declare_add(S, T) \ + __ckd_inline char S(void *__res, \ + __ckd_uintmax_t __x, \ + __ckd_uintmax_t __y, \ + char __a_signed, \ + char __b_signed) { \ + __ckd_uintmax_t __z = __x + __y; \ + *(T *)__res = __z; \ + char __truncated = 0; \ + if (sizeof(T) < sizeof(__ckd_intmax_t)) { \ + __truncated = __z != (__ckd_uintmax_t)(T)__z; \ + } \ + switch (__ckd_is_signed((T)0) << 2 | \ + __a_signed << 1 | __b_signed) { \ + case 0: /* u = u + u */ \ + return __truncated | (__z < __x); \ + case 1: /* u = u + s */ \ + __y ^= __ckd_sign(__ckd_uintmax_t); \ + return __truncated | \ + ((__ckd_intmax_t)((__z ^ __x) & \ + (__z ^ __y)) < 0); \ + case 2: /* u = s + u */ \ + __x ^= __ckd_sign(__ckd_uintmax_t); \ + return __truncated | \ + ((__ckd_intmax_t)((__z ^ __x) & \ + (__z ^ __y)) < 0); \ + case 3: /* u = s + s */ \ + return __truncated | \ + ((__ckd_intmax_t)(((__z | __x) & __y) | \ + ((__z & __x) & ~__y)) < 0); \ + case 4: /* s = u + u */ \ + return __truncated | (__z < __x) | ((__ckd_intmax_t)__z < 0); \ + case 5: /* s = u + s */ \ + __y ^= __ckd_sign(__ckd_uintmax_t); \ + return __truncated | (__x + __y < __y); \ + case 6: /* s = s + u */ \ + __x ^= __ckd_sign(__ckd_uintmax_t); \ + return __truncated | (__x + __y < __x); \ + case 7: /* s = s + s */ \ + return __truncated | \ + ((__ckd_intmax_t)((__z ^ __x) & \ + (__z ^ __y)) < 0); \ + default: \ + for (;;) (void)0; \ + } \ + } + +__ckd_declare_add(__ckd_add_schar, signed char) +__ckd_declare_add(__ckd_add_uchar, unsigned char) +__ckd_declare_add(__ckd_add_sshort, signed short) +__ckd_declare_add(__ckd_add_ushort, unsigned short) +__ckd_declare_add(__ckd_add_sint, signed int) +__ckd_declare_add(__ckd_add_uint, unsigned int) +__ckd_declare_add(__ckd_add_slong, signed long) +__ckd_declare_add(__ckd_add_ulong, unsigned long) +__ckd_declare_add(__ckd_add_slonger, signed long long) +__ckd_declare_add(__ckd_add_ulonger, unsigned long long) +#ifdef __ckd_have_int128 +__ckd_declare_add(__ckd_add_sint128, signed __int128) +__ckd_declare_add(__ckd_add_uint128, unsigned __int128) +#endif + +#define __ckd_declare_sub(S, T) \ + __ckd_inline char S(void *__res, \ + __ckd_uintmax_t __x, \ + __ckd_uintmax_t __y, \ + char __a_signed, \ + char __b_signed) { \ + __ckd_uintmax_t __z = __x - __y; \ + *(T *)__res = __z; \ + char __truncated = 0; \ + if (sizeof(T) < sizeof(__ckd_intmax_t)) { \ + __truncated = __z != (__ckd_uintmax_t)(T)__z; \ + } \ + switch (__ckd_is_signed((T)0) << 2 | \ + __a_signed << 1 | __b_signed) { \ + case 0: /* u = u - u */ \ + return __truncated | (__x < __y); \ + case 1: /* u = u - s */ \ + __y ^= __ckd_sign(__ckd_uintmax_t); \ + return __truncated | \ + ((__ckd_intmax_t)((__x ^ __y) & \ + (__z ^ __x)) < 0); \ + case 2: /* u = s - u */ \ + return __truncated | (__y > __x) | ((__ckd_intmax_t)__x < 0); \ + case 3: /* u = s - s */ \ + return __truncated | \ + ((__ckd_intmax_t)(((__z & __x) & __y) | \ + ((__z | __x) & ~__y)) < 0); \ + case 4: /* s = u - u */ \ + return __truncated | ((__x < __y) ^ ((__ckd_intmax_t)__z < 0)); \ + case 5: /* s = u - s */ \ + __y ^= __ckd_sign(__ckd_uintmax_t); \ + return __truncated | (__x >= __y); \ + case 6: /* s = s - u */ \ + __x ^= __ckd_sign(__ckd_uintmax_t); \ + return __truncated | (__x < __y); \ + case 7: /* s = s - s */ \ + return __truncated | \ + ((__ckd_intmax_t)((__x ^ __y) & \ + (__z ^ __x)) < 0); \ + default: \ + for (;;) (void)0; \ + } \ + } + +__ckd_declare_sub(__ckd_sub_schar, signed char) +__ckd_declare_sub(__ckd_sub_uchar, unsigned char) +__ckd_declare_sub(__ckd_sub_sshort, signed short) +__ckd_declare_sub(__ckd_sub_ushort, unsigned short) +__ckd_declare_sub(__ckd_sub_sint, signed int) +__ckd_declare_sub(__ckd_sub_uint, unsigned int) +__ckd_declare_sub(__ckd_sub_slong, signed long) +__ckd_declare_sub(__ckd_sub_ulong, unsigned long) +__ckd_declare_sub(__ckd_sub_slonger, signed long long) +__ckd_declare_sub(__ckd_sub_ulonger, unsigned long long) +#ifdef __ckd_have_int128 +__ckd_declare_sub(__ckd_sub_sint128, signed __int128) +__ckd_declare_sub(__ckd_sub_uint128, unsigned __int128) +#endif + +#define __ckd_declare_mul(S, T) \ + __ckd_inline char S(void *__res, \ + __ckd_uintmax_t __x, \ + __ckd_uintmax_t __y, \ + char __a_signed, \ + char __b_signed) { \ + switch (__ckd_is_signed((T)0) << 2 | \ + __a_signed << 1 | __b_signed) { \ + case 0: { /* u = u * u */ \ + __ckd_uintmax_t __z = __x * __y; \ + int __o = __x && __z / __x != __y; \ + *(T *)__res = __z; \ + return __o | (sizeof(T) < sizeof(__z) && \ + __z != (__ckd_uintmax_t)*(T *)__res); \ + } \ + case 1: { /* u = u * s */ \ + __ckd_uintmax_t __z = __x * __y; \ + int __o = __x && __z / __x != __y; \ + *(T *)__res = __z; \ + return (__o | (((__ckd_intmax_t)__y < 0) & !!__x) | \ + (sizeof(T) < sizeof(__z) && \ + __z != (__ckd_uintmax_t)*(T *)__res)); \ + } \ + case 2: { /* u = s * u */ \ + __ckd_uintmax_t __z = __x * __y; \ + int __o = __x && __z / __x != __y; \ + *(T *)__res = __z; \ + return (__o | (((__ckd_intmax_t)__x < 0) & !!__y) | \ + (sizeof(T) < sizeof(__z) && \ + __z != (__ckd_uintmax_t)*(T *)__res)); \ + } \ + case 3: { /* u = s * s */ \ + int __o = 0; \ + if ((__ckd_intmax_t)(__x & __y) < 0) { \ + __x = -__x; \ + __y = -__y; \ + } else if ((__ckd_intmax_t)(__x ^ __y) < 0) { \ + __o = __x && __y; \ + } \ + __ckd_uintmax_t __z = __x * __y; \ + __o |= __x && __z / __x != __y; \ + *(T *)__res = __z; \ + return __o | (sizeof(T) < sizeof(__z) && \ + __z != (__ckd_uintmax_t)*(T *)__res); \ + } \ + case 4: { /* s = u * u */ \ + __ckd_uintmax_t __z = __x * __y; \ + int __o = __x && __z / __x != __y; \ + *(T *)__res = __z; \ + return (__o | ((__ckd_intmax_t)(__z) < 0) | \ + (sizeof(T) < sizeof(__z) && \ + __z != (__ckd_uintmax_t)*(T *)__res)); \ + } \ + case 5: { /* s = u * s */ \ + __ckd_uintmax_t __t = -__y; \ + __t = (__ckd_intmax_t)(__t) < 0 ? __y : __t; \ + __ckd_uintmax_t __p = __t * __x; \ + int __o = __t && __p / __t != __x; \ + int __n = (__ckd_intmax_t)__y < 0; \ + __ckd_uintmax_t __z = __n ? -__p : __p; \ + *(T *)__res = __z; \ + __ckd_uintmax_t __m = __ckd_sign(__ckd_uintmax_t) - 1; \ + return (__o | (__p > __m + __n) | \ + (sizeof(T) < sizeof(__z) && \ + __z != (__ckd_uintmax_t)*(T *)__res)); \ + } \ + case 6: { /* s = s * u */ \ + __ckd_uintmax_t __t = -__x; \ + __t = (__ckd_intmax_t)(__t) < 0 ? __x : __t; \ + __ckd_uintmax_t __p = __t * __y; \ + int __o = __t && __p / __t != __y; \ + int __n = (__ckd_intmax_t)__x < 0; \ + __ckd_uintmax_t __z = __n ? -__p : __p; \ + *(T *)__res = __z; \ + __ckd_uintmax_t __m = __ckd_sign(__ckd_uintmax_t) - 1; \ + return (__o | (__p > __m + __n) | \ + (sizeof(T) < sizeof(__z) && \ + __z != (__ckd_uintmax_t)*(T *)__res)); \ + } \ + case 7: { /* s = s * s */ \ + __ckd_uintmax_t __z = __x * __y; \ + *(T *)__res = __z; \ + return (((((__ckd_intmax_t)__y < 0) && \ + (__x == __ckd_sign(__ckd_uintmax_t))) || \ + (__y && (((__ckd_intmax_t)__z / \ + (__ckd_intmax_t)__y) != \ + (__ckd_intmax_t)__x))) | \ + (sizeof(T) < sizeof(__z) && \ + __z != (__ckd_uintmax_t)*(T *)__res)); \ + } \ + default: \ + for (;;) (void)0; \ + } \ + } + +__ckd_declare_mul(__ckd_mul_schar, signed char) +__ckd_declare_mul(__ckd_mul_uchar, unsigned char) +__ckd_declare_mul(__ckd_mul_sshort, signed short) +__ckd_declare_mul(__ckd_mul_ushort, unsigned short) +__ckd_declare_mul(__ckd_mul_sint, signed int) +__ckd_declare_mul(__ckd_mul_uint, unsigned int) +__ckd_declare_mul(__ckd_mul_slong, signed long) +__ckd_declare_mul(__ckd_mul_ulong, unsigned long) +__ckd_declare_mul(__ckd_mul_slonger, signed long long) +__ckd_declare_mul(__ckd_mul_ulonger, unsigned long long) +#ifdef __ckd_have_int128 +__ckd_declare_mul(__ckd_mul_sint128, signed __int128) +__ckd_declare_mul(__ckd_mul_uint128, unsigned __int128) +#endif + +#else +#pragma message "checked integer arithmetic unsupported in this environment" + +#define ckd_add(res, x, y) (*(res) = (x) + (y), 0) +#define ckd_sub(res, x, y) (*(res) = (x) - (y), 0) +#define ckd_mul(res, x, y) (*(res) = (x) * (y), 0) + +#endif /* GNU */ + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#endif /* stdckdint.h */ +#endif /* JTCKDINT_H_ */ diff --git a/include/flux/core/numeric.hpp b/include/flux/core/numeric.hpp index 4e41f1e9..e7110957 100644 --- a/include/flux/core/numeric.hpp +++ b/include/flux/core/numeric.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ template struct unchecked_cast_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(From from) const noexcept -> To { return static_cast(from); @@ -53,6 +55,7 @@ template struct overflowing_cast_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(From from) const noexcept -> overflow_result { if constexpr (requires { To{from}; }) { @@ -67,15 +70,20 @@ template struct checked_cast_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(From from, std::source_location loc = std::source_location::current()) const -> To { - auto r = overflowing_cast_fn{}(from); - if (r.overflowed) { - runtime_error("checked_cast failed", loc); + if constexpr (requires { To{from}; }) { + return To{from}; + } else { + if (std::in_range(from)) { + return static_cast(from); + } else { + runtime_error("checked_cast failed", loc); + } } - return r.value; } }; @@ -83,6 +91,7 @@ template struct cast_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(From from, std::source_location loc = std::source_location::current()) const -> To @@ -98,6 +107,7 @@ struct cast_fn { struct unchecked_add_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> T { return static_cast(lhs + rhs); @@ -107,6 +117,7 @@ struct unchecked_add_fn { struct unchecked_sub_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> T { return static_cast(lhs - rhs); @@ -116,6 +127,7 @@ struct unchecked_sub_fn { struct unchecked_mul_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> T { return static_cast(lhs * rhs); @@ -125,6 +137,7 @@ struct unchecked_mul_fn { struct unchecked_div_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> T { return static_cast(lhs / rhs); @@ -134,6 +147,7 @@ struct unchecked_div_fn { struct unchecked_mod_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> T { return static_cast(lhs % rhs); @@ -143,6 +157,7 @@ struct unchecked_mod_fn { struct unchecked_shl_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, U rhs) const noexcept -> T { return static_cast(lhs << rhs); @@ -152,6 +167,7 @@ struct unchecked_shl_fn { struct unchecked_shr_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, U rhs) const noexcept -> T { return static_cast(lhs >> rhs); @@ -161,6 +177,7 @@ struct unchecked_shr_fn { struct unchecked_neg_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T val) const noexcept -> T { return static_cast(-val); @@ -170,193 +187,111 @@ struct unchecked_neg_fn { struct wrapping_add_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> T { - using U = std::make_unsigned_t; - return static_cast(static_cast(lhs) + static_cast(rhs)); + T r; + (void) ckd_add(&r, lhs, rhs); + return r; } }; struct wrapping_sub_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> T { - using U = std::make_unsigned_t; - return static_cast(static_cast(lhs) - static_cast(rhs)); + T r; + (void) ckd_sub(&r, lhs, rhs); + return r; } }; struct wrapping_mul_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> T { - using U = std::conditional_t<(sizeof(T) < sizeof(unsigned)), - unsigned, - std::make_unsigned_t>; - return static_cast(static_cast(lhs) * static_cast(rhs)); + T r; + (void) ckd_mul(&r, lhs, rhs); + return r; } }; struct wrapping_neg_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T val) const noexcept -> T { - using U = std::make_unsigned_t; - return static_cast(static_cast(0) - static_cast(val)); + T r; + (void) ckd_sub(&r, T{0}, val); + return r; } }; -#if defined(__has_builtin) -# if __has_builtin(__builtin_add_overflow) && \ - __has_builtin(__builtin_sub_overflow) && \ - __has_builtin(__builtin_mul_overflow) -# define FLUX_HAVE_BUILTIN_OVERFLOW_OPS 1 -# endif -#endif - -#ifdef FLUX_HAVE_BUILTIN_OVERFLOW_OPS -inline constexpr bool use_builtin_overflow_ops = true; -#else -inline constexpr bool use_builtin_overflow_ops = false; -#endif - -#undef FLUX_HAVE_BUILTIN_OVERFLOW_OPS - struct overflowing_add_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> overflow_result { - if constexpr (use_builtin_overflow_ops) { - bool o = __builtin_add_overflow(lhs, rhs, &lhs); - return {lhs, o}; - } else { - T value = wrapping_add_fn{}(lhs, rhs); - if constexpr (signed_integral) { - bool o = ((lhs < T{}) == (rhs < T{})) && ((lhs < T{}) != (value < T{})); - return {value, o}; - } else { - return {value, value < lhs}; - } - } + T r; + bool o = ckd_add(&r, lhs, rhs); + return {r, o}; } }; struct overflowing_sub_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> overflow_result { - if constexpr (use_builtin_overflow_ops) { - bool o = __builtin_sub_overflow(lhs, rhs, &lhs); - return {lhs, o}; - } else { - T value = wrapping_sub_fn{}(lhs, rhs); - if constexpr (signed_integral) { - bool o = (lhs >= T{} && rhs < T{} && value < T{}) || - (lhs < T{} && rhs > T{} && value > T{}); - return {value, o}; - } else { - return {value, rhs > lhs}; - } - } - } -}; - -template struct builtin_sized_int {}; -template <> struct builtin_sized_int<2, true> { using type = std::int16_t; }; -template <> struct builtin_sized_int<2, false> { using type = std::uint16_t; }; -template <> struct builtin_sized_int<4, true> { using type = std::int32_t; }; -template <> struct builtin_sized_int<4, false> { using type = std::uint32_t; }; -template <> struct builtin_sized_int<8, true> { using type = std::int64_t; }; -template <> struct builtin_sized_int<8, false> { using type = std::uint64_t; }; - -template -using builtin_sized_int_t = typename builtin_sized_int::type; - -template -struct overflowing_mul_impl; - -// If we have a builtin that is big enough to hold the result of the -// multiplication, use it -template - requires requires { typename builtin_sized_int_t<2 * sizeof(T), signed_integral>; } -struct overflowing_mul_impl { - inline constexpr auto operator()(T lhs, T rhs) const -> overflow_result - { - using U = builtin_sized_int_t<2 * sizeof(T), signed_integral>; - auto result = static_cast(lhs) * static_cast(rhs); - return overflowing_cast_fn{}(result); - } -}; - -// Otherwise, fall back to checking for overflow via a division operation -template -struct overflowing_mul_impl { - inline constexpr auto operator()(T lhs, T rhs) const -> overflow_result - { - constexpr T min = std::numeric_limits::lowest(); - T value = wrapping_mul_fn{}(lhs, rhs); - if constexpr (signed_integral) { - bool o = (lhs == T{-1} && rhs == min) || - (lhs != T{} && unchecked_div_fn{}(value, lhs) != rhs); - return {value, o}; - } else { - bool o = lhs != T{} && unchecked_div_fn{}(value, lhs) != rhs; - return {value, o}; - } + T r; + bool o = ckd_sub(&r, lhs, rhs); + return {r, o}; } }; struct overflowing_mul_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs) const noexcept -> overflow_result { - if constexpr (detail::use_builtin_overflow_ops) { - bool o = __builtin_mul_overflow(lhs, rhs, &lhs); - return {lhs, o}; - } else { - return overflowing_mul_impl{}(lhs, rhs); - } + T r; + bool o = ckd_mul(&r, lhs, rhs); + return {r, o}; } }; struct overflowing_neg_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T val) const noexcept -> overflow_result { - if constexpr (use_builtin_overflow_ops) { - bool o = __builtin_sub_overflow(T{0}, val, &val); - return {val, o}; - } else { - return {wrapping_neg_fn{}(val), val == std::numeric_limits::lowest()}; - } + T r; + bool o = ckd_sub(&r, T{0}, val); + return {r, o}; } }; struct checked_add_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs, std::source_location loc = std::source_location::current()) const -> T { - // For built-in signed types at least as large as int, - // constant evaluation already checks for overflow - if (signed_integral && (sizeof(T) >= sizeof(int)) && - std::is_constant_evaluated()) { - return unchecked_add_fn{}(lhs, rhs); // LCOV_EXCL_LINE + if (T r; !ckd_add(&r, lhs, rhs)) { + return r; } else { - auto result = overflowing_add_fn{}(lhs, rhs); - if (result.overflowed) { - runtime_error("overflow in addition", loc); - } - return result.value; + runtime_error("overflow in addition", loc); } } }; @@ -364,19 +299,15 @@ struct checked_add_fn { struct checked_sub_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs, std::source_location loc = std::source_location::current()) const -> T { - if (signed_integral && (sizeof(T) >= sizeof(int)) && - std::is_constant_evaluated()) { - return unchecked_sub_fn{}(lhs, rhs); // LCOV_EXCL_LINE + if (T r; !ckd_sub(&r, lhs, rhs)) { + return r; } else { - auto result = overflowing_sub_fn{}(lhs, rhs); - if (result.overflowed) { - runtime_error("overflow in subtraction", loc); - } - return result.value; + runtime_error("overflow in subtraction", loc); } } }; @@ -384,19 +315,15 @@ struct checked_sub_fn { struct checked_mul_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs, std::source_location loc = std::source_location::current()) const -> T { - if (signed_integral && (sizeof(T) >= sizeof(int)) && - std::is_constant_evaluated()) { - return unchecked_mul_fn{}(lhs, rhs); // LCOV_EXCL_LINE + if (T r; !ckd_mul(&r, lhs, rhs)) { + return r; } else { - auto result = overflowing_mul_fn{}(lhs, rhs); - if (result.overflowed) { - runtime_error("overflow in multiplication", loc); - } - return result.value; + runtime_error("overflow in multiplication", loc); } } }; @@ -406,6 +333,7 @@ template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs, std::source_location loc = std::source_location::current()) const -> T @@ -435,6 +363,7 @@ template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, T rhs, std::source_location loc = std::source_location::current()) const -> T @@ -460,6 +389,7 @@ struct checked_mod_fn { struct checked_shl_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, U rhs, std::source_location loc = std::source_location::current()) const -> T @@ -478,6 +408,7 @@ struct checked_shl_fn { struct checked_shr_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T lhs, U rhs, std::source_location loc = std::source_location::current()) const -> T @@ -494,15 +425,16 @@ struct checked_shr_fn { struct checked_neg_fn { template [[nodiscard]] + FLUX_ALWAYS_INLINE constexpr auto operator()(T val, std::source_location loc = std::source_location::current()) const -> T { - auto [r, o] = overflowing_neg_fn{}(val); - if (o) { - flux::runtime_error("Overflow in signed negation", loc); + if (T r; !ckd_sub(&r, T{0}, val)) { + return r; + } else { + runtime_error("overflow in signed negation", loc); } - return r; } };