From 1ca471e766557f1063eef55adf0defd6dc2a49f4 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Oct 2026 15:54:40 -0400 Subject: [PATCH 1/4] Consolidate and replace all configuration macros --- include/beman/big_int/basic_big_int.hpp | 18 +- .../beman/big_int/detail/base_conversion.hpp | 2 +- include/beman/big_int/detail/config.hpp | 246 ++++++++++++------ include/beman/big_int/detail/div_impl.hpp | 6 +- include/beman/big_int/detail/floats.hpp | 24 +- include/beman/big_int/detail/mul_impl.hpp | 6 +- include/beman/big_int/detail/ntt_fp.hpp | 4 +- .../big_int/detail/scratch_allocator.hpp | 2 +- include/beman/big_int/detail/wide_ops.hpp | 28 +- include/beman/big_int/format.hpp | 8 +- include/beman/big_int/string.hpp | 2 +- module/big_int.cppm | 4 +- tests/beman/big_int/allocation.test.cpp | 2 +- .../beman/big_int/basic_construction.test.cpp | 6 +- tests/beman/big_int/format.test.cpp | 4 +- .../perf/small_value_optimization.gbench.cpp | 8 +- tests/beman/big_int/pmr.test.cpp | 2 +- 17 files changed, 222 insertions(+), 150 deletions(-) diff --git a/include/beman/big_int/basic_big_int.hpp b/include/beman/big_int/basic_big_int.hpp index 5bc0337b..7de383de 100644 --- a/include/beman/big_int/basic_big_int.hpp +++ b/include/beman/big_int/basic_big_int.hpp @@ -22,10 +22,6 @@ #include #include #include - - #if __has_include() - #include - #endif #endif // BEMAN_BIG_INT_BUILD_MODULE #include @@ -123,7 +119,7 @@ template inline constexpr bool is_implicit_constructible_from = detail::signed_or_unsigned> || std::is_same_v, basic_big_int>; -#if defined(__cpp_lib_allocate_at_least) && __cpp_lib_allocate_at_least >= 202302L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST using std::allocation_result; #else template @@ -131,7 +127,7 @@ struct allocation_result { Pointer ptr; SizeType count; }; -#endif // __cpp_lib_allocate_at_least +#endif // BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST [[noreturn]] inline void throw_length_error() { #ifdef BEMAN_BIG_INT_ALLOW_EXCEPTIONS @@ -448,7 +444,7 @@ class BEMAN_BIG_INT_TRIVIAL_ABI basic_big_int { requires detail::signed_or_unsigned> constexpr basic_big_int(I begin, S end, const allocator_type& a = allocator_type()); -#if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES template requires detail::signed_or_unsigned> constexpr basic_big_int(std::from_range_t, R&& r, const allocator_type& a = allocator_type()) @@ -3387,7 +3383,7 @@ template constexpr auto basic_big_int::alloc_limbs_from(allocator_type& a, const size_type n) -> alloc_result { BEMAN_BIG_INT_ASSERT(n != 0); check_length(n); -#if defined(__cpp_lib_allocate_at_least) && __cpp_lib_allocate_at_least >= 202302L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST if constexpr (detail::traits_has_allocate_at_least) { return alloc_traits::allocate_at_least(a, n); } else { @@ -3459,18 +3455,18 @@ basic_big_int::copy_n_to_allocation(const limb_type* const p, const siz BEMAN_BIG_INT_ASSERT(p != nullptr); BEMAN_BIG_INT_ASSERT(out.ptr != nullptr); BEMAN_BIG_INT_ASSERT(n <= out.count); -// If __cpp_lib_raw_memory_algorithms is available, +// If the constexpr raw memory algorithms are available, // we don't need to differentiate between constant evaluation and runtime. // Even when we need this fallback case, // it is always important that all elements in the allocation are initialized // because we don't keep track of "requested" vs "received" capacity // (these may not be the same with allocate_at_least). -#if !defined(__cpp_lib_raw_memory_algorithms) || __cpp_lib_raw_memory_algorithms < 202411L +#ifndef BEMAN_BIG_INT_HAS_CPP_LIB_RAW_MEMORY_ALGORITHMS if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { #endif std::uninitialized_copy_n(p, n, out.ptr); std::uninitialized_value_construct_n(out.ptr + n, out.count - n); -#if !defined(__cpp_lib_raw_memory_algorithms) || __cpp_lib_raw_memory_algorithms < 202411L +#ifndef BEMAN_BIG_INT_HAS_CPP_LIB_RAW_MEMORY_ALGORITHMS } else { for (size_type i = 0; i < n; ++i) { std::construct_at(out.ptr + i, p[i]); diff --git a/include/beman/big_int/detail/base_conversion.hpp b/include/beman/big_int/detail/base_conversion.hpp index 07ca4f57..592f6481 100644 --- a/include/beman/big_int/detail/base_conversion.hpp +++ b/include/beman/big_int/detail/base_conversion.hpp @@ -78,7 +78,7 @@ namespace detail { // only, also used for other AArch64 cores and MSVC ARM64): leaves of 16 chunks beat 32 by 5.5% on average. #if defined(BEMAN_BIG_INT_ARCH_AARCH64) inline constexpr std::size_t fast_input_basecase_chunks = 16; -#elif defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) +#elif defined(BEMAN_BIG_INT_TARGET_X86_64) inline constexpr std::size_t fast_input_basecase_chunks = 512; #else inline constexpr std::size_t fast_input_basecase_chunks = 32; diff --git a/include/beman/big_int/detail/config.hpp b/include/beman/big_int/detail/config.hpp index 94656168..c04a8a03 100644 --- a/include/beman/big_int/detail/config.hpp +++ b/include/beman/big_int/detail/config.hpp @@ -4,6 +4,157 @@ #ifndef BEMAN_BIG_INT_CONFIG_HPP #define BEMAN_BIG_INT_CONFIG_HPP +#if __has_include() + +#include + +#else + +// Feature detection =========================================================== + +// Every feature-test, compiler-extension, and target-architecture macro the +// library branches on is tested here and nowhere else; the rest of the library +// checks only the BEMAN_BIG_INT_HAS_* and BEMAN_BIG_INT_TARGET_* spellings below. +// Each is defined to 1 when the feature is available and left undefined otherwise. + +// Guarding these includes is safe only because the .cppm supplies them in its +// global module fragment before the purview include of this header. +#ifndef BEMAN_BIG_INT_BUILD_MODULE + #include // for BITINT_MAXWIDTH + #include // for INTPTR_MAX + #include // for __cpp_lib_* +#endif + +// Language features + +#if defined(__cpp_if_consteval) && __cpp_if_consteval >= 202106L + #define BEMAN_BIG_INT_HAS_CPP_IF_CONSTEVAL 1 +#endif + +// Library features + +#if defined(__cpp_lib_allocate_at_least) && __cpp_lib_allocate_at_least >= 202302L + #define BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST 1 +#endif + +#if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L + #define BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH 1 +#endif + +#if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L + #define BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES 1 +#endif + +#if __has_include() && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L + #define BEMAN_BIG_INT_HAS_CPP_LIB_FORMAT 1 +#endif + +// 202411L is the revision that makes the uninitialized memory algorithms constexpr. +#if defined(__cpp_lib_raw_memory_algorithms) && __cpp_lib_raw_memory_algorithms >= 202411L + #define BEMAN_BIG_INT_HAS_CPP_LIB_RAW_MEMORY_ALGORITHMS 1 +#endif + +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + #define BEMAN_BIG_INT_HAS_CPP_LIB_STRING_RESIZE_AND_OVERWRITE 1 +#endif + +// Extended floating-point types + +#if __has_include() + #define BEMAN_BIG_INT_HAS_STDFLOAT 1 +#endif + +#ifdef __STDCPP_FLOAT16_T__ + #define BEMAN_BIG_INT_HAS_STDCPP_FLOAT16_T 1 +#endif + +#ifdef __STDCPP_BFLOAT16_T__ + #define BEMAN_BIG_INT_HAS_STDCPP_BFLOAT16_T 1 +#endif + +#ifdef __STDCPP_FLOAT128_T__ + #define BEMAN_BIG_INT_HAS_STDCPP_FLOAT128_T 1 +#endif + +// Bit-precise integers (_BitInt) + +#ifdef BITINT_MAXWIDTH + // Once _BitInt is a standard feature and available on all compilers, + // this case should be selected for all compilers. + #define BEMAN_BIG_INT_HAS_BITINT 1 + #define BEMAN_BIG_INT_BITINT_MAXWIDTH BITINT_MAXWIDTH +#elif defined(__BITINT_MAXWIDTH__) + // This case is for Clang when it provides _BitInt as an extension. + #define BEMAN_BIG_INT_HAS_BITINT 1 + #define BEMAN_BIG_INT_HAS_BITINT_EXTENSION 1 + #define BEMAN_BIG_INT_BITINT_MAXWIDTH __BITINT_MAXWIDTH__ +#else + // Prevent warnings for use of undefined macros. + #define BEMAN_BIG_INT_BITINT_MAXWIDTH 0 +#endif // BITINT_MAXWIDTH + +// Workaround for Clang-19 ICE past 128 bits, even though it reports far more than that +// Crashes in EmitAutoVarInit; fixed in Clang 20 by https://github.com/llvm/llvm-project/pull/112218 +#if defined(__clang__) && __clang_major__ == 19 && BEMAN_BIG_INT_BITINT_MAXWIDTH > 128 + #undef BEMAN_BIG_INT_BITINT_MAXWIDTH + #define BEMAN_BIG_INT_BITINT_MAXWIDTH 128 +#endif + +// GNU __int128 + +#ifdef __SIZEOF_INT128__ + #define BEMAN_BIG_INT_HAS_INT128_EXTENSION 1 +#endif + +// Exceptions + +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || defined(__EXCEPTIONS) + #define BEMAN_BIG_INT_ALLOW_EXCEPTIONS +#else + #define BEMAN_BIG_INT_NO_EXCEPTIONS +#endif + +// Target architecture + +#if defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) + #define BEMAN_BIG_INT_TARGET_X86_64 1 +#endif + +#if defined(__i386__) || defined(_M_IX86) + #define BEMAN_BIG_INT_TARGET_X86_32 1 +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) + #define BEMAN_BIG_INT_TARGET_AARCH64 1 +#endif + +#if defined(__wasm__) || defined(__EMSCRIPTEN__) + #define BEMAN_BIG_INT_TARGET_WASM 1 +#endif + +// x86 instruction set extensions enabled at compile time (e.g. -mbmi2 -madx). +// BEMAN_BIG_INT_HAS_AVX512_IFMA is the full AVX-512 set the IFMA kernels need. + +#if defined(__BMI2__) && defined(__ADX__) + #define BEMAN_BIG_INT_HAS_BMI2_ADX 1 +#endif + +#if defined(__AVX512IFMA__) && defined(__AVX512VL__) && defined(__AVX512BW__) && defined(__AVX512VBMI__) + #define BEMAN_BIG_INT_HAS_AVX512_IFMA 1 +#endif + +// Word size + +#if INTPTR_MAX == INT64_MAX || defined(BEMAN_BIG_INT_TARGET_WASM) + #define BEMAN_BIG_INT_WORD_BITS 64 +#elif INTPTR_MAX == INT32_MAX + #define BEMAN_BIG_INT_WORD_BITS 32 +#else + #error Unknown pointer size or missing size macros! +#endif + +#endif // Config Generated + // Module support ============================================================== // `BEMAN_BIG_INT_BUILD_MODULE` is defined by module/big_int.cppm and propagated @@ -137,32 +288,16 @@ #define BEMAN_BIG_INT_DIAGNOSTIC_IGNORED_MSVC(...) #endif -// _BitInt detection =========================================================== - -// Guarding this include is safe only because the .cppm supplies in -// its global module fragment before the purview include below tests -// BITINT_MAXWIDTH. -#ifndef BEMAN_BIG_INT_BUILD_MODULE - #include // for BITINT_MAXWIDTH -#endif - -#ifdef BITINT_MAXWIDTH - // Once _BitInt is a standard feature and available on all compilers, - // this case should be selected for all compilers. - #define BEMAN_BIG_INT_BITINT_MAXWIDTH BITINT_MAXWIDTH - #define BEMAN_BIG_INT_HAS_BITINT 1 -#elif defined(__BITINT_MAXWIDTH__) - // This case is for Clang when it provides _BitInt as an extension. - #define BEMAN_BIG_INT_BITINT_MAXWIDTH __BITINT_MAXWIDTH__ - #define BEMAN_BIG_INT_HAS_BITINT 1 +// _BitInt aliases ============================================================= - // A module consumer receives these aliases from the import, so they are only - // declared in ordinary builds and in the module interface unit itself; - // declaring them again in a consumer would give a second, distinct type. They - // are not marked BEMAN_BIG_INT_EXPORT: these are global-scope aliases, and - // exporting them would inject unqualified `bit_int`/`bit_uint` into every - // importer's global namespace. They only need to be reachable here. - #if !defined(BEMAN_BIG_INT_BUILD_MODULE) || defined(BEMAN_BIG_INT_INTERFACE_UNIT) +// A module consumer receives these aliases from the import, so they are only +// declared in ordinary builds and in the module interface unit itself; +// declaring them again in a consumer would give a second, distinct type. They +// are not marked BEMAN_BIG_INT_EXPORT: these are global-scope aliases, and +// exporting them would inject unqualified `bit_int`/`bit_uint` into every +// importer's global namespace. They only need to be reachable here. +#if defined(BEMAN_BIG_INT_HAS_BITINT_EXTENSION) && \ + (!defined(BEMAN_BIG_INT_BUILD_MODULE) || defined(BEMAN_BIG_INT_INTERFACE_UNIT)) __extension__ template using bit_int = _BitInt(N); @@ -170,18 +305,6 @@ using bit_int = _BitInt(N); __extension__ template using bit_uint = unsigned _BitInt(N); - #endif - -#else - // Prevent warnings for use of undefined macros. - #define BEMAN_BIG_INT_BITINT_MAXWIDTH 0 -#endif // BITINT_MAXWIDTH - -// Workaround for Clang-19 ICE past 128 bits, even though it reports far more than that -// Crashes in EmitAutoVarInit; fixed in Clang 20 by https://github.com/llvm/llvm-project/pull/112218 -#if defined(__clang__) && __clang_major__ == 19 && BEMAN_BIG_INT_BITINT_MAXWIDTH > 128 - #undef BEMAN_BIG_INT_BITINT_MAXWIDTH - #define BEMAN_BIG_INT_BITINT_MAXWIDTH 128 #endif // std::hash reach over bit-precise integers =================================== @@ -193,23 +316,6 @@ using bit_uint = unsigned _BitInt(N); #define BEMAN_BIG_INT_HASH_MAX_OBJECT_WORDS 4 #endif -// 32-bit/64-bit =============================================================== - -// Guarding this include is safe only because the .cppm supplies in -// its global module fragment before the purview include below tests -// INTPTR_MAX. -#ifndef BEMAN_BIG_INT_BUILD_MODULE - #include -#endif - -#if INTPTR_MAX == INT64_MAX || defined(__wasm__) || defined(__EMSCRIPTEN__) - #define BEMAN_BIG_INT_WORD_BITS 64 -#elif INTPTR_MAX == INT32_MAX - #define BEMAN_BIG_INT_WORD_BITS 32 -#else - #error Unknown pointer size or missing size macros! -#endif - // 128-bit integer support ===================================================== #ifdef BEMAN_BIG_INT_MSVC @@ -226,7 +332,7 @@ namespace detail { #define BEMAN_BIG_INT_HAS_INT128_FUNDAMENTAL 1 using int128_t = bit_int<128>; using uint128_t = bit_uint<128>; -#elif defined(__SIZEOF_INT128__) +#elif defined(BEMAN_BIG_INT_HAS_INT128_EXTENSION) #define BEMAN_BIG_INT_HAS_INT128 1 #define BEMAN_BIG_INT_HAS_INT128_FUNDAMENTAL 1 __extension__ using int128_t = __int128; @@ -308,9 +414,7 @@ BEMAN_BIG_INT_END_NAMESPACE // uses the portable kernels instead. Those have C++ linkage, so they cannot // bind to the assembly symbols that CMake still builds into the library. -#if ((defined(BEMAN_BIG_INT_CLANG) && (defined(__x86_64__) || defined(_M_X64))) || \ - (defined(BEMAN_BIG_INT_GCC) && defined(__x86_64__)) || (defined(BEMAN_BIG_INT_MSVC) && defined(_M_X64))) && \ - BEMAN_BIG_INT_LIMB_WIDTH == 64 +#if defined(BEMAN_BIG_INT_TARGET_X86_64) && BEMAN_BIG_INT_LIMB_WIDTH == 64 #define BEMAN_BIG_INT_ARCH_X86_64 #endif @@ -319,7 +423,7 @@ BEMAN_BIG_INT_END_NAMESPACE #undef BEMAN_BIG_INT_X86_64_BMI2_ADX #define BEMAN_BIG_INT_X86_64_BMI2_ADX 0 #elif !defined(BEMAN_BIG_INT_X86_64_BMI2_ADX) - #if defined(__BMI2__) && defined(__ADX__) + #ifdef BEMAN_BIG_INT_HAS_BMI2_ADX #define BEMAN_BIG_INT_X86_64_BMI2_ADX 1 #else #define BEMAN_BIG_INT_X86_64_BMI2_ADX 0 @@ -332,17 +436,14 @@ BEMAN_BIG_INT_END_NAMESPACE #undef BEMAN_BIG_INT_X86_64_AVX512_IFMA #define BEMAN_BIG_INT_X86_64_AVX512_IFMA 0 #elif !defined(BEMAN_BIG_INT_X86_64_AVX512_IFMA) - #if defined(__AVX512IFMA__) && defined(__AVX512VL__) && defined(__AVX512BW__) && defined(__AVX512VBMI__) && \ - defined(__BMI2__) && defined(__ADX__) + #if defined(BEMAN_BIG_INT_HAS_AVX512_IFMA) && defined(BEMAN_BIG_INT_HAS_BMI2_ADX) #define BEMAN_BIG_INT_X86_64_AVX512_IFMA 1 #else #define BEMAN_BIG_INT_X86_64_AVX512_IFMA 0 #endif #endif -#if (((defined(BEMAN_BIG_INT_GCC) || defined(BEMAN_BIG_INT_CLANG)) && defined(__aarch64__)) || \ - (defined(BEMAN_BIG_INT_MSVC) && defined(_M_ARM64))) && \ - BEMAN_BIG_INT_LIMB_WIDTH == 64 +#if defined(BEMAN_BIG_INT_TARGET_AARCH64) && BEMAN_BIG_INT_LIMB_WIDTH == 64 #define BEMAN_BIG_INT_ARCH_AARCH64 #endif @@ -573,12 +674,11 @@ BEMAN_BIG_INT_END_NAMESPACE // Allocator trait detection =================================================== -// `` defines `__cpp_lib_allocate_at_least` #ifndef BEMAN_BIG_INT_BUILD_MODULE #include #endif -#if defined(__cpp_lib_allocate_at_least) && __cpp_lib_allocate_at_least >= 202302L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST BEMAN_BIG_INT_BEGIN_NAMESPACE namespace detail { @@ -594,7 +694,7 @@ concept traits_has_allocate_at_least = requires(Alloc& a, typename Traits::size_ } // namespace detail BEMAN_BIG_INT_END_NAMESPACE -#endif // __cpp_lib_allocate_at_least +#endif // BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST // Trivial ABI ================================================================= @@ -670,7 +770,7 @@ BEMAN_BIG_INT_END_NAMESPACE // if consteval ================================================================ -#if defined(__cpp_if_consteval) && __cpp_if_consteval >= 202106L +#ifdef BEMAN_BIG_INT_HAS_CPP_IF_CONSTEVAL #define BEMAN_BIG_INT_IS_CONSTEVAL consteval #ifdef BEMAN_BIG_INT_MSVC // In MSVC, all code following `if !consteval` is considered unreachable. @@ -745,16 +845,12 @@ BEMAN_BIG_INT_END_NAMESPACE // Exceptions ================================================================== -#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || defined(__EXCEPTIONS) - #ifndef BEMAN_BIG_INT_BUILD_MODULE +#ifndef BEMAN_BIG_INT_BUILD_MODULE + #ifdef BEMAN_BIG_INT_ALLOW_EXCEPTIONS #include - #endif - #define BEMAN_BIG_INT_ALLOW_EXCEPTIONS -#else - #ifndef BEMAN_BIG_INT_BUILD_MODULE + #else #include #endif - #define BEMAN_BIG_INT_NO_EXCEPTIONS #endif // ============================================================================= diff --git a/include/beman/big_int/detail/div_impl.hpp b/include/beman/big_int/detail/div_impl.hpp index aac942dd..0038434a 100644 --- a/include/beman/big_int/detail/div_impl.hpp +++ b/include/beman/big_int/detail/div_impl.hpp @@ -387,7 +387,7 @@ constexpr void divide_unsigned_approx(const std::span #if defined(BEMAN_BIG_INT_ARCH_AARCH64) inline constexpr std::size_t burnikel_ziegler_cutoff = 40; inline constexpr std::size_t burnikel_ziegler_offset = 10; - #elif defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) + #elif defined(BEMAN_BIG_INT_TARGET_X86_64) inline constexpr std::size_t burnikel_ziegler_cutoff = 160; inline constexpr std::size_t burnikel_ziegler_offset = 64; #else @@ -739,7 +739,7 @@ inline constexpr std::size_t barrett_quarter_cutoff = 49152; inline constexpr std::size_t barrett_quarter_cutoff = 24576; #endif inline constexpr std::size_t barrett_balanced_cutoff = 131072; -#elif defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) +#elif defined(BEMAN_BIG_INT_TARGET_X86_64) inline constexpr std::size_t barrett_march_cutoff = 512; inline constexpr std::size_t barrett_march8_cutoff = 4096; #if defined(BEMAN_BIG_INT_SIMD_MUL) @@ -770,7 +770,7 @@ static_assert(barrett_march8_cutoff >= barrett_march_cutoff, // loses from n = 1024 up). #if defined(BEMAN_BIG_INT_ARCH_AARCH64) inline constexpr std::size_t reciprocal_span_cutoff = 64; // M4 Max, 2026-09-30: kept (ties) -#elif defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) +#elif defined(BEMAN_BIG_INT_TARGET_X86_64) inline constexpr std::size_t reciprocal_span_cutoff = 512; #else inline constexpr std::size_t reciprocal_span_cutoff = 64; diff --git a/include/beman/big_int/detail/floats.hpp b/include/beman/big_int/detail/floats.hpp index 0c128720..d6ae5db3 100644 --- a/include/beman/big_int/detail/floats.hpp +++ b/include/beman/big_int/detail/floats.hpp @@ -20,11 +20,9 @@ BEMAN_BIG_INT_DIAGNOSTIC_PUSH() BEMAN_BIG_INT_DIAGNOSTIC_IGNORED_GCC("-Wpadded") -#ifndef BEMAN_BIG_INT_BUILD_MODULE - #if __has_include() - #include - #endif -#endif // BEMAN_BIG_INT_BUILD_MODULE +#if !defined(BEMAN_BIG_INT_BUILD_MODULE) && defined(BEMAN_BIG_INT_HAS_STDFLOAT) + #include +#endif BEMAN_BIG_INT_BEGIN_NAMESPACE namespace detail { @@ -68,7 +66,7 @@ struct ieee_traits { static constexpr bool explicit_int_bit = false; }; -#ifdef __STDCPP_FLOAT16_T__ +#ifdef BEMAN_BIG_INT_HAS_STDCPP_FLOAT16_T template <> struct ieee_traits { using bits_type = std::uint16_t; @@ -81,7 +79,7 @@ struct ieee_traits { }; #endif -#ifdef __STDCPP_BFLOAT16_T__ +#ifdef BEMAN_BIG_INT_HAS_STDCPP_BFLOAT16_T template <> struct ieee_traits { using bits_type = std::uint16_t; @@ -94,7 +92,7 @@ struct ieee_traits { }; #endif -#ifdef __STDCPP_FLOAT128_T__ +#ifdef BEMAN_BIG_INT_HAS_STDCPP_FLOAT128_T template <> struct ieee_traits { using bits_type = uint128_t; @@ -147,7 +145,7 @@ struct ieee_traits : ieee_traits {}; template [[nodiscard]] constexpr bool constexpr_signbit(const F x) noexcept { -#if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::signbit(x); #elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_signbit) return static_cast(__builtin_signbit(x)); @@ -169,7 +167,7 @@ template template [[nodiscard]] constexpr bool constexpr_isfinite(const F x) noexcept { -#if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::isfinite(x); #elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_isfinite) return static_cast(__builtin_isfinite(x)); @@ -188,7 +186,7 @@ template template [[nodiscard]] constexpr F constexpr_copysign(const F x, const F s) noexcept { -#if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::copysign(x, s); #elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_copysign) if constexpr (std::is_same_v) { @@ -239,7 +237,7 @@ template template [[nodiscard]] constexpr F constexpr_ldexp(const F x, const int exp) noexcept { -#if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::ldexp(x, exp); #elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_ldexp) if constexpr (std::is_same_v) { @@ -273,7 +271,7 @@ template template [[nodiscard]] constexpr F constexpr_fabs(const F x) noexcept { -#if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::fabs(x); #elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_fabs) if constexpr (std::is_same_v) { diff --git a/include/beman/big_int/detail/mul_impl.hpp b/include/beman/big_int/detail/mul_impl.hpp index 632990e6..cb15ee84 100644 --- a/include/beman/big_int/detail/mul_impl.hpp +++ b/include/beman/big_int/detail/mul_impl.hpp @@ -787,7 +787,7 @@ inline constexpr std::size_t fft_mul_model_den = 64; inline constexpr std::size_t square_fft_min_limbs = 8000; inline constexpr std::size_t square_fft_model_num = 75; inline constexpr std::size_t square_fft_model_den = 512; - #elif defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) + #elif defined(BEMAN_BIG_INT_TARGET_X86_64) inline constexpr std::size_t fft_mul_min_limbs = 6000; inline constexpr std::size_t fft_mul_model_num = 1; inline constexpr std::size_t fft_mul_model_den = 0; @@ -841,7 +841,7 @@ inline constexpr std::size_t fft_mul_model_den = 64; inline constexpr std::size_t square_fft_min_limbs = 3000; inline constexpr std::size_t square_fft_model_num = 10; inline constexpr std::size_t square_fft_model_den = 64; -#elif defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) +#elif defined(BEMAN_BIG_INT_TARGET_X86_64) inline constexpr std::size_t fft_mul_min_limbs = 24000; inline constexpr std::size_t fft_mul_model_num = 1; inline constexpr std::size_t fft_mul_model_den = 0; @@ -1031,7 +1031,7 @@ inline constexpr std::size_t fft_cyclic_cutoff = 2048; #endif #elif defined(BEMAN_BIG_INT_SIMD_MUL) inline constexpr std::size_t fft_cyclic_cutoff = 8192; -#elif defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) +#elif defined(BEMAN_BIG_INT_TARGET_X86_64) inline constexpr std::size_t fft_cyclic_cutoff = 36864; #else inline constexpr std::size_t fft_cyclic_cutoff = 2048; diff --git a/include/beman/big_int/detail/ntt_fp.hpp b/include/beman/big_int/detail/ntt_fp.hpp index 4b4cf881..321c913c 100644 --- a/include/beman/big_int/detail/ntt_fp.hpp +++ b/include/beman/big_int/detail/ntt_fp.hpp @@ -28,12 +28,12 @@ namespace detail { // Architectures with a hand-written SIMD kernel. // NEON is mandatory baseline on AArch64 (no dispatch); // x86-64 selects AVX2 at runtime, else the scalar kernel. -#if defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) +#ifdef BEMAN_BIG_INT_TARGET_X86_64 #define BEMAN_BIG_INT_NTT_FP_X86 1 #else #define BEMAN_BIG_INT_NTT_FP_X86 0 #endif -#if defined(__aarch64__) || defined(_M_ARM64) +#ifdef BEMAN_BIG_INT_TARGET_AARCH64 #define BEMAN_BIG_INT_NTT_FP_ARM64 1 #else #define BEMAN_BIG_INT_NTT_FP_ARM64 0 diff --git a/include/beman/big_int/detail/scratch_allocator.hpp b/include/beman/big_int/detail/scratch_allocator.hpp index 5f14fd4b..effde94d 100644 --- a/include/beman/big_int/detail/scratch_allocator.hpp +++ b/include/beman/big_int/detail/scratch_allocator.hpp @@ -165,7 +165,7 @@ struct scratch_allocator : scratch_allocator_base { // Heap-allocate at least `cap` limbs using the provided allocator. constexpr scratch_allocator(std::size_t cap, const Allocator& alloc) : m_alloc(alloc), m_owns(true) { install_heap_hooks(); -#if defined(__cpp_lib_allocate_at_least) && __cpp_lib_allocate_at_least >= 202302L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST if constexpr (traits_has_allocate_at_least) { auto result = alloc_traits::allocate_at_least(m_alloc, cap); m_owned_pointer = result.ptr; diff --git a/include/beman/big_int/detail/wide_ops.hpp b/include/beman/big_int/detail/wide_ops.hpp index 19ad7365..161631de 100644 --- a/include/beman/big_int/detail/wide_ops.hpp +++ b/include/beman/big_int/detail/wide_ops.hpp @@ -209,18 +209,6 @@ constexpr T high_mul(const T x, const T y) noexcept { return __umulh(x, y); } } - #elif defined(_M_AMD64) - if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { - if constexpr (std::is_signed_v) { - __int64 result; - void(_mul128(x, y, &result)); - return result; - } else { - unsigned __int64 result; - void(_umul128(x, y, &result)); - return result; - } - } #endif if constexpr (width_v == 64) { return high_mul_detail::high_mul_portable(x, y); @@ -252,7 +240,7 @@ template const auto product = static_cast>(x) * static_cast>(y); return wide::from_int(product); } else { - #if defined(_M_AMD64) + #if defined(BEMAN_BIG_INT_MSVC) && defined(BEMAN_BIG_INT_TARGET_X86_64) // MSVC intrinsics are not usable during constant evaluation, so fall through // to the portable path when we're in a consteval context. if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { @@ -423,7 +411,7 @@ template } else { return carrying_add_portable(x, y, carry); } -#elif defined(BEMAN_BIG_INT_MSVC) && (defined(_M_AMD64) || defined(_M_IX86)) +#elif defined(BEMAN_BIG_INT_MSVC) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_X86_32)) if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { // Theoretically we could use the ADX intrinsic, but then the user always has to compile for it // This is more portable. @@ -442,7 +430,7 @@ template const unsigned char carry_out = _addcarry_u32(static_cast(carry), x, y, &value); return {.value = value, .carry = carry_out != 0}; } - #ifdef _M_AMD64 + #ifdef BEMAN_BIG_INT_TARGET_X86_64 else if constexpr (width_v == 64) { std::uint64_t value; const unsigned char carry_out = _addcarry_u64(static_cast(carry), x, y, &value); @@ -503,7 +491,7 @@ template } else { return borrowing_sub_portable(x, y, borrow); } -#elif defined(BEMAN_BIG_INT_MSVC) && (defined(_M_AMD64) || defined(_M_IX86)) +#elif defined(BEMAN_BIG_INT_MSVC) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_X86_32)) if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { // Mirror the `carrying_add` MSVC path using the matching `_subborrow_*` intrinsics. // Each branch returns directly, so we don't share uninitialized state across the @@ -521,13 +509,13 @@ template const unsigned char borrow_out = _subborrow_u32(static_cast(borrow), x, y, &value); return {.value = value, .borrow = borrow_out != 0}; } - #ifdef _M_AMD64 + #ifdef BEMAN_BIG_INT_TARGET_X86_64 else if constexpr (width_v == 64) { std::uint64_t value; const unsigned char borrow_out = _subborrow_u64(static_cast(borrow), x, y, &value); return {.value = value, .borrow = borrow_out != 0}; } - #endif // _M_AMD64 + #endif // BEMAN_BIG_INT_TARGET_X86_64 else { return borrowing_sub_portable(x, y, borrow); } @@ -595,7 +583,7 @@ template if constexpr (width_v == 64) { if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { if (!BEMAN_BIG_INT_IS_CONSTANT_PROPAGATED(y)) { - #if defined(BEMAN_BIG_INT_GNUC) && (defined(__x86_64__) || defined(__i386__)) + #if defined(BEMAN_BIG_INT_GNUC) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_X86_32)) T q, r; // volatile is load-bearing: a non-volatile asm counts as // pure, and GCC at -O2 speculatively hoisted this above a @@ -605,7 +593,7 @@ template // This has happened with GCC-14 in release mode __asm__ volatile("div %[d]" : "=a"(q), "=d"(r) : "a"(x.low_bits), "d"(x.high_bits), [d] "r"(y) : "cc"); return {.quotient = q, .remainder = r}; - #elif defined(_WIN32) && defined(_M_X64) + #elif defined(BEMAN_BIG_INT_MSVC) && defined(BEMAN_BIG_INT_TARGET_X86_64) T r; T q = _udiv128(static_cast(x.high_bits), static_cast(x.low_bits), static_cast(y), &r); return {.quotient = static_cast(q), .remainder = static_cast(r)}; diff --git a/include/beman/big_int/format.hpp b/include/beman/big_int/format.hpp index fa6384e5..980132b5 100644 --- a/include/beman/big_int/format.hpp +++ b/include/beman/big_int/format.hpp @@ -6,13 +6,7 @@ #include -// Guarding this include is safe only because the .cppm supplies in its -// global module fragment before the purview #if below tests __cpp_lib_format. -#ifndef BEMAN_BIG_INT_BUILD_MODULE - #include -#endif - -#if __has_include() && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_FORMAT #ifndef BEMAN_BIG_INT_BUILD_MODULE #include diff --git a/include/beman/big_int/string.hpp b/include/beman/big_int/string.hpp index 9c6da752..667956db 100644 --- a/include/beman/big_int/string.hpp +++ b/include/beman/big_int/string.hpp @@ -51,7 +51,7 @@ template ? std::size_t{1} + minus_sign_size : detail::approximate_ceil_div_log2(width - 1, base) + 1 + minus_sign_size; -#ifdef __cpp_lib_string_resize_and_overwrite +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_STRING_RESIZE_AND_OVERWRITE std::string narrow; // `resize_and_overwrite` invokes the operation exactly once, so forwarding // from inside the lambda hands `x` over at most once. diff --git a/module/big_int.cppm b/module/big_int.cppm index 77486a78..3e563b2a 100644 --- a/module/big_int.cppm +++ b/module/big_int.cppm @@ -24,7 +24,7 @@ module; // because `import std` exports declarations but not macros, and the headers // branch on the macros/objects these six provide regardless of which mode is // active. -#include // every __cpp_lib_* feature test the headers branch on +#include // every __cpp_lib_* feature test detail/config.hpp branches on #include // parity with the sibling Boost modules #include // LDBL_MANT_DIG / LDBL_MAX_EXP (detail/floats.hpp has a hard #error without them) #include // BITINT_MAXWIDTH (selects whether _BitInt support exists), CHAR_BIT @@ -64,6 +64,8 @@ module; #include #include + // detail/config.hpp is not included yet, so these two mirror its + // BEMAN_BIG_INT_HAS_CPP_LIB_FORMAT and BEMAN_BIG_INT_HAS_STDFLOAT tests. #if __has_include() && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L #include #endif diff --git a/tests/beman/big_int/allocation.test.cpp b/tests/beman/big_int/allocation.test.cpp index 8434f8cb..67c651ca 100644 --- a/tests/beman/big_int/allocation.test.cpp +++ b/tests/beman/big_int/allocation.test.cpp @@ -964,7 +964,7 @@ TEST(Allocation, ShrinkToFitWhenCapacityEqualsCount) { // ----- from_range with heap allocation ----- TEST(Allocation, FromRangeLargeAllocatesThenDestroys) { -#if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES std::array limbs{1, 2, 3, 4, 5, 6, 7, 8}; BEMAN_BIG_INT_NAMESPACE::big_int x(std::from_range, limbs); EXPECT_EQ(x.representation().size(), 8U); diff --git a/tests/beman/big_int/basic_construction.test.cpp b/tests/beman/big_int/basic_construction.test.cpp index e973d952..ad3b9fa6 100644 --- a/tests/beman/big_int/basic_construction.test.cpp +++ b/tests/beman/big_int/basic_construction.test.cpp @@ -49,7 +49,7 @@ consteval bool test_integral_construction_with_allocator() { static_assert(test_integral_construction_with_allocator()); consteval bool test_from_range_construction() { -#if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES std::array limbs{0xDEADBEEFU, 0xCAFEBABEU}; BEMAN_BIG_INT_NAMESPACE::big_int x(std::from_range, limbs); return x.representation().size() == 2; @@ -113,7 +113,7 @@ TEST(BasicConstruction, IntegralConstructionWithAllocator) { } TEST(BasicConstruction, FromRangeConstruction) { -#if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES std::array limbs{0xDEADBEEFU, 0xCAFEBABEU}; BEMAN_BIG_INT_NAMESPACE::big_int x(std::from_range, limbs); EXPECT_EQ(x.representation().size(), 2U); @@ -123,7 +123,7 @@ TEST(BasicConstruction, FromRangeConstruction) { } TEST(BasicConstruction, FromRangeConstructionTrimsLeadingZeros) { -#if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES std::array limbs{1U, 0U, 0U}; BEMAN_BIG_INT_NAMESPACE::big_int x(std::from_range, limbs); EXPECT_EQ(x.representation().size(), 1U); diff --git a/tests/beman/big_int/format.test.cpp b/tests/beman/big_int/format.test.cpp index d250aca0..fe7ccfc5 100644 --- a/tests/beman/big_int/format.test.cpp +++ b/tests/beman/big_int/format.test.cpp @@ -6,9 +6,7 @@ #include -#include - -#if __has_include() && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_FORMAT #include diff --git a/tests/beman/big_int/perf/small_value_optimization.gbench.cpp b/tests/beman/big_int/perf/small_value_optimization.gbench.cpp index 40216153..da4530b3 100644 --- a/tests/beman/big_int/perf/small_value_optimization.gbench.cpp +++ b/tests/beman/big_int/perf/small_value_optimization.gbench.cpp @@ -115,7 +115,7 @@ big_int_64 large_b() { return (big_int_64{0xDEADBEEFCAFEBABEull} << 192) | big_i // ---------------------------------------------------------------------------- BENCHMARK_CAPTURE(add, int64, std::int64_t{k64_add_a}, std::int64_t{k64_add_b}); BENCHMARK_CAPTURE(add, big_int_64bit, big_int_64{k64_add_a}, big_int_64{k64_add_b}); -#ifdef __SIZEOF_INT128__ +#ifdef BEMAN_BIG_INT_HAS_INT128_EXTENSION BENCHMARK_CAPTURE(add, int128, compose<__int128>(k128_a_hi, k128_a_lo), compose<__int128>(k128_b_hi, k128_b_lo)); BENCHMARK_CAPTURE(add, big_int_128bit, @@ -129,7 +129,7 @@ BENCHMARK_CAPTURE(add, big_int_large, large_a(), large_b()); // ---------------------------------------------------------------------------- BENCHMARK_CAPTURE(subtract, int64, std::int64_t{k64_add_a}, std::int64_t{k64_add_b}); BENCHMARK_CAPTURE(subtract, big_int_64bit, big_int_64{k64_add_a}, big_int_64{k64_add_b}); -#ifdef __SIZEOF_INT128__ +#ifdef BEMAN_BIG_INT_HAS_INT128_EXTENSION BENCHMARK_CAPTURE(subtract, int128, compose<__int128>(k128_a_hi, k128_a_lo), compose<__int128>(k128_b_hi, k128_b_lo)); BENCHMARK_CAPTURE(subtract, big_int_128bit, @@ -143,7 +143,7 @@ BENCHMARK_CAPTURE(subtract, big_int_large, large_a(), large_b()); // ---------------------------------------------------------------------------- BENCHMARK_CAPTURE(multiply, int64, std::int64_t{k64_mul_a}, std::int64_t{k64_mul_b}); BENCHMARK_CAPTURE(multiply, big_int_64bit, big_int_64{k64_mul_a}, big_int_64{k64_mul_b}); -#ifdef __SIZEOF_INT128__ +#ifdef BEMAN_BIG_INT_HAS_INT128_EXTENSION BENCHMARK_CAPTURE(multiply, int128, static_cast<__int128>(k128_mul_a), static_cast<__int128>(k128_mul_b)); BENCHMARK_CAPTURE(multiply, big_int_128bit, big_int_128{k128_mul_a}, big_int_128{k128_mul_b}); #endif @@ -154,7 +154,7 @@ BENCHMARK_CAPTURE(multiply, big_int_large, large_a(), large_b()); // ---------------------------------------------------------------------------- BENCHMARK_CAPTURE(divide, int64, std::int64_t{k64_div_a}, std::int64_t{k64_div_b}); BENCHMARK_CAPTURE(divide, big_int_64bit, big_int_64{k64_div_a}, big_int_64{k64_div_b}); -#ifdef __SIZEOF_INT128__ +#ifdef BEMAN_BIG_INT_HAS_INT128_EXTENSION BENCHMARK_CAPTURE(divide, int128, compose<__int128>(k128_a_hi, k128_a_lo), static_cast<__int128>(k128_div_b)); BENCHMARK_CAPTURE(divide, big_int_128bit, compose(k128_a_hi, k128_a_lo), big_int_128{k128_div_b}); #endif diff --git a/tests/beman/big_int/pmr.test.cpp b/tests/beman/big_int/pmr.test.cpp index e1ddeecc..dd0894ed 100644 --- a/tests/beman/big_int/pmr.test.cpp +++ b/tests/beman/big_int/pmr.test.cpp @@ -220,7 +220,7 @@ TEST(Pmr, FromIteratorPairWithResource) { EXPECT_GE(cr.alloc_count(), 1U); } -#if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L +#ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES TEST(Pmr, FromRangeWithResource) { counting_resource cr; std::array limbs{0xDEADBEEFU, 0xCAFEBABEU}; From 22ef17b12246e1ebed3571208589e7785ec388ea Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Oct 2026 16:24:09 -0400 Subject: [PATCH 2/4] Add configuration generation via CMake or set defaults --- .gitignore | 4 + CMakeLists.txt | 254 ++++++++++++---- docs/modules/ROOT/pages/build_and_usage.adoc | 42 ++- docs/modules/ROOT/pages/format.adoc | 3 +- docs/modules/ROOT/pages/modules.adoc | 6 +- include/beman/big_int/detail/config.hpp | 271 +++++++++--------- .../big_int/detail/config_generated.hpp.in | 29 ++ module/CMakeLists.txt | 6 +- tests/beman/big_int/CMakeLists.txt | 67 ++++- tests/beman/big_int/config.test.cpp | 50 ++++ tests/beman/big_int/config_defaults.cpp | 60 ++++ tests/beman/big_int/module/CMakeLists.txt | 8 +- tests/beman/big_int/perf/shape_sweep.cpp | 16 +- 13 files changed, 595 insertions(+), 221 deletions(-) create mode 100644 include/beman/big_int/detail/config_generated.hpp.in create mode 100644 tests/beman/big_int/config.test.cpp create mode 100644 tests/beman/big_int/config_defaults.cpp diff --git a/.gitignore b/.gitignore index 5f2ce7ae..a4e399f9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ /compile_commands.json /build +# CMake generates this from detail/config_generated.hpp.in (which is tracked); +# ignored anywhere, e.g. an in-source build's generated/include. +config_generated.hpp + # ignore emacs temp files *~ \#*\# diff --git a/CMakeLists.txt b/CMakeLists.txt index d869797f..d05f70e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,8 +97,8 @@ target_compile_features(beman.big_int PUBLIC cxx_std_23) # [CMAKE.NAMESPACE] # The namespace the library is declared in (BEMAN_BIG_INT_NAMESPACE in # detail/config.hpp), e.g. mylib::big_int for a project that wraps it. The -# compiled sources and every consumer must agree on it, so a non-default value is -# propagated PUBLICly. +# compiled sources and every consumer must agree on it, so it is recorded in +# config_generated.hpp (see below). set(BEMAN_BIG_INT_NAMESPACE "beman::big_int" CACHE STRING @@ -115,19 +115,13 @@ if( "mylib::big_int, got '${BEMAN_BIG_INT_NAMESPACE}'." ) endif() -if(NOT BEMAN_BIG_INT_NAMESPACE STREQUAL "beman::big_int") - target_compile_definitions( - beman.big_int - PUBLIC "BEMAN_BIG_INT_NAMESPACE=${BEMAN_BIG_INT_NAMESPACE}" - ) -endif() # [CMAKE.X86_64_BMI2_ADX] # x86-64 only: both the generic and BMI2+ADX schoolbook kernels are always -# built, but which one the library calls is chosen at compile time (see -# detail/config.hpp). AUTO leaves that choice to the compiler's own -# __BMI2__/__ADX__ feature macros (set by -march=native, -march=broadwell or -# newer, znver*, or -mbmi2 -madx). ON forces the BMI2/ADX kernels: the +# built, but which one the library calls is chosen at compile time and recorded +# in config_generated.hpp. AUTO checks the compiler's own __BMI2__/__ADX__ +# feature macros under this build's flags (set by -march=native, +# -march=broadwell or newer, znver*, or -mbmi2 -madx). ON forces the BMI2/ADX kernels: the # resulting binary requires a CPU with both BMI2 and ADX (Intel Broadwell+ or # AMD Zen+), or it dies with an illegal instruction -- this is also how MSVC # opts in, since it defines neither __BMI2__ nor __ADX__. OFF forces the @@ -220,28 +214,7 @@ if( src/multiply_long_runtime_x86_64_avx512_ifma.s src/square_long_runtime_x86_64_avx512_ifma.s ) - if(_bigint_bmi2_adx_mode STREQUAL "ON") - target_compile_definitions( - beman.big_int - PUBLIC BEMAN_BIG_INT_X86_64_BMI2_ADX=1 - ) - elseif(_bigint_bmi2_adx_mode STREQUAL "OFF") - target_compile_definitions( - beman.big_int - PUBLIC BEMAN_BIG_INT_X86_64_BMI2_ADX=0 - ) - endif() - if(_bigint_avx512_ifma_mode STREQUAL "ON") - target_compile_definitions( - beman.big_int - PUBLIC BEMAN_BIG_INT_X86_64_AVX512_IFMA=1 - ) - elseif(_bigint_avx512_ifma_mode STREQUAL "OFF") - target_compile_definitions( - beman.big_int - PUBLIC BEMAN_BIG_INT_X86_64_AVX512_IFMA=0 - ) - endif() + set(_bigint_x86_64_kernels TRUE) elseif( ( CMAKE_CXX_COMPILER_ID STREQUAL "GNU" @@ -269,28 +242,7 @@ elseif(MSVC AND CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "x64") src/multiply_long_runtime_x86_64_avx512_ifma.asm src/square_long_runtime_x86_64_avx512_ifma.asm ) - if(_bigint_bmi2_adx_mode STREQUAL "ON") - target_compile_definitions( - beman.big_int - PUBLIC BEMAN_BIG_INT_X86_64_BMI2_ADX=1 - ) - elseif(_bigint_bmi2_adx_mode STREQUAL "OFF") - target_compile_definitions( - beman.big_int - PUBLIC BEMAN_BIG_INT_X86_64_BMI2_ADX=0 - ) - endif() - if(_bigint_avx512_ifma_mode STREQUAL "ON") - target_compile_definitions( - beman.big_int - PUBLIC BEMAN_BIG_INT_X86_64_AVX512_IFMA=1 - ) - elseif(_bigint_avx512_ifma_mode STREQUAL "OFF") - target_compile_definitions( - beman.big_int - PUBLIC BEMAN_BIG_INT_X86_64_AVX512_IFMA=0 - ) - endif() + set(_bigint_x86_64_kernels TRUE) elseif(MSVC AND CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "ARM64") # ASM_MARMASM requires CMake >= 3.26; the project minimum (3.30) already covers it. enable_language(ASM_MARMASM) @@ -302,6 +254,173 @@ elseif(MSVC AND CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "ARM64") ) endif() +# Feature detection for config_generated.hpp. The probe compiles the non-CMake +# detection in detail/config.hpp (the #else of its __has_include, whose +# BEMAN_BIG_INT_* macros are collected here) once with this build's compiler, +# standard and flags. Each macro's value is embedded as a string in the probe +# binary and read back, the way CMake itself detects compiler properties, so the +# detection logic lives only in config.hpp. NO_CACHE reruns it on every +# configure, so a changed CMAKE_CXX_FLAGS is never answered from a stale result. +set(_bigint_config_hpp + "${CMAKE_CURRENT_SOURCE_DIR}/include/beman/big_int/detail/config.hpp" +) +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS "${_bigint_config_hpp}" +) +block( + SCOPE_FOR VARIABLES + PROPAGATE + _bigint_config_features + _bigint_compiler_bmi2_adx + _bigint_compiler_avx512_ifma +) + file(READ "${_bigint_config_hpp}" _config) + string( + FIND "${_config}" + "#if __has_include()" + _begin + ) + if(_begin EQUAL -1) + message( + FATAL_ERROR + "beman.big_int: detail/config.hpp has no config_generated.hpp branch" + ) + endif() + string(SUBSTRING "${_config}" ${_begin} -1 _config) + # Nested directives are indented, so the first unindented #endif closes it. + string(FIND "${_config}" "\n#endif" _end) + string(SUBSTRING "${_config}" 0 ${_end} _config) + string( + REGEX MATCHALL "#[ ]*define[ ]+BEMAN_BIG_INT_[A-Za-z0-9_]+" + _defines + "${_config}" + ) + set(_macros "") + foreach(_define IN LISTS _defines) + string(REGEX REPLACE "^#[ ]*define[ ]+" "" _macro "${_define}") + list(APPEND _macros "${_macro}") + endforeach() + list(REMOVE_DUPLICATES _macros) + # Build options: set from the CMake options below instead. + list( + REMOVE_ITEM _macros + BEMAN_BIG_INT_X86_64_BMI2_ADX + BEMAN_BIG_INT_X86_64_AVX512_IFMA + ) + + set(_probe "#include \n") + string(APPEND _probe "#define BEMAN_BIG_INT_PROBE_STR(...) #__VA_ARGS__\n") + string( + APPEND _probe + "#define BEMAN_BIG_INT_PROBE_VALUE(...) BEMAN_BIG_INT_PROBE_STR(__VA_ARGS__)\n" + ) + string(APPEND _probe "extern const char beman_big_int_probe[];\n") + string( + APPEND _probe + "const char beman_big_int_probe[] = \"BEMAN_BIG_INT_PROBE\"\n" + ) + foreach(_macro IN LISTS _macros) + string( + APPEND _probe + "#ifdef ${_macro}\n\"[${_macro}=\" BEMAN_BIG_INT_PROBE_VALUE(${_macro}) \"]\"\n#endif\n" + ) + endforeach() + string( + APPEND _probe + ";\nint main(int argc, char**) { return beman_big_int_probe[argc]; }\n" + ) + + # The library is C++23 at minimum; a newer CMAKE_CXX_STANDARD is honored. + set(_std 23) + if( + DEFINED CMAKE_CXX_STANDARD + AND NOT CMAKE_CXX_STANDARD MATCHES "^(98|11|14|17|20|23)$" + ) + set(_std "${CMAKE_CXX_STANDARD}") + endif() + if(CMAKE_BUILD_TYPE) + set(CMAKE_TRY_COMPILE_CONFIGURATION "${CMAKE_BUILD_TYPE}") + endif() + set(_binary + "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/beman_big_int_probe.bin" + ) + try_compile( + _compiled + SOURCE_FROM_CONTENT config_probe.cpp "${_probe}" + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${CMAKE_CURRENT_SOURCE_DIR}/include" + CXX_STANDARD ${_std} + CXX_STANDARD_REQUIRED ON + NO_CACHE + OUTPUT_VARIABLE _output + COPY_FILE "${_binary}" + ) + if(NOT _compiled) + message( + FATAL_ERROR + "beman.big_int: the configuration probe failed to compile:\n${_output}" + ) + endif() + file(STRINGS "${_binary}" _strings REGEX "BEMAN_BIG_INT_PROBE") + list(REMOVE_DUPLICATES _strings) + list(LENGTH _strings _count) + if(NOT _count EQUAL 1) + message( + FATAL_ERROR + "beman.big_int: expected one configuration probe result, found ${_count}. " + "A build for several architectures at once (e.g. CMAKE_OSX_ARCHITECTURES " + "with more than one entry) is not supported." + ) + endif() + + set(_bigint_config_features "") + set(_found "") + foreach(_macro IN LISTS _macros) + if(_strings MATCHES "\\[${_macro}=([^]]*)\\]") + list(APPEND _found "${_macro}") + if(CMAKE_MATCH_1 STREQUAL "") + string(APPEND _bigint_config_features "#define ${_macro}\n") + else() + string( + APPEND _bigint_config_features + "#define ${_macro} ${CMAKE_MATCH_1}\n" + ) + endif() + endif() + endforeach() + set(_bigint_compiler_bmi2_adx 0) + set(_bigint_compiler_avx512_ifma 0) + if("BEMAN_BIG_INT_HAS_BMI2_ADX" IN_LIST _found) + set(_bigint_compiler_bmi2_adx 1) + if("BEMAN_BIG_INT_HAS_AVX512_IFMA" IN_LIST _found) + set(_bigint_compiler_avx512_ifma 1) + endif() + endif() +endblock() + +# Resolve the x86-64 kernel choices to 0/1 for config_generated.hpp. AUTO takes +# what the probe found, i.e. the compiler's own feature macros under this +# build's flags (e.g. -march=native in CMAKE_CXX_FLAGS). +set(_bigint_config_bmi2_adx 0) +set(_bigint_config_avx512_ifma 0) +if(_bigint_x86_64_kernels) + if(_bigint_bmi2_adx_mode STREQUAL "ON") + set(_bigint_config_bmi2_adx 1) + elseif(_bigint_bmi2_adx_mode STREQUAL "AUTO") + set(_bigint_config_bmi2_adx ${_bigint_compiler_bmi2_adx}) + endif() + if(_bigint_avx512_ifma_mode STREQUAL "ON") + set(_bigint_config_avx512_ifma 1) + elseif(_bigint_avx512_ifma_mode STREQUAL "AUTO") + set(_bigint_config_avx512_ifma ${_bigint_compiler_avx512_ifma}) + endif() + message( + STATUS + "beman.big_int: x86-64 BMI2/ADX kernels ${_bigint_config_bmi2_adx}, AVX-512 IFMA kernels ${_bigint_config_avx512_ifma}" + ) +endif() + # Multiplication's FFT tier. By default it is an integer Montgomery NTT # (src/ntt.cpp, src/fft_mul.cpp): pure integer arithmetic, exact on every # conforming compiler with no floating-point-environment assumptions. @@ -319,9 +438,7 @@ option( if(BEMAN_BIG_INT_SIMD_MUL) # The dispatch lives in a header (mul_impl.hpp), so the library and every - # consumer must agree on the path: propagate the macro PUBLICly. - target_compile_definitions(beman.big_int PUBLIC BEMAN_BIG_INT_SIMD_MUL) - + # consumer must agree on the path: config_generated.hpp records it. target_sources( beman.big_int PRIVATE src/ntt_fp_scalar.cpp src/ntt_fp_dispatch.cpp src/fft_mul_fp.cpp @@ -378,6 +495,25 @@ target_sources( FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/beman/big_int.hpp" ) +# config_generated.hpp records the options above for detail/config.hpp, so the +# compiled library, the module and every consumer, installed ones included, agree +# on them. It is generated outside the source include/ tree, so a build that does +# not use CMake never picks up a stale copy. +set(_bigint_generated_include "${CMAKE_CURRENT_BINARY_DIR}/generated/include") +configure_file( + include/beman/big_int/detail/config_generated.hpp.in + "${_bigint_generated_include}/beman/big_int/detail/config_generated.hpp" + @ONLY +) +target_sources( + beman.big_int + PUBLIC + FILE_SET HEADERS + BASE_DIRS "${_bigint_generated_include}" + FILES + "${_bigint_generated_include}/beman/big_int/detail/config_generated.hpp" +) + set_target_properties( beman.big_int PROPERTIES VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL} diff --git a/docs/modules/ROOT/pages/build_and_usage.adoc b/docs/modules/ROOT/pages/build_and_usage.adoc index 6acc7e0e..4be27a92 100644 --- a/docs/modules/ROOT/pages/build_and_usage.adoc +++ b/docs/modules/ROOT/pages/build_and_usage.adoc @@ -101,6 +101,8 @@ Which one the library calls is chosen at compile time, with no runtime dispatch. When you compile with flags that define both `__BMI2__` and `__ADX__` (for example `-march=native`, or `-march=broadwell` or newer, or `-march=znver1` or newer), the BMI2/ADX kernels are selected automatically. +With CMake, that check runs once at configure time against the flags the library +is compiled with (see xref:#build_and_usage_config[Build configuration]). You can also select explicitly with the CMake option `BEMAN_BIG_INT_X86_64_BMI2_ADX` (default `AUTO`, also accepting `ON`/`OFF`): @@ -147,7 +149,8 @@ When you compile with flags that define `__AVX512IFMA__`, `__AVX512VL__`, `__AVX512BW__`, `__AVX512VBMI__`, `__BMI2__`, and `__ADX__` (for example `-march=native`, or `-march=icelake-client`, `-march=rocketlake`, `-march=sapphirerapids`, `-march=znver4`, or newer), the IFMA kernels are -selected automatically for operands at or above the size gate. +selected automatically for operands at or above the size gate. As with BMI2/ADX, +CMake makes that check once at configure time. You can also select explicitly with the CMake option `BEMAN_BIG_INT_X86_64_AVX512_IFMA` (default `AUTO`, also accepting `ON`/`OFF`): @@ -200,8 +203,8 @@ cmake --preset gcc-release -DBEMAN_BIG_INT_NAMESPACE=mylib::big_int ---- The library is partly compiled, so its sources and every consumer must agree on -the namespace. CMake propagates a non-default value to consumers of the -`beman::big_int` target. Without CMake, define the `BEMAN_BIG_INT_NAMESPACE` +the namespace. CMake records it in the generated configuration header (see +xref:#build_and_usage_config[Build configuration]). Without CMake, define the `BEMAN_BIG_INT_NAMESPACE` macro to the same value everywhere, both for the sources under `src/` and for every translation unit that includes the headers. The `*-release-namespace` presets build and run the whole test suite with @@ -217,6 +220,39 @@ both define `detail::make_signed` and `detail::make_unsigned`, and Boost's `literals` namespace is not `inline` while this library's is. ==== +[#build_and_usage_config] +=== Build configuration + +CMake writes a generated header, `beman/big_int/detail/config_generated.hpp`, +under the build directory's `generated/include` and installs it with the other +headers. It holds the options above together with the result of the library's +feature detection (standard library features, `_BitInt` and `__int128` +support, exceptions, the target architecture and its instruction set +extensions), which CMake runs once at configure time with the compiler, language +standard and `CMAKE_CXX_FLAGS` of the build. `detail/config.hpp` includes it +instead of detecting anything itself whenever it is on the include path, so the +compiled library and every translation unit that uses it agree on all of these. +In particular, `AUTO` for the x86-64 kernel options is resolved there, and +compiling a consumer with different flags than the library (another `-march=`, +`-fno-exceptions`, or an older `-std=`) does not change what the headers see. + +A build that does not use CMake has no generated header: `detail/config.hpp` +then detects features in every translation unit and falls back on the defaults +below. Each can be overridden by defining the macro, identically for the +sources under `src/` and for every translation unit that includes the headers. + +* `BEMAN_BIG_INT_X86_64_BMI2_ADX` is `1` when the compiler defines both + `__BMI2__` and `__ADX__`, or when `BEMAN_BIG_INT_X86_64_AVX512_IFMA` is defined + to `1`, and `0` otherwise. +* `BEMAN_BIG_INT_X86_64_AVX512_IFMA` is `1` when the compiler defines + `__AVX512IFMA__`, `__AVX512VL__`, `__AVX512BW__`, `__AVX512VBMI__`, `__BMI2__`, + and `__ADX__`, and `0` otherwise. +* `BEMAN_BIG_INT_SIMD_MUL` is never defined automatically, whatever the target + supports: the SIMD path is exact only under floating-point flags the headers + cannot query (see xref:#build_and_usage_simd[SIMD-accelerated multiplication]). + Define it yourself only once those flags are guaranteed. +* `BEMAN_BIG_INT_NAMESPACE` is `beman::big_int`. + [#build_and_usage_install] == Installation diff --git a/docs/modules/ROOT/pages/format.adoc b/docs/modules/ROOT/pages/format.adoc index b041bcf5..139acb27 100644 --- a/docs/modules/ROOT/pages/format.adoc +++ b/docs/modules/ROOT/pages/format.adoc @@ -249,7 +249,8 @@ Where the library is built with exceptions disabled, there is nothing to throw: each of the conditions above calls `std::abort()` instead. The mode is deduced in `` from `__EXCEPTIONS` and, on MSVC, `_CPPUNWIND`, and is recorded as `BEMAN_BIG_INT_ALLOW_EXCEPTIONS` or -`BEMAN_BIG_INT_NO_EXCEPTIONS`. This matches how the rest of the library +`BEMAN_BIG_INT_NO_EXCEPTIONS`; a CMake build deduces it once, from the flags the +library is built with (see xref:build_and_usage.adoc#build_and_usage_config[Build configuration]). This matches how the rest of the library reports a length limit; see xref:big_int_type.adoc#big_int_type_length_limit[the length limit]. diff --git a/docs/modules/ROOT/pages/modules.adoc b/docs/modules/ROOT/pages/modules.adoc index c575ba20..ba597b90 100644 --- a/docs/modules/ROOT/pages/modules.adoc +++ b/docs/modules/ROOT/pages/modules.adoc @@ -267,9 +267,9 @@ macro in your own translation unit has no effect. Set them when the module itself is configured and built -- for example as compile definitions on the `beman.big_int_module` target -- not at the point of use. -`BEMAN_BIG_INT_SIMD_MUL` needs no such care: this project's CMake applies it -as a `PUBLIC` compile definition on the `beman.big_int` target, and the -module target links that target, so turning the option on when the library is +`BEMAN_BIG_INT_SIMD_MUL` needs no such care: this project's CMake records it +in the generated `detail/config_generated.hpp`, which the interface unit +includes when the module is built, so turning the option on when the library is configured reaches every consumer of the module automatically. === `detail` is not exported diff --git a/include/beman/big_int/detail/config.hpp b/include/beman/big_int/detail/config.hpp index c04a8a03..29ea1143 100644 --- a/include/beman/big_int/detail/config.hpp +++ b/include/beman/big_int/detail/config.hpp @@ -4,19 +4,6 @@ #ifndef BEMAN_BIG_INT_CONFIG_HPP #define BEMAN_BIG_INT_CONFIG_HPP -#if __has_include() - -#include - -#else - -// Feature detection =========================================================== - -// Every feature-test, compiler-extension, and target-architecture macro the -// library branches on is tested here and nowhere else; the rest of the library -// checks only the BEMAN_BIG_INT_HAS_* and BEMAN_BIG_INT_TARGET_* spellings below. -// Each is defined to 1 when the feature is available and left undefined otherwise. - // Guarding these includes is safe only because the .cppm supplies them in its // global module fragment before the purview include of this header. #ifndef BEMAN_BIG_INT_BUILD_MODULE @@ -25,133 +12,174 @@ #include // for __cpp_lib_* #endif -// Language features +// A CMake build generates config_generated.hpp at configure time +#if __has_include() -#if defined(__cpp_if_consteval) && __cpp_if_consteval >= 202106L - #define BEMAN_BIG_INT_HAS_CPP_IF_CONSTEVAL 1 -#endif + #include -// Library features +#else -#if defined(__cpp_lib_allocate_at_least) && __cpp_lib_allocate_at_least >= 202302L - #define BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST 1 -#endif + // Feature detection =========================================================== -#if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L - #define BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH 1 -#endif + // Every feature-test, compiler-extension, and target-architecture macro the + // library branches on is tested here and nowhere else; the rest of the library + // checks only the BEMAN_BIG_INT_HAS_* and BEMAN_BIG_INT_TARGET_* spellings below. + // Each is defined to 1 when the feature is available and left undefined otherwise. -#if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L - #define BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES 1 -#endif + // Language features -#if __has_include() && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L - #define BEMAN_BIG_INT_HAS_CPP_LIB_FORMAT 1 -#endif + #if defined(__cpp_if_consteval) && __cpp_if_consteval >= 202106L + #define BEMAN_BIG_INT_HAS_CPP_IF_CONSTEVAL 1 + #endif -// 202411L is the revision that makes the uninitialized memory algorithms constexpr. -#if defined(__cpp_lib_raw_memory_algorithms) && __cpp_lib_raw_memory_algorithms >= 202411L - #define BEMAN_BIG_INT_HAS_CPP_LIB_RAW_MEMORY_ALGORITHMS 1 -#endif + // Library features -#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L - #define BEMAN_BIG_INT_HAS_CPP_LIB_STRING_RESIZE_AND_OVERWRITE 1 -#endif + #if defined(__cpp_lib_allocate_at_least) && __cpp_lib_allocate_at_least >= 202302L + #define BEMAN_BIG_INT_HAS_CPP_LIB_ALLOCATE_AT_LEAST 1 + #endif -// Extended floating-point types + #if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202202L + #define BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH 1 + #endif -#if __has_include() - #define BEMAN_BIG_INT_HAS_STDFLOAT 1 -#endif + #if defined(__cpp_lib_containers_ranges) && __cpp_lib_containers_ranges >= 202202L + #define BEMAN_BIG_INT_HAS_CPP_LIB_CONTAINERS_RANGES 1 + #endif -#ifdef __STDCPP_FLOAT16_T__ - #define BEMAN_BIG_INT_HAS_STDCPP_FLOAT16_T 1 -#endif + #if __has_include() && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L + #define BEMAN_BIG_INT_HAS_CPP_LIB_FORMAT 1 + #endif -#ifdef __STDCPP_BFLOAT16_T__ - #define BEMAN_BIG_INT_HAS_STDCPP_BFLOAT16_T 1 -#endif + // 202411L is the revision that makes the uninitialized memory algorithms constexpr. + #if defined(__cpp_lib_raw_memory_algorithms) && __cpp_lib_raw_memory_algorithms >= 202411L + #define BEMAN_BIG_INT_HAS_CPP_LIB_RAW_MEMORY_ALGORITHMS 1 + #endif -#ifdef __STDCPP_FLOAT128_T__ - #define BEMAN_BIG_INT_HAS_STDCPP_FLOAT128_T 1 -#endif + #if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + #define BEMAN_BIG_INT_HAS_CPP_LIB_STRING_RESIZE_AND_OVERWRITE 1 + #endif -// Bit-precise integers (_BitInt) + // Extended floating-point types -#ifdef BITINT_MAXWIDTH - // Once _BitInt is a standard feature and available on all compilers, - // this case should be selected for all compilers. - #define BEMAN_BIG_INT_HAS_BITINT 1 - #define BEMAN_BIG_INT_BITINT_MAXWIDTH BITINT_MAXWIDTH -#elif defined(__BITINT_MAXWIDTH__) - // This case is for Clang when it provides _BitInt as an extension. - #define BEMAN_BIG_INT_HAS_BITINT 1 - #define BEMAN_BIG_INT_HAS_BITINT_EXTENSION 1 - #define BEMAN_BIG_INT_BITINT_MAXWIDTH __BITINT_MAXWIDTH__ -#else - // Prevent warnings for use of undefined macros. - #define BEMAN_BIG_INT_BITINT_MAXWIDTH 0 -#endif // BITINT_MAXWIDTH + #if __has_include() + #define BEMAN_BIG_INT_HAS_STDFLOAT 1 + #endif -// Workaround for Clang-19 ICE past 128 bits, even though it reports far more than that -// Crashes in EmitAutoVarInit; fixed in Clang 20 by https://github.com/llvm/llvm-project/pull/112218 -#if defined(__clang__) && __clang_major__ == 19 && BEMAN_BIG_INT_BITINT_MAXWIDTH > 128 - #undef BEMAN_BIG_INT_BITINT_MAXWIDTH - #define BEMAN_BIG_INT_BITINT_MAXWIDTH 128 -#endif + #ifdef __STDCPP_FLOAT16_T__ + #define BEMAN_BIG_INT_HAS_STDCPP_FLOAT16_T 1 + #endif -// GNU __int128 + #ifdef __STDCPP_BFLOAT16_T__ + #define BEMAN_BIG_INT_HAS_STDCPP_BFLOAT16_T 1 + #endif -#ifdef __SIZEOF_INT128__ - #define BEMAN_BIG_INT_HAS_INT128_EXTENSION 1 -#endif + #ifdef __STDCPP_FLOAT128_T__ + #define BEMAN_BIG_INT_HAS_STDCPP_FLOAT128_T 1 + #endif + + // Bit-precise integers (_BitInt) + + #ifdef BITINT_MAXWIDTH + // Once _BitInt is a standard feature and available on all compilers, + // this case should be selected for all compilers. + #define BEMAN_BIG_INT_HAS_BITINT 1 + #define BEMAN_BIG_INT_BITINT_MAXWIDTH BITINT_MAXWIDTH + #elif defined(__BITINT_MAXWIDTH__) + // This case is for Clang when it provides _BitInt as an extension. + #define BEMAN_BIG_INT_HAS_BITINT 1 + #define BEMAN_BIG_INT_HAS_BITINT_EXTENSION 1 + #define BEMAN_BIG_INT_BITINT_MAXWIDTH __BITINT_MAXWIDTH__ + #else + // Prevent warnings for use of undefined macros. + #define BEMAN_BIG_INT_BITINT_MAXWIDTH 0 + #endif // BITINT_MAXWIDTH + + // Workaround for Clang-19 ICE past 128 bits, even though it reports far more than that + // Crashes in EmitAutoVarInit; fixed in Clang 20 by https://github.com/llvm/llvm-project/pull/112218 + #if defined(__clang__) && __clang_major__ == 19 && BEMAN_BIG_INT_BITINT_MAXWIDTH > 128 + #undef BEMAN_BIG_INT_BITINT_MAXWIDTH + #define BEMAN_BIG_INT_BITINT_MAXWIDTH 128 + #endif -// Exceptions + // GNU __int128 -#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || defined(__EXCEPTIONS) - #define BEMAN_BIG_INT_ALLOW_EXCEPTIONS -#else - #define BEMAN_BIG_INT_NO_EXCEPTIONS -#endif + #ifdef __SIZEOF_INT128__ + #define BEMAN_BIG_INT_HAS_INT128_EXTENSION 1 + #endif -// Target architecture + // Exceptions -#if defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) - #define BEMAN_BIG_INT_TARGET_X86_64 1 -#endif + #if (defined(_MSC_VER) && defined(_CPPUNWIND)) || defined(__EXCEPTIONS) + #define BEMAN_BIG_INT_ALLOW_EXCEPTIONS + #else + #define BEMAN_BIG_INT_NO_EXCEPTIONS + #endif -#if defined(__i386__) || defined(_M_IX86) - #define BEMAN_BIG_INT_TARGET_X86_32 1 -#endif + // Target architecture -#if defined(__aarch64__) || defined(_M_ARM64) - #define BEMAN_BIG_INT_TARGET_AARCH64 1 -#endif + #if defined(__x86_64__) || defined(_M_X64) || defined(__amd64__) + #define BEMAN_BIG_INT_TARGET_X86_64 1 + #endif -#if defined(__wasm__) || defined(__EMSCRIPTEN__) - #define BEMAN_BIG_INT_TARGET_WASM 1 -#endif + #if defined(__i386__) || defined(_M_IX86) + #define BEMAN_BIG_INT_TARGET_X86_32 1 + #endif -// x86 instruction set extensions enabled at compile time (e.g. -mbmi2 -madx). -// BEMAN_BIG_INT_HAS_AVX512_IFMA is the full AVX-512 set the IFMA kernels need. + #if defined(__aarch64__) || defined(_M_ARM64) + #define BEMAN_BIG_INT_TARGET_AARCH64 1 + #endif -#if defined(__BMI2__) && defined(__ADX__) - #define BEMAN_BIG_INT_HAS_BMI2_ADX 1 -#endif + #if defined(__wasm__) || defined(__EMSCRIPTEN__) + #define BEMAN_BIG_INT_TARGET_WASM 1 + #endif -#if defined(__AVX512IFMA__) && defined(__AVX512VL__) && defined(__AVX512BW__) && defined(__AVX512VBMI__) - #define BEMAN_BIG_INT_HAS_AVX512_IFMA 1 -#endif + // x86 instruction set extensions enabled at compile time (e.g. -mbmi2 -madx). + // BEMAN_BIG_INT_HAS_AVX512_IFMA is the full AVX-512 set the IFMA kernels need. -// Word size + #if defined(__BMI2__) && defined(__ADX__) + #define BEMAN_BIG_INT_HAS_BMI2_ADX 1 + #endif -#if INTPTR_MAX == INT64_MAX || defined(BEMAN_BIG_INT_TARGET_WASM) - #define BEMAN_BIG_INT_WORD_BITS 64 -#elif INTPTR_MAX == INT32_MAX - #define BEMAN_BIG_INT_WORD_BITS 32 -#else - #error Unknown pointer size or missing size macros! -#endif + #if defined(__AVX512IFMA__) && defined(__AVX512VL__) && defined(__AVX512BW__) && defined(__AVX512VBMI__) + #define BEMAN_BIG_INT_HAS_AVX512_IFMA 1 + #endif + + // Word size + + #if INTPTR_MAX == INT64_MAX || defined(BEMAN_BIG_INT_TARGET_WASM) + #define BEMAN_BIG_INT_WORD_BITS 64 + #elif INTPTR_MAX == INT32_MAX + #define BEMAN_BIG_INT_WORD_BITS 32 + #else + #error Unknown pointer size or missing size macros! + #endif + + // Build configuration defaults ================================================ + + // CMake sets these from its options. Without it they follow the compiler's own + // feature macros, and each can still be set explicitly with -D. The FP SIMD + // multiplication (BEMAN_BIG_INT_SIMD_MUL) is never turned on here: it is exact + // only under floating-point flags that cannot be queried, so it must be + // defined explicitly. + + // As in CMake, forcing the IFMA kernels on also forces their small-operand + // fallback, the BMI2/ADX kernels, unless that is set explicitly. + #ifndef BEMAN_BIG_INT_X86_64_BMI2_ADX + #if defined(BEMAN_BIG_INT_HAS_BMI2_ADX) || \ + (defined(BEMAN_BIG_INT_X86_64_AVX512_IFMA) && BEMAN_BIG_INT_X86_64_AVX512_IFMA) + #define BEMAN_BIG_INT_X86_64_BMI2_ADX 1 + #else + #define BEMAN_BIG_INT_X86_64_BMI2_ADX 0 + #endif + #endif + + #ifndef BEMAN_BIG_INT_X86_64_AVX512_IFMA + #if defined(BEMAN_BIG_INT_HAS_AVX512_IFMA) && defined(BEMAN_BIG_INT_HAS_BMI2_ADX) + #define BEMAN_BIG_INT_X86_64_AVX512_IFMA 1 + #else + #define BEMAN_BIG_INT_X86_64_AVX512_IFMA 0 + #endif + #endif #endif // Config Generated @@ -418,29 +446,14 @@ BEMAN_BIG_INT_END_NAMESPACE #define BEMAN_BIG_INT_ARCH_X86_64 #endif -// Compile-time choice between the generic and BMI2+ADX x86-64 kernels -#if !defined(BEMAN_BIG_INT_ARCH_X86_64) +// The BMI2/ADX and AVX-512 IFMA kernel choices (see Build configuration) only +// apply where the x86-64 kernels are called at all. IFMA covers large operands +// only; small ones still go through the BMI2/ADX-or-generic choice. +#ifndef BEMAN_BIG_INT_ARCH_X86_64 #undef BEMAN_BIG_INT_X86_64_BMI2_ADX #define BEMAN_BIG_INT_X86_64_BMI2_ADX 0 -#elif !defined(BEMAN_BIG_INT_X86_64_BMI2_ADX) - #ifdef BEMAN_BIG_INT_HAS_BMI2_ADX - #define BEMAN_BIG_INT_X86_64_BMI2_ADX 1 - #else - #define BEMAN_BIG_INT_X86_64_BMI2_ADX 0 - #endif -#endif - -// Compile-time choice of the AVX-512 IFMA x86-64 kernels (large operands -// only; small ones still go through the BMI2/ADX-or-generic choice above). -#if !defined(BEMAN_BIG_INT_ARCH_X86_64) #undef BEMAN_BIG_INT_X86_64_AVX512_IFMA #define BEMAN_BIG_INT_X86_64_AVX512_IFMA 0 -#elif !defined(BEMAN_BIG_INT_X86_64_AVX512_IFMA) - #if defined(BEMAN_BIG_INT_HAS_AVX512_IFMA) && defined(BEMAN_BIG_INT_HAS_BMI2_ADX) - #define BEMAN_BIG_INT_X86_64_AVX512_IFMA 1 - #else - #define BEMAN_BIG_INT_X86_64_AVX512_IFMA 0 - #endif #endif #if defined(BEMAN_BIG_INT_TARGET_AARCH64) && BEMAN_BIG_INT_LIMB_WIDTH == 64 diff --git a/include/beman/big_int/detail/config_generated.hpp.in b/include/beman/big_int/detail/config_generated.hpp.in new file mode 100644 index 00000000..aac3e270 --- /dev/null +++ b/include/beman/big_int/detail/config_generated.hpp.in @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-License-Identifier: BSL-1.0 + +// Generated by CMake from config_generated.hpp.in -- do not edit. +// +// Records the options this build of the library was configured with, so the +// compiled library and every translation unit that includes its headers agree +// on them. detail/config.hpp includes this file instead of running its own +// feature detection whenever it is on the include path; a build that does not +// use CMake has no copy and detects features per translation unit. + +#ifndef BEMAN_BIG_INT_CONFIG_GENERATED_HPP +#define BEMAN_BIG_INT_CONFIG_GENERATED_HPP + +#define BEMAN_BIG_INT_CONFIG_GENERATED 1 + +// Feature detection: what detail/config.hpp's own detection found for this +// build's compiler, language standard and flags. +@_bigint_config_features@ +// Build options + +#define BEMAN_BIG_INT_NAMESPACE @BEMAN_BIG_INT_NAMESPACE@ + +#define BEMAN_BIG_INT_X86_64_BMI2_ADX @_bigint_config_bmi2_adx@ +#define BEMAN_BIG_INT_X86_64_AVX512_IFMA @_bigint_config_avx512_ifma@ + +#cmakedefine BEMAN_BIG_INT_SIMD_MUL + +#endif // BEMAN_BIG_INT_CONFIG_GENERATED_HPP diff --git a/module/CMakeLists.txt b/module/CMakeLists.txt index 9f0fa6fe..ba7c3197 100644 --- a/module/CMakeLists.txt +++ b/module/CMakeLists.txt @@ -27,9 +27,9 @@ target_sources( FILES "${CMAKE_CURRENT_SOURCE_DIR}/big_int.cppm" ) -# Also carries BEMAN_BIG_INT_SIMD_MUL, which is PUBLIC on beman.big_int and -# selects the multiplication tier inside a header: the module and the library it -# wraps must agree on that choice. +# Also carries the include path of detail/config_generated.hpp, which records +# BEMAN_BIG_INT_SIMD_MUL and the other build options: the module and the library +# it wraps must agree on them. target_link_libraries(beman.big_int_module PUBLIC beman::big_int) target_compile_features(beman.big_int_module PUBLIC cxx_std_23) diff --git a/tests/beman/big_int/CMakeLists.txt b/tests/beman/big_int/CMakeLists.txt index 4ad04b78..0b41a56b 100644 --- a/tests/beman/big_int/CMakeLists.txt +++ b/tests/beman/big_int/CMakeLists.txt @@ -27,16 +27,8 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(boost_multiprecision) -foreach(test_source ${TEST_SOURCES}) - get_filename_component(test_name ${test_source} NAME_WE) - set(test_target beman.big_int.tests.${test_name}) - - add_executable(${test_target}) - target_sources(${test_target} PRIVATE ${test_source}) - target_link_libraries( - ${test_target} - PRIVATE beman::big_int GTest::gtest_main Boost::multiprecision - ) +# The project's test warning set. +function(beman_big_int_test_warnings test_target) if( CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang" @@ -76,9 +68,64 @@ foreach(test_source ${TEST_SOURCES}) # /WX = -Werror target_compile_options(${test_target} PRIVATE /WX /W4) endif() +endfunction() + +foreach(test_source ${TEST_SOURCES}) + get_filename_component(test_name ${test_source} NAME_WE) + set(test_target beman.big_int.tests.${test_name}) + + add_executable(${test_target}) + target_sources(${test_target} PRIVATE ${test_source}) + target_link_libraries( + ${test_target} + PRIVATE beman::big_int GTest::gtest_main Boost::multiprecision + ) + beman_big_int_test_warnings(${test_target}) gtest_discover_tests(${test_target} DISCOVERY_TIMEOUT 60) endforeach() +# What CMake resolved for config_generated.hpp, for config.test.cpp to check +# against what the headers actually see. +target_compile_definitions( + beman.big_int.tests.config + PRIVATE + "BEMAN_BIG_INT_TEST_EXPECTED_NAMESPACE=\"${BEMAN_BIG_INT_NAMESPACE}\"" + BEMAN_BIG_INT_TEST_EXPECTED_BMI2_ADX=${_bigint_config_bmi2_adx} + BEMAN_BIG_INT_TEST_EXPECTED_AVX512_IFMA=${_bigint_config_avx512_ifma} + BEMAN_BIG_INT_TEST_EXPECTED_SIMD_MUL=$ +) + +# detail/config.hpp's defaults for a build that does not use CMake. These targets +# deliberately do not link beman::big_int, so config_generated.hpp is not on +# their include path; config_defaults.cpp only checks macros, so nothing needs +# the compiled library. +function(beman_big_int_config_defaults_test name) + cmake_parse_arguments(arg "" "" "DEFINITIONS;OPTIONS" ${ARGN}) + set(test_target beman.big_int.tests.config_defaults_${name}) + add_executable(${test_target} config_defaults.cpp) + target_include_directories( + ${test_target} + PRIVATE "${PROJECT_SOURCE_DIR}/include" + ) + target_compile_features(${test_target} PRIVATE cxx_std_23) + target_compile_definitions(${test_target} PRIVATE ${arg_DEFINITIONS}) + target_compile_options(${test_target} PRIVATE ${arg_OPTIONS}) + target_link_libraries(${test_target} PRIVATE GTest::gtest_main) + beman_big_int_test_warnings(${test_target}) + gtest_discover_tests(${test_target} DISCOVERY_TIMEOUT 60) +endfunction() + +beman_big_int_config_defaults_test(compiler) +beman_big_int_config_defaults_test( + forced_ifma + DEFINITIONS + BEMAN_BIG_INT_X86_64_AVX512_IFMA=1 + BEMAN_BIG_INT_TEST_FORCED_IFMA +) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|amd64" AND NOT MSVC) + beman_big_int_config_defaults_test(bmi2_adx OPTIONS -mbmi2 -madx) +endif() + if(BEMAN_BIG_INT_FUZZ) add_subdirectory(fuzzing) endif() diff --git a/tests/beman/big_int/config.test.cpp b/tests/beman/big_int/config.test.cpp new file mode 100644 index 00000000..54f4622a --- /dev/null +++ b/tests/beman/big_int/config.test.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-License-Identifier: BSL-1.0 + +// Built through CMake, so detail/config.hpp must have picked up the generated +// config_generated.hpp, and the headers must see the options CMake resolved +// (passed in as BEMAN_BIG_INT_TEST_EXPECTED_* by CMakeLists.txt). + +#include + +#include + +#include + +#ifndef BEMAN_BIG_INT_CONFIG_GENERATED + #error "detail/config_generated.hpp is not on the include path of a CMake build" +#endif + +#define BEMAN_BIG_INT_TEST_STRINGIZE_IMPL(...) #__VA_ARGS__ +#define BEMAN_BIG_INT_TEST_STRINGIZE(...) BEMAN_BIG_INT_TEST_STRINGIZE_IMPL(__VA_ARGS__) + +namespace { + +TEST(Config, Namespace) { + EXPECT_EQ(std::string_view{BEMAN_BIG_INT_TEST_STRINGIZE(BEMAN_BIG_INT_NAMESPACE)}, + std::string_view{BEMAN_BIG_INT_TEST_EXPECTED_NAMESPACE}); +} + +TEST(Config, X86KernelChoices) { + // The choices only apply where the x86-64 kernels are called at all. +#ifdef BEMAN_BIG_INT_ARCH_X86_64 + constexpr int bmi2_adx = BEMAN_BIG_INT_TEST_EXPECTED_BMI2_ADX; + constexpr int avx512_ifma = BEMAN_BIG_INT_TEST_EXPECTED_AVX512_IFMA; +#else + constexpr int bmi2_adx = 0; + constexpr int avx512_ifma = 0; +#endif + EXPECT_EQ(BEMAN_BIG_INT_X86_64_BMI2_ADX, bmi2_adx); + EXPECT_EQ(BEMAN_BIG_INT_X86_64_AVX512_IFMA, avx512_ifma); +} + +TEST(Config, SimdMul) { +#ifdef BEMAN_BIG_INT_SIMD_MUL + constexpr bool simd_mul = true; +#else + constexpr bool simd_mul = false; +#endif + EXPECT_EQ(simd_mul, BEMAN_BIG_INT_TEST_EXPECTED_SIMD_MUL != 0); +} + +} // namespace diff --git a/tests/beman/big_int/config_defaults.cpp b/tests/beman/big_int/config_defaults.cpp new file mode 100644 index 00000000..7f74807e --- /dev/null +++ b/tests/beman/big_int/config_defaults.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-License-Identifier: BSL-1.0 + +// detail/config.hpp's defaults for a build that does not use CMake, checked +// against the compiler's own feature macros. The targets built from this file do +// not link the library, so config_generated.hpp is not on their include path +// (see CMakeLists.txt). BEMAN_BIG_INT_TEST_FORCED_IFMA marks the variant that +// defines BEMAN_BIG_INT_X86_64_AVX512_IFMA=1 itself. + +#include + +#include + +#ifdef BEMAN_BIG_INT_CONFIG_GENERATED + #error "detail/config_generated.hpp must not be reachable without CMake" +#endif + +// Exact only under floating-point flags that cannot be queried, so never on by default. +#ifdef BEMAN_BIG_INT_SIMD_MUL + #error "BEMAN_BIG_INT_SIMD_MUL must never be enabled by default" +#endif + +namespace { + +#if defined(__BMI2__) && defined(__ADX__) +constexpr bool compiler_bmi2_adx = true; +#else +constexpr bool compiler_bmi2_adx = false; +#endif + +#if defined(__AVX512IFMA__) && defined(__AVX512VL__) && defined(__AVX512BW__) && defined(__AVX512VBMI__) +constexpr bool compiler_avx512_ifma = true; +#else +constexpr bool compiler_avx512_ifma = false; +#endif + +#ifdef BEMAN_BIG_INT_TEST_FORCED_IFMA +constexpr bool forced_ifma = true; +#else +constexpr bool forced_ifma = false; +#endif + +#ifdef BEMAN_BIG_INT_ARCH_X86_64 +constexpr bool x86_64_kernels = true; +#else +constexpr bool x86_64_kernels = false; +#endif + +TEST(ConfigDefaults, Bmi2Adx) { + // Forcing the IFMA kernels on also forces their small-operand fallback, as CMake does. + const bool expected = x86_64_kernels && (compiler_bmi2_adx || forced_ifma); + EXPECT_EQ(BEMAN_BIG_INT_X86_64_BMI2_ADX, expected ? 1 : 0); +} + +TEST(ConfigDefaults, Avx512Ifma) { + const bool expected = x86_64_kernels && (forced_ifma || (compiler_avx512_ifma && compiler_bmi2_adx)); + EXPECT_EQ(BEMAN_BIG_INT_X86_64_AVX512_IFMA, expected ? 1 : 0); +} + +} // namespace diff --git a/tests/beman/big_int/module/CMakeLists.txt b/tests/beman/big_int/module/CMakeLists.txt index 356fae04..50eec95e 100644 --- a/tests/beman/big_int/module/CMakeLists.txt +++ b/tests/beman/big_int/module/CMakeLists.txt @@ -16,11 +16,9 @@ include(GoogleTest) # The sources here name the library's namespace through BEMAN_BIG_INT_NAMESPACE, which -# cannot cross the module boundary from detail/config.hpp, so it is passed on the -# command line. A non-default namespace already arrives PUBLIC from beman.big_int. -if(BEMAN_BIG_INT_NAMESPACE STREQUAL "beman::big_int") - add_compile_definitions(BEMAN_BIG_INT_NAMESPACE=beman::big_int) -endif() +# cannot cross the module boundary from detail/config_generated.hpp, so it is passed +# on the command line. +add_compile_definitions("BEMAN_BIG_INT_NAMESPACE=${BEMAN_BIG_INT_NAMESPACE}") # The project's test warning set, so a diagnostic only the module path produces is # still an error. diff --git a/tests/beman/big_int/perf/shape_sweep.cpp b/tests/beman/big_int/perf/shape_sweep.cpp index 0315e6f4..c3f7998a 100644 --- a/tests/beman/big_int/perf/shape_sweep.cpp +++ b/tests/beman/big_int/perf/shape_sweep.cpp @@ -17,15 +17,15 @@ // -DBEMAN_BIG_INT_SWEEP_GMP, 64-bit limbs and -lgmp; div and rem time only the named operator, so GMP's mpn_tdiv_qr // (quotient and remainder) is paired with divrem only. // -// Build through CMake (target beman.big_int.benchmarks.shape_sweep, BEMAN_BIG_INT_BUILD_BENCHMARKS=ON), which passes -// the library's PUBLIC definitions (BMI2_ADX, AVX512_IFMA, SIMD_MUL, FORCED_LIMB_WIDTH) and the compile flags. A -// manual build must reproduce that full set: the same BEMAN_BIG_INT_X86_64_BMI2_ADX / _AVX512_IFMA values, SIMD_MUL, -// BEMAN_BIG_INT_FORCED_LIMB_WIDTH, -march / -mavx2 style flags and NDEBUG as the library was built with (see the -// library target's entries in the build directory's compile_commands.json), and must pass +// Build through CMake (target beman.big_int.benchmarks.shape_sweep, BEMAN_BIG_INT_BUILD_BENCHMARKS=ON), which puts +// the library's generated detail/config_generated.hpp (BMI2_ADX, AVX512_IFMA, SIMD_MUL) on the include path and passes +// the compile flags. A manual build must reproduce that: the build directory's generated/include on the include path, +// plus the same BEMAN_BIG_INT_FORCED_LIMB_WIDTH, -march / -mavx2 style flags and NDEBUG as the library was built with +// (see the library target's entries in the build directory's compile_commands.json), and must pass // -DBEMAN_BIG_INT_SWEEP_MANUAL_OK. Otherwise the harness TU can disagree with the library and #const would lie. -// L=build/appleclang-release/libbeman.big_int.a -// c++ -std=c++23 -O2 -DNDEBUG -DBEMAN_BIG_INT_SWEEP_MANUAL_OK -Iinclude -o shape_sweep $L -// tests/beman/big_int/perf/shape_sweep.cpp (one command; keep the source before or after $L as usual) +// B=build/appleclang-release +// c++ -std=c++23 -O2 -DNDEBUG -DBEMAN_BIG_INT_SWEEP_MANUAL_OK -I$B/generated/include -Iinclude -o shape_sweep +// $B/libbeman.big_int.a tests/beman/big_int/perf/shape_sweep.cpp (one command; source before or after the .a) #if defined(BEMAN_BIG_INT_SWEEP_VIA_CMAKE) #include "shape_sweep_build_info.hpp" From 441a63d76860df00b35705c8fab92560e9f06174 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Oct 2026 15:51:54 -0400 Subject: [PATCH 3/4] Consolidate intrinsics usage into config --- docs/modules/ROOT/pages/build_and_usage.adoc | 4 +- include/beman/big_int/detail/config.hpp | 379 +++++++++++++++++-- include/beman/big_int/detail/floats.hpp | 61 ++- include/beman/big_int/detail/wide_ops.hpp | 109 +++--- module/big_int.cppm | 8 +- src/ntt_fp_dispatch.cpp | 18 +- tests/beman/big_int/conversion.test.cpp | 2 +- 7 files changed, 441 insertions(+), 140 deletions(-) diff --git a/docs/modules/ROOT/pages/build_and_usage.adoc b/docs/modules/ROOT/pages/build_and_usage.adoc index 4be27a92..65b4701d 100644 --- a/docs/modules/ROOT/pages/build_and_usage.adoc +++ b/docs/modules/ROOT/pages/build_and_usage.adoc @@ -227,8 +227,8 @@ CMake writes a generated header, `beman/big_int/detail/config_generated.hpp`, under the build directory's `generated/include` and installs it with the other headers. It holds the options above together with the result of the library's feature detection (standard library features, `_BitInt` and `__int128` -support, exceptions, the target architecture and its instruction set -extensions), which CMake runs once at configure time with the compiler, language +support, compiler builtins and intrinsics, the `long double` format, exceptions, +the target architecture and its instruction set extensions), which CMake runs once at configure time with the compiler, language standard and `CMAKE_CXX_FLAGS` of the build. `detail/config.hpp` includes it instead of detecting anything itself whenever it is on the include path, so the compiled library and every translation unit that uses it agree on all of these. diff --git a/include/beman/big_int/detail/config.hpp b/include/beman/big_int/detail/config.hpp index 29ea1143..fac192bd 100644 --- a/include/beman/big_int/detail/config.hpp +++ b/include/beman/big_int/detail/config.hpp @@ -7,6 +7,7 @@ // Guarding these includes is safe only because the .cppm supplies them in its // global module fragment before the purview include of this header. #ifndef BEMAN_BIG_INT_BUILD_MODULE + #include // for LDBL_MANT_DIG, LDBL_MAX_EXP #include // for BITINT_MAXWIDTH #include // for INTPTR_MAX #include // for __cpp_lib_* @@ -23,7 +24,9 @@ // Every feature-test, compiler-extension, and target-architecture macro the // library branches on is tested here and nowhere else; the rest of the library - // checks only the BEMAN_BIG_INT_HAS_* and BEMAN_BIG_INT_TARGET_* spellings below. + // checks only the BEMAN_BIG_INT_HAS_*, BEMAN_BIG_INT_TARGET_* and + // BEMAN_BIG_INT_LONG_DOUBLE_* spellings below, and the BEMAN_BIG_INT_BUILTIN_* + // and BEMAN_BIG_INT_INTRINSIC_* wrappers defined from them after this branch. // Each is defined to 1 when the feature is available and left undefined otherwise. // Language features @@ -77,6 +80,18 @@ #define BEMAN_BIG_INT_HAS_STDCPP_FLOAT128_T 1 #endif + // long double format + + #if !defined(LDBL_MANT_DIG) || !defined(LDBL_MAX_EXP) + #error Cannot determine the format of long double without LDBL_MANT_DIG and LDBL_MAX_EXP. + #elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 + #define BEMAN_BIG_INT_LONG_DOUBLE_X87_EXTENDED 1 + #elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 + #define BEMAN_BIG_INT_LONG_DOUBLE_BINARY128 1 + #elif LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 + #define BEMAN_BIG_INT_LONG_DOUBLE_BINARY64 1 + #endif + // Bit-precise integers (_BitInt) #ifdef BITINT_MAXWIDTH @@ -107,6 +122,139 @@ #define BEMAN_BIG_INT_HAS_INT128_EXTENSION 1 #endif + // Compiler builtins + + // BEMAN_BIG_INT_HAS_BUILTIN_ means __builtin_ (or the type trait + // __) exists; BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_ means it is also + // usable during constant evaluation. Only Clang can test the latter exactly, so + // on GCC the floating-point builtins, which it folds, count whenever they exist. + // The library calls them through the BEMAN_BIG_INT_BUILTIN_* wrappers defined + // from these below. The DETECT helpers are function-like and #undef'd below, so + // the CMake probe never records them. + + #ifdef __has_builtin + #define BEMAN_BIG_INT_DETECT_BUILTIN(...) __has_builtin(__VA_ARGS__) + #else + #define BEMAN_BIG_INT_DETECT_BUILTIN(...) 0 + #endif + + #ifdef __has_constexpr_builtin + #define BEMAN_BIG_INT_DETECT_CONSTEXPR_BUILTIN(...) __has_constexpr_builtin(__VA_ARGS__) + #define BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(...) __has_constexpr_builtin(__VA_ARGS__) + #else + #define BEMAN_BIG_INT_DETECT_CONSTEXPR_BUILTIN(...) 0 + #define BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(...) BEMAN_BIG_INT_DETECT_BUILTIN(__VA_ARGS__) + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__is_integral) + #define BEMAN_BIG_INT_HAS_BUILTIN_IS_INTEGRAL 1 + #endif + + // MSVC provides this one without __has_builtin. + #if defined(_MSC_VER) || BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_is_constant_evaluated) + #define BEMAN_BIG_INT_HAS_BUILTIN_IS_CONSTANT_EVALUATED 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_constant_p) + #define BEMAN_BIG_INT_HAS_BUILTIN_CONSTANT_P 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_trap) + #define BEMAN_BIG_INT_HAS_BUILTIN_TRAP 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_add_overflow) + #define BEMAN_BIG_INT_HAS_BUILTIN_ADD_OVERFLOW 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_sub_overflow) + #define BEMAN_BIG_INT_HAS_BUILTIN_SUB_OVERFLOW 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_mul_overflow) + #define BEMAN_BIG_INT_HAS_BUILTIN_MUL_OVERFLOW 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_addc) + #define BEMAN_BIG_INT_HAS_BUILTIN_ADDC 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_subc) + #define BEMAN_BIG_INT_HAS_BUILTIN_SUBC 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_BUILTIN(__builtin_addc) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_ADDC 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_BUILTIN(__builtin_subc) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_SUBC 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_elementwise_fshl) + #define BEMAN_BIG_INT_HAS_BUILTIN_ELEMENTWISE_FSHL 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_elementwise_fshr) + #define BEMAN_BIG_INT_HAS_BUILTIN_ELEMENTWISE_FSHR 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_signbit) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_SIGNBIT 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_isfinite) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_ISFINITE 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_copysign) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGN 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_copysignf) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGNF 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_copysignl) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGNL 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_ldexp) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXP 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_ldexpf) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXPF 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_ldexpl) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXPL 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_fabs) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABS 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_fabsf) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABSF 1 + #endif + + #if BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN(__builtin_fabsl) + #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABSL 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_cpu_init) + #define BEMAN_BIG_INT_HAS_BUILTIN_CPU_INIT 1 + #endif + + #if BEMAN_BIG_INT_DETECT_BUILTIN(__builtin_cpu_supports) + #define BEMAN_BIG_INT_HAS_BUILTIN_CPU_SUPPORTS 1 + #endif + + #undef BEMAN_BIG_INT_DETECT_BUILTIN + #undef BEMAN_BIG_INT_DETECT_CONSTEXPR_BUILTIN + #undef BEMAN_BIG_INT_DETECT_CONSTEXPR_MATH_BUILTIN + // Exceptions #if (defined(_MSC_VER) && defined(_CPPUNWIND)) || defined(__EXCEPTIONS) @@ -144,6 +292,31 @@ #define BEMAN_BIG_INT_HAS_AVX512_IFMA 1 #endif + // MSVC intrinsics () and GNU x86 inline assembly. Clang-cl also + // defines _MSC_VER, but takes the __builtin_* paths wherever both exist. + + // __umulh and __mulh + #if defined(_MSC_VER) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_AARCH64)) + #define BEMAN_BIG_INT_HAS_INTRINSIC_UMULH 1 + #endif + + // _umul128 and _mul128, _udiv128, _addcarry_u64 and _subborrow_u64 + #if defined(_MSC_VER) && defined(BEMAN_BIG_INT_TARGET_X86_64) + #define BEMAN_BIG_INT_HAS_INTRINSIC_UMUL128 1 + #define BEMAN_BIG_INT_HAS_INTRINSIC_UDIV128 1 + #define BEMAN_BIG_INT_HAS_INTRINSIC_ADDCARRY_U64 1 + #endif + + // _addcarry_u8/16/32 and _subborrow_u8/16/32; __cpuid, __cpuidex and _xgetbv + #if defined(_MSC_VER) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_X86_32)) + #define BEMAN_BIG_INT_HAS_INTRINSIC_ADDCARRY 1 + #define BEMAN_BIG_INT_HAS_INTRINSIC_CPUID 1 + #endif + + #if defined(__GNUC__) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_X86_32)) + #define BEMAN_BIG_INT_HAS_X86_GNU_ASM 1 + #endif + // Word size #if INTPTR_MAX == INT64_MAX || defined(BEMAN_BIG_INT_TARGET_WASM) @@ -241,26 +414,158 @@ #define BEMAN_BIG_INT_GNUC __GNUC__ #endif // __GNUC__ -// Builtin detection =========================================================== +// Compiler builtins =========================================================== -#ifdef __has_builtin - #define BEMAN_BIG_INT_HAS_BUILTIN(...) __has_builtin(__VA_ARGS__) -#else - #define BEMAN_BIG_INT_HAS_BUILTIN(...) 0 -#endif // __has_builtin - -// In addition to BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN, which is an exact test, -// `BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN` falls back onto `BEMAN_BIG_INT_HAS_BUILTIN` -// if constexpr builtin testing is not available. -// This is useful for GCC, which supports __has_builtin but not __has_constexpr_builtin, -// and many GCC intrinsics are usable during constant evaluation. -#ifdef __has_constexpr_builtin - #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN(...) __has_constexpr_builtin(__VA_ARGS__) - #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(...) __has_constexpr_builtin(__VA_ARGS__) -#else - #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN(...) 0 - #define BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(...) BEMAN_BIG_INT_HAS_BUILTIN(__VA_ARGS__) -#endif // __has_constexpr_builtin +// The library calls compiler builtins and intrinsics only through these wrappers, +// each defined only when feature detection found it, so a compiler that spells +// one differently needs a new mapping here and nowhere else. Code tests the +// wrapper itself with #ifdef, or BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_ where +// it must also work during constant evaluation. The floating-point ones are only +// ever needed there, so they are defined only when usable in constant evaluation. + +// Every supported compiler has these, so they are always defined. +#define BEMAN_BIG_INT_BUILTIN_FILE() __builtin_FILE() +#define BEMAN_BIG_INT_BUILTIN_LINE() __builtin_LINE() +#define BEMAN_BIG_INT_BUILTIN_FUNCTION() __builtin_FUNCTION() + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_IS_INTEGRAL + #define BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(...) __is_integral(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_IS_CONSTANT_EVALUATED + #define BEMAN_BIG_INT_BUILTIN_IS_CONSTANT_EVALUATED() __builtin_is_constant_evaluated() +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_CONSTANT_P + #define BEMAN_BIG_INT_BUILTIN_CONSTANT_P(...) __builtin_constant_p(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_TRAP + #define BEMAN_BIG_INT_BUILTIN_TRAP() __builtin_trap() +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_ADD_OVERFLOW + #define BEMAN_BIG_INT_BUILTIN_ADD_OVERFLOW(...) __builtin_add_overflow(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_SUB_OVERFLOW + #define BEMAN_BIG_INT_BUILTIN_SUB_OVERFLOW(...) __builtin_sub_overflow(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_MUL_OVERFLOW + #define BEMAN_BIG_INT_BUILTIN_MUL_OVERFLOW(...) __builtin_mul_overflow(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_ADDC + #define BEMAN_BIG_INT_BUILTIN_ADDC(...) __builtin_addc(__VA_ARGS__) + #define BEMAN_BIG_INT_BUILTIN_ADDCL(...) __builtin_addcl(__VA_ARGS__) + #define BEMAN_BIG_INT_BUILTIN_ADDCLL(...) __builtin_addcll(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_SUBC + #define BEMAN_BIG_INT_BUILTIN_SUBC(...) __builtin_subc(__VA_ARGS__) + #define BEMAN_BIG_INT_BUILTIN_SUBCL(...) __builtin_subcl(__VA_ARGS__) + #define BEMAN_BIG_INT_BUILTIN_SUBCLL(...) __builtin_subcll(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_ELEMENTWISE_FSHL + #define BEMAN_BIG_INT_BUILTIN_ELEMENTWISE_FSHL(...) __builtin_elementwise_fshl(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_ELEMENTWISE_FSHR + #define BEMAN_BIG_INT_BUILTIN_ELEMENTWISE_FSHR(...) __builtin_elementwise_fshr(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_SIGNBIT + #define BEMAN_BIG_INT_BUILTIN_SIGNBIT(...) __builtin_signbit(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_ISFINITE + #define BEMAN_BIG_INT_BUILTIN_ISFINITE(...) __builtin_isfinite(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGN + #define BEMAN_BIG_INT_BUILTIN_COPYSIGN(...) __builtin_copysign(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGNF + #define BEMAN_BIG_INT_BUILTIN_COPYSIGNF(...) __builtin_copysignf(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGNL + #define BEMAN_BIG_INT_BUILTIN_COPYSIGNL(...) __builtin_copysignl(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXP + #define BEMAN_BIG_INT_BUILTIN_LDEXP(...) __builtin_ldexp(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXPF + #define BEMAN_BIG_INT_BUILTIN_LDEXPF(...) __builtin_ldexpf(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXPL + #define BEMAN_BIG_INT_BUILTIN_LDEXPL(...) __builtin_ldexpl(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABS + #define BEMAN_BIG_INT_BUILTIN_FABS(...) __builtin_fabs(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABSF + #define BEMAN_BIG_INT_BUILTIN_FABSF(...) __builtin_fabsf(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABSL + #define BEMAN_BIG_INT_BUILTIN_FABSL(...) __builtin_fabsl(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_CPU_INIT + #define BEMAN_BIG_INT_BUILTIN_CPU_INIT() __builtin_cpu_init() +#endif + +#ifdef BEMAN_BIG_INT_HAS_BUILTIN_CPU_SUPPORTS + #define BEMAN_BIG_INT_BUILTIN_CPU_SUPPORTS(...) __builtin_cpu_supports(__VA_ARGS__) +#endif + +// The module interface unit includes in its global module fragment. +#if defined(BEMAN_BIG_INT_MSVC) && !defined(BEMAN_BIG_INT_BUILD_MODULE) + #include +#endif + +#ifdef BEMAN_BIG_INT_HAS_INTRINSIC_UMULH + #define BEMAN_BIG_INT_INTRINSIC_MULH(...) __mulh(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_UMULH(...) __umulh(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_INTRINSIC_UMUL128 + #define BEMAN_BIG_INT_INTRINSIC_MUL128(...) _mul128(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_UMUL128(...) _umul128(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_INTRINSIC_UDIV128 + #define BEMAN_BIG_INT_INTRINSIC_UDIV128(...) _udiv128(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_INTRINSIC_ADDCARRY + #define BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U8(...) _addcarry_u8(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U16(...) _addcarry_u16(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U32(...) _addcarry_u32(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U8(...) _subborrow_u8(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U16(...) _subborrow_u16(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U32(...) _subborrow_u32(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_INTRINSIC_ADDCARRY_U64 + #define BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U64(...) _addcarry_u64(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U64(...) _subborrow_u64(__VA_ARGS__) +#endif + +#ifdef BEMAN_BIG_INT_HAS_INTRINSIC_CPUID + #define BEMAN_BIG_INT_INTRINSIC_CPUID(...) __cpuid(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_CPUIDEX(...) __cpuidex(__VA_ARGS__) + #define BEMAN_BIG_INT_INTRINSIC_XGETBV(...) _xgetbv(__VA_ARGS__) +#endif // Undefine min()/max() for MSVC =============================================== #ifdef min @@ -490,15 +795,17 @@ concept character_type = // std::is_same_v || std::is_same_v || // std::is_same_v || std::is_same_v || std::is_same_v; -#if BEMAN_BIG_INT_HAS_BUILTIN(__is_integral) +#ifdef BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL #ifdef BEMAN_BIG_INT_HAS_BITINT -static_assert(__is_integral(bit_int<32>) && __is_integral(bit_int<32>), +static_assert(BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(bit_int<32>) && BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(bit_uint<32>), "Bad compiler builtin __is_integral rejects _BitInt."); #endif -static_assert(__is_integral(int) && __is_integral(const volatile unsigned int)); -static_assert(__is_integral(char) && __is_integral(signed char) && __is_integral(unsigned char)); +static_assert(BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(int) && + BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(const volatile unsigned int)); +static_assert(BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(char) && BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(signed char) && + BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(unsigned char)); template -concept integral = __is_integral(T); +concept integral = BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(T); #elif defined(BEMAN_BIG_INT_HAS_BITINT) // If bit-precise integers do exist but we don't have a builtin __is_integral, // we need to create our own concept for integral types that also includes _BitInt. @@ -755,12 +1062,12 @@ namespace detail { // builtins carry the same caller location with no consteval machinery and // exist on GCC, Clang, and MSVC. [[noreturn]] inline void assert_fail(const char* const source, - const char* const file = __builtin_FILE(), - const int line = __builtin_LINE(), - const char* const function = __builtin_FUNCTION()) { + const char* const file = BEMAN_BIG_INT_BUILTIN_FILE(), + const int line = BEMAN_BIG_INT_BUILTIN_LINE(), + const char* const function = BEMAN_BIG_INT_BUILTIN_FUNCTION()) { std::fprintf(stderr, "%s:%d Assertion failed: %s\nSee: %s\n", file, line, source, function); -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_trap) - __builtin_trap(); +#ifdef BEMAN_BIG_INT_BUILTIN_TRAP + BEMAN_BIG_INT_BUILTIN_TRAP(); #else std::abort(); #endif @@ -789,13 +1096,13 @@ BEMAN_BIG_INT_END_NAMESPACE // In MSVC, all code following `if !consteval` is considered unreachable. // The warning is also impossible to suppress, so NEVER use `if !consteval` on MSVC. // https://developercommunity.microsoft.com/t/Code-following-if-consteval-is-unreac/11073119 - #define BEMAN_BIG_INT_IS_NOT_CONSTEVAL (!__builtin_is_constant_evaluated()) + #define BEMAN_BIG_INT_IS_NOT_CONSTEVAL (!BEMAN_BIG_INT_BUILTIN_IS_CONSTANT_EVALUATED()) #else #define BEMAN_BIG_INT_IS_NOT_CONSTEVAL !consteval #endif // BEMAN_BIG_INT_MSVC -#elif BEMAN_BIG_INT_HAS_BUILTIN(__builtin_is_constant_evaluated) - #define BEMAN_BIG_INT_IS_CONSTEVAL (__builtin_is_constant_evaluated()) - #define BEMAN_BIG_INT_IS_NOT_CONSTEVAL (!__builtin_is_constant_evaluated()) +#elif defined(BEMAN_BIG_INT_BUILTIN_IS_CONSTANT_EVALUATED) + #define BEMAN_BIG_INT_IS_CONSTEVAL (BEMAN_BIG_INT_BUILTIN_IS_CONSTANT_EVALUATED()) + #define BEMAN_BIG_INT_IS_NOT_CONSTEVAL (!BEMAN_BIG_INT_BUILTIN_IS_CONSTANT_EVALUATED()) #else #ifndef BEMAN_BIG_INT_BUILD_MODULE #include @@ -820,8 +1127,8 @@ BEMAN_BIG_INT_END_NAMESPACE // Constant propagation detection ============================================== -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_constant_p) - #define BEMAN_BIG_INT_IS_CONSTANT_PROPAGATED(...) __builtin_constant_p(__VA_ARGS__) +#ifdef BEMAN_BIG_INT_BUILTIN_CONSTANT_P + #define BEMAN_BIG_INT_IS_CONSTANT_PROPAGATED(...) BEMAN_BIG_INT_BUILTIN_CONSTANT_P(__VA_ARGS__) #else #define BEMAN_BIG_INT_IS_CONSTANT_PROPAGATED(...) (void(__VA_ARGS__), false) #endif diff --git a/include/beman/big_int/detail/floats.hpp b/include/beman/big_int/detail/floats.hpp index d6ae5db3..a6fd9be6 100644 --- a/include/beman/big_int/detail/floats.hpp +++ b/include/beman/big_int/detail/floats.hpp @@ -8,7 +8,6 @@ #include #include #include - #include #include #include #include @@ -110,11 +109,7 @@ struct x87_extended_float_bits { std::uint16_t sign_and_exponent; }; -#if !defined(LDBL_MANT_DIG) || !defined(LDBL_MAX_EXP) - #error Cannot define ieee_traits without LDBL_MANT_DIG and LDBL_MAX_EXP. -#endif - -#if LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 +#ifdef BEMAN_BIG_INT_LONG_DOUBLE_X87_EXTENDED template <> struct ieee_traits { using bits_type = x87_extended_float_bits; @@ -125,7 +120,7 @@ struct ieee_traits { static constexpr int bias = 16383; static constexpr bool explicit_int_bit = true; }; -#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 +#elif defined(BEMAN_BIG_INT_LONG_DOUBLE_BINARY128) template <> struct ieee_traits { using bits_type = uint128_t; @@ -136,7 +131,7 @@ struct ieee_traits { static constexpr int bias = 16383; static constexpr bool explicit_int_bit = false; }; -#elif LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +#elif defined(BEMAN_BIG_INT_LONG_DOUBLE_BINARY64) template <> struct ieee_traits : ieee_traits {}; #else @@ -147,8 +142,8 @@ template [[nodiscard]] constexpr bool constexpr_signbit(const F x) noexcept { #ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::signbit(x); -#elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_signbit) - return static_cast(__builtin_signbit(x)); +#elif defined(BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_SIGNBIT) + return static_cast(BEMAN_BIG_INT_BUILTIN_SIGNBIT(x)); #else if BEMAN_BIG_INT_IS_CONSTEVAL { using bits_t = typename ieee_traits::bits_type; @@ -169,8 +164,8 @@ template [[nodiscard]] constexpr bool constexpr_isfinite(const F x) noexcept { #ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::isfinite(x); -#elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_isfinite) - return static_cast(__builtin_isfinite(x)); +#elif defined(BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_ISFINITE) + return static_cast(BEMAN_BIG_INT_BUILTIN_ISFINITE(x)); #else if BEMAN_BIG_INT_IS_CONSTEVAL { BEMAN_BIG_INT_DIAGNOSTIC_PUSH() @@ -188,20 +183,20 @@ template [[nodiscard]] constexpr F constexpr_copysign(const F x, const F s) noexcept { #ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::copysign(x, s); -#elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_copysign) - if constexpr (std::is_same_v) { +#elif defined(BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGN) + if constexpr (std::is_same_v) { // The builtin is either generic or F is double. - return __builtin_copysign(x, s); + return BEMAN_BIG_INT_BUILTIN_COPYSIGN(x, s); } else { if BEMAN_BIG_INT_IS_CONSTEVAL { - #if BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_copysignf) + #ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGNF if constexpr (std::is_same_v) { - return __builtin_copysignf(x, s); + return BEMAN_BIG_INT_BUILTIN_COPYSIGNF(x, s); } #endif - #if BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_copysignl) + #ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_COPYSIGNL if constexpr (std::is_same_v) { - return __builtin_copysignl(x, s); + return BEMAN_BIG_INT_BUILTIN_COPYSIGNL(x, s); } #endif return constexpr_signbit(x) == constexpr_signbit(s) ? x : -x; @@ -239,20 +234,20 @@ template [[nodiscard]] constexpr F constexpr_ldexp(const F x, const int exp) noexcept { #ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::ldexp(x, exp); -#elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_ldexp) - if constexpr (std::is_same_v) { +#elif defined(BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXP) + if constexpr (std::is_same_v) { // The builtin is either generic or F is double. - return __builtin_ldexp(x, exp); + return BEMAN_BIG_INT_BUILTIN_LDEXP(x, exp); } else { if BEMAN_BIG_INT_IS_CONSTEVAL { - #if BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_ldexpf) + #ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXPF if constexpr (std::is_same_v) { - return __builtin_ldexpf(x, exp); + return BEMAN_BIG_INT_BUILTIN_LDEXPF(x, exp); } #endif - #if BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_ldexpl) + #ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_LDEXPL if constexpr (std::is_same_v) { - return __builtin_ldexpl(x, exp); + return BEMAN_BIG_INT_BUILTIN_LDEXPL(x, exp); } #endif return consteval_ldexp(x, exp); @@ -273,20 +268,20 @@ template [[nodiscard]] constexpr F constexpr_fabs(const F x) noexcept { #ifdef BEMAN_BIG_INT_HAS_CPP_LIB_CONSTEXPR_CMATH return std::fabs(x); -#elif BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_fabs) - if constexpr (std::is_same_v) { +#elif defined(BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABS) + if constexpr (std::is_same_v) { // The builtin is either generic or F is double. - return __builtin_fabs(x); + return BEMAN_BIG_INT_BUILTIN_FABS(x); } else { if BEMAN_BIG_INT_IS_CONSTEVAL { - #if BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_fabsf) + #ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABSF if constexpr (std::is_same_v) { - return __builtin_fabsf(x); + return BEMAN_BIG_INT_BUILTIN_FABSF(x); } #endif - #if BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_OR_BUILTIN(__builtin_fabsl) + #ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_FABSL if constexpr (std::is_same_v) { - return __builtin_fabsl(x); + return BEMAN_BIG_INT_BUILTIN_FABSL(x); } #endif return constexpr_signbit(x) ? -x : x; diff --git a/include/beman/big_int/detail/wide_ops.hpp b/include/beman/big_int/detail/wide_ops.hpp index 161631de..0985a978 100644 --- a/include/beman/big_int/detail/wide_ops.hpp +++ b/include/beman/big_int/detail/wide_ops.hpp @@ -10,12 +10,6 @@ #include -#ifdef BEMAN_BIG_INT_MSVC - #ifndef BEMAN_BIG_INT_BUILD_MODULE - #include - #endif -#endif - BEMAN_BIG_INT_BEGIN_NAMESPACE namespace detail { @@ -201,12 +195,12 @@ constexpr T high_mul(const T x, const T y) noexcept { } else { // MSVC intrinsics are not usable during constant evaluation, so fall through // to the portable path when we're in a consteval context. - #if defined(BEMAN_BIG_INT_MSVC) + #ifdef BEMAN_BIG_INT_INTRINSIC_UMULH if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { if constexpr (std::is_signed_v) { - return __mulh(x, y); + return BEMAN_BIG_INT_INTRINSIC_MULH(x, y); } else { - return __umulh(x, y); + return BEMAN_BIG_INT_INTRINSIC_UMULH(x, y); } } #endif @@ -240,17 +234,17 @@ template const auto product = static_cast>(x) * static_cast>(y); return wide::from_int(product); } else { - #if defined(BEMAN_BIG_INT_MSVC) && defined(BEMAN_BIG_INT_TARGET_X86_64) + #ifdef BEMAN_BIG_INT_INTRINSIC_UMUL128 // MSVC intrinsics are not usable during constant evaluation, so fall through // to the portable path when we're in a consteval context. if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { if constexpr (std::is_signed_v) { - __int64 high; - __int64 low = _mul128(x, y, &high); + std::int64_t high; + std::int64_t low = BEMAN_BIG_INT_INTRINSIC_MUL128(x, y, &high); return {low, high}; } else { - unsigned __int64 high; - unsigned __int64 low = _umul128(x, y, &high); + std::uint64_t high; + std::uint64_t low = BEMAN_BIG_INT_INTRINSIC_UMUL128(x, y, &high); return {low, high}; } } @@ -276,8 +270,8 @@ BEMAN_BIG_INT_DIAGNOSTIC_IGNORED_GCC("-Wuseless-cast") template [[nodiscard]] constexpr T funnel_shl(const wide x, const unsigned s) { BEMAN_BIG_INT_DEBUG_ASSERT(s < width_v); -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_elementwise_fshl) - return __builtin_elementwise_fshl(x.high_bits, x.low_bits, static_cast(s)); +#ifdef BEMAN_BIG_INT_BUILTIN_ELEMENTWISE_FSHL + return BEMAN_BIG_INT_BUILTIN_ELEMENTWISE_FSHL(x.high_bits, x.low_bits, static_cast(s)); #else if (s == 0) { return x.high_bits; @@ -293,8 +287,8 @@ BEMAN_BIG_INT_DIAGNOSTIC_POP() template [[nodiscard]] constexpr T funnel_shr(const wide x, const unsigned s) { BEMAN_BIG_INT_DEBUG_ASSERT(s < width_v); -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_elementwise_fshr) - return __builtin_elementwise_fshr(x.high_bits, x.low_bits, static_cast(s)); +#ifdef BEMAN_BIG_INT_BUILTIN_ELEMENTWISE_FSHR + return BEMAN_BIG_INT_BUILTIN_ELEMENTWISE_FSHR(x.high_bits, x.low_bits, static_cast(s)); #else // It usually makes sense to implement this in terms of a right-shift of a wider type. // However, the 128-bit version optimizes poorly; @@ -324,9 +318,9 @@ struct overflow_result { template [[nodiscard]] constexpr overflow_result overflowing_add(const T x, const T y) noexcept { -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_add_overflow) +#ifdef BEMAN_BIG_INT_BUILTIN_ADD_OVERFLOW T value; - bool overflow = __builtin_add_overflow(x, y, &value); + bool overflow = BEMAN_BIG_INT_BUILTIN_ADD_OVERFLOW(x, y, &value); return {.value = value, .overflow = overflow}; #else if constexpr (std::is_unsigned_v) { @@ -338,14 +332,14 @@ template const auto value = static_cast(wide); return {.value = value, .overflow = wide != value}; } -#endif // __builtin_add_overflow +#endif // BEMAN_BIG_INT_BUILTIN_ADD_OVERFLOW } template [[nodiscard]] constexpr overflow_result overflowing_sub(const T x, const T y) noexcept { -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_sub_overflow) +#ifdef BEMAN_BIG_INT_BUILTIN_SUB_OVERFLOW T value; - bool overflow = __builtin_sub_overflow(x, y, &value); + bool overflow = BEMAN_BIG_INT_BUILTIN_SUB_OVERFLOW(x, y, &value); return {.value = value, .overflow = overflow}; #else if constexpr (std::is_unsigned_v) { @@ -355,19 +349,19 @@ template const auto value = static_cast(wide); return {.value = value, .overflow = wide != value}; } -#endif // __builtin_sub_overflow +#endif // BEMAN_BIG_INT_BUILTIN_SUB_OVERFLOW } template [[nodiscard]] constexpr overflow_result overflowing_mul(const T x, const T y) noexcept { -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_mul_overflow) +#ifdef BEMAN_BIG_INT_BUILTIN_MUL_OVERFLOW T value; - bool overflow = __builtin_mul_overflow(x, y, &value); + bool overflow = BEMAN_BIG_INT_BUILTIN_MUL_OVERFLOW(x, y, &value); return {.value = value, .overflow = overflow}; #else const T value = x * y; return {.value = value, .overflow = x != 0 && value / x != y}; -#endif // __builtin_mul_overflow +#endif // BEMAN_BIG_INT_BUILTIN_MUL_OVERFLOW } template @@ -386,8 +380,8 @@ carrying_add_portable(const T x, const T y, const bool carry = false) noexcept { template [[nodiscard]] constexpr carry_result carrying_add(const T x, const T y, const bool carry = false) noexcept { -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_addc) - #if BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN(__builtin_addc) +#ifdef BEMAN_BIG_INT_BUILTIN_ADDC + #ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_ADDC if constexpr (true) #else if BEMAN_BIG_INT_IS_NOT_CONSTEVAL @@ -395,15 +389,15 @@ template { if constexpr (width_v == width_v) { unsigned carry_out; - unsigned value = __builtin_addc(x, y, carry, &carry_out); + unsigned value = BEMAN_BIG_INT_BUILTIN_ADDC(x, y, carry, &carry_out); return {.value = value, .carry = carry_out != 0}; } else if constexpr (width_v == width_v) { unsigned long carry_out; - unsigned long value = __builtin_addcl(x, y, carry, &carry_out); + unsigned long value = BEMAN_BIG_INT_BUILTIN_ADDCL(x, y, carry, &carry_out); return {.value = value, .carry = carry_out != 0}; } else if constexpr (width_v == width_v) { unsigned long long carry_out; - unsigned long long value = __builtin_addcll(x, y, carry, &carry_out); + unsigned long long value = BEMAN_BIG_INT_BUILTIN_ADDCLL(x, y, carry, &carry_out); return {.value = value, .carry = carry_out != 0}; } else { return carrying_add_portable(x, y, carry); @@ -411,7 +405,7 @@ template } else { return carrying_add_portable(x, y, carry); } -#elif defined(BEMAN_BIG_INT_MSVC) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_X86_32)) +#elif defined(BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U32) if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { // Theoretically we could use the ADX intrinsic, but then the user always has to compile for it // This is more portable. @@ -419,21 +413,25 @@ template // across the `if constexpr` chain. if constexpr (width_v == 8) { std::uint8_t value; - const unsigned char carry_out = _addcarry_u8(static_cast(carry), x, y, &value); + const unsigned char carry_out = + BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U8(static_cast(carry), x, y, &value); return {.value = value, .carry = carry_out != 0}; } else if constexpr (width_v == 16) { std::uint16_t value; - const unsigned char carry_out = _addcarry_u16(static_cast(carry), x, y, &value); + const unsigned char carry_out = + BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U16(static_cast(carry), x, y, &value); return {.value = value, .carry = carry_out != 0}; } else if constexpr (width_v == 32) { std::uint32_t value; - const unsigned char carry_out = _addcarry_u32(static_cast(carry), x, y, &value); + const unsigned char carry_out = + BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U32(static_cast(carry), x, y, &value); return {.value = value, .carry = carry_out != 0}; } - #ifdef BEMAN_BIG_INT_TARGET_X86_64 + #ifdef BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U64 else if constexpr (width_v == 64) { std::uint64_t value; - const unsigned char carry_out = _addcarry_u64(static_cast(carry), x, y, &value); + const unsigned char carry_out = + BEMAN_BIG_INT_INTRINSIC_ADDCARRY_U64(static_cast(carry), x, y, &value); return {.value = value, .carry = carry_out != 0}; } #endif @@ -466,8 +464,8 @@ borrowing_sub_portable(const T x, const T y, const bool borrow = false) noexcept template [[nodiscard]] constexpr borrow_result borrowing_sub(const T x, const T y, const bool borrow = false) noexcept { -#if BEMAN_BIG_INT_HAS_BUILTIN(__builtin_subc) - #if BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN(__builtin_subc) +#ifdef BEMAN_BIG_INT_BUILTIN_SUBC + #ifdef BEMAN_BIG_INT_HAS_CONSTEXPR_BUILTIN_SUBC if constexpr (true) #else if BEMAN_BIG_INT_IS_NOT_CONSTEVAL @@ -475,15 +473,15 @@ template { if constexpr (width_v == width_v) { unsigned borrow_out; - unsigned value = __builtin_subc(x, y, borrow, &borrow_out); + unsigned value = BEMAN_BIG_INT_BUILTIN_SUBC(x, y, borrow, &borrow_out); return {.value = value, .borrow = borrow_out != 0}; } else if constexpr (width_v == width_v) { unsigned long borrow_out; - unsigned long value = __builtin_subcl(x, y, borrow, &borrow_out); + unsigned long value = BEMAN_BIG_INT_BUILTIN_SUBCL(x, y, borrow, &borrow_out); return {.value = value, .borrow = borrow_out != 0}; } else if constexpr (width_v == width_v) { unsigned long long borrow_out; - unsigned long long value = __builtin_subcll(x, y, borrow, &borrow_out); + unsigned long long value = BEMAN_BIG_INT_BUILTIN_SUBCLL(x, y, borrow, &borrow_out); return {.value = value, .borrow = borrow_out != 0}; } else { return borrowing_sub_portable(x, y, borrow); @@ -491,31 +489,35 @@ template } else { return borrowing_sub_portable(x, y, borrow); } -#elif defined(BEMAN_BIG_INT_MSVC) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_X86_32)) +#elif defined(BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U32) if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { // Mirror the `carrying_add` MSVC path using the matching `_subborrow_*` intrinsics. // Each branch returns directly, so we don't share uninitialized state across the // `if constexpr` chain. if constexpr (width_v == 8) { std::uint8_t value; - const unsigned char borrow_out = _subborrow_u8(static_cast(borrow), x, y, &value); + const unsigned char borrow_out = + BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U8(static_cast(borrow), x, y, &value); return {.value = value, .borrow = borrow_out != 0}; } else if constexpr (width_v == 16) { std::uint16_t value; - const unsigned char borrow_out = _subborrow_u16(static_cast(borrow), x, y, &value); + const unsigned char borrow_out = + BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U16(static_cast(borrow), x, y, &value); return {.value = value, .borrow = borrow_out != 0}; } else if constexpr (width_v == 32) { std::uint32_t value; - const unsigned char borrow_out = _subborrow_u32(static_cast(borrow), x, y, &value); + const unsigned char borrow_out = + BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U32(static_cast(borrow), x, y, &value); return {.value = value, .borrow = borrow_out != 0}; } - #ifdef BEMAN_BIG_INT_TARGET_X86_64 + #ifdef BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U64 else if constexpr (width_v == 64) { std::uint64_t value; - const unsigned char borrow_out = _subborrow_u64(static_cast(borrow), x, y, &value); + const unsigned char borrow_out = + BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U64(static_cast(borrow), x, y, &value); return {.value = value, .borrow = borrow_out != 0}; } - #endif // BEMAN_BIG_INT_TARGET_X86_64 + #endif // BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U64 else { return borrowing_sub_portable(x, y, borrow); } @@ -583,7 +585,7 @@ template if constexpr (width_v == 64) { if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { if (!BEMAN_BIG_INT_IS_CONSTANT_PROPAGATED(y)) { - #if defined(BEMAN_BIG_INT_GNUC) && (defined(BEMAN_BIG_INT_TARGET_X86_64) || defined(BEMAN_BIG_INT_TARGET_X86_32)) + #ifdef BEMAN_BIG_INT_HAS_X86_GNU_ASM T q, r; // volatile is load-bearing: a non-volatile asm counts as // pure, and GCC at -O2 speculatively hoisted this above a @@ -593,9 +595,10 @@ template // This has happened with GCC-14 in release mode __asm__ volatile("div %[d]" : "=a"(q), "=d"(r) : "a"(x.low_bits), "d"(x.high_bits), [d] "r"(y) : "cc"); return {.quotient = q, .remainder = r}; - #elif defined(BEMAN_BIG_INT_MSVC) && defined(BEMAN_BIG_INT_TARGET_X86_64) + #elif defined(BEMAN_BIG_INT_INTRINSIC_UDIV128) T r; - T q = _udiv128(static_cast(x.high_bits), static_cast(x.low_bits), static_cast(y), &r); + T q = BEMAN_BIG_INT_INTRINSIC_UDIV128( + static_cast(x.high_bits), static_cast(x.low_bits), static_cast(y), &r); return {.quotient = static_cast(q), .remainder = static_cast(r)}; #endif } diff --git a/module/big_int.cppm b/module/big_int.cppm index 3e563b2a..91ed4d9c 100644 --- a/module/big_int.cppm +++ b/module/big_int.cppm @@ -5,8 +5,8 @@ module; // Tier A - platform headers, always textual regardless of import std. Only two, // and only on MSVC: <__msvc_int128.hpp> supplies std::_Signed128/std::_Unsigned128 -// for detail::int128_t, and supplies the widening-multiply / bit-scan -// intrinsics detail/wide_ops.hpp uses on that compiler. +// for detail::int128_t, and supplies the intrinsics detail/config.hpp +// wraps on that compiler. // // Deliberately NOT included here: and . Those are // reachable only from src/ntt_fp_avx2.cpp and src/ntt_fp_neon.cpp, which are @@ -17,7 +17,7 @@ module; // actively wrong. #ifdef _MSC_VER #include <__msvc_int128.hpp> // std::_Signed128 / std::_Unsigned128 - #include // widening-multiply / bit-scan intrinsics (detail/wide_ops.hpp) + #include // intrinsics wrapped by detail/config.hpp #endif // Tier B - macro-carrying headers. These stay textual even under `import std` @@ -26,7 +26,7 @@ module; // active. #include // every __cpp_lib_* feature test detail/config.hpp branches on #include // parity with the sibling Boost modules -#include // LDBL_MANT_DIG / LDBL_MAX_EXP (detail/floats.hpp has a hard #error without them) +#include // LDBL_MANT_DIG / LDBL_MAX_EXP (detail/config.hpp has a hard #error without them) #include // BITINT_MAXWIDTH (selects whether _BitInt support exists), CHAR_BIT #include // INTPTR_MAX / INT64_MAX / INT32_MAX (selects the limb width, hence the ABI) #include // stderr, which `import std` deliberately does not provide diff --git a/src/ntt_fp_dispatch.cpp b/src/ntt_fp_dispatch.cpp index 48ef9e15..4dee0695 100644 --- a/src/ntt_fp_dispatch.cpp +++ b/src/ntt_fp_dispatch.cpp @@ -14,10 +14,6 @@ #include -#if BEMAN_BIG_INT_NTT_FP_X86 && defined(_MSC_VER) - #include -#endif - BEMAN_BIG_INT_BEGIN_NAMESPACE namespace detail { @@ -33,19 +29,19 @@ namespace { #if BEMAN_BIG_INT_NTT_FP_X86 [[nodiscard]] bool cpu_has_avx2_fma() noexcept { - #if defined(__GNUC__) || defined(__clang__) - __builtin_cpu_init(); - return __builtin_cpu_supports("avx2") != 0 && __builtin_cpu_supports("fma") != 0; - #elif defined(_MSC_VER) + #if defined(BEMAN_BIG_INT_BUILTIN_CPU_INIT) && defined(BEMAN_BIG_INT_BUILTIN_CPU_SUPPORTS) + BEMAN_BIG_INT_BUILTIN_CPU_INIT(); + return BEMAN_BIG_INT_BUILTIN_CPU_SUPPORTS("avx2") != 0 && BEMAN_BIG_INT_BUILTIN_CPU_SUPPORTS("fma") != 0; + #elif defined(BEMAN_BIG_INT_INTRINSIC_CPUID) int regs[4]; - __cpuid(regs, 1); + BEMAN_BIG_INT_INTRINSIC_CPUID(regs, 1); const bool fma = (regs[2] & (1 << 12)) != 0; const bool osxsave = (regs[2] & (1 << 27)) != 0; - __cpuidex(regs, 7, 0); + BEMAN_BIG_INT_INTRINSIC_CPUIDEX(regs, 7, 0); const bool avx2 = (regs[1] & (1 << 5)) != 0; bool ymm = false; if (osxsave) { - const unsigned long long xcr0 = _xgetbv(0); + const unsigned long long xcr0 = BEMAN_BIG_INT_INTRINSIC_XGETBV(0); ymm = (xcr0 & 0x6u) == 0x6u; // XMM + YMM state enabled by the OS } return fma && avx2 && ymm; diff --git a/tests/beman/big_int/conversion.test.cpp b/tests/beman/big_int/conversion.test.cpp index 2da62d9f..9f1b5a25 100644 --- a/tests/beman/big_int/conversion.test.cpp +++ b/tests/beman/big_int/conversion.test.cpp @@ -246,7 +246,7 @@ TEST(Conversion, ToLongDoubleThreeLimbTieWithStickyBit) { // This is just to verify that the test itself is valid, // assuming that _BitInt -> long double conversions are implemented correctly. -#if LDBL_MANT_DIG == 64 && BEMAN_BIG_INT_BITINT_MAXWIDTH >= 192 +#if defined(BEMAN_BIG_INT_LONG_DOUBLE_X87_EXTENDED) && BEMAN_BIG_INT_BITINT_MAXWIDTH >= 192 BEMAN_BIG_INT_DIAGNOSTIC_PUSH() BEMAN_BIG_INT_DIAGNOSTIC_IGNORED_GCC("-Wfloat-equal") BEMAN_BIG_INT_DIAGNOSTIC_IGNORED_CLANG("-Wfloat-equal") From e9378ed7347cd8657ba01fbf77be64a020903532 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 8 Oct 2026 08:28:09 -0400 Subject: [PATCH 4/4] Add __VA_ARGS__ to zero parameter builtin macros --- include/beman/big_int/detail/config.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/beman/big_int/detail/config.hpp b/include/beman/big_int/detail/config.hpp index fac192bd..c41aa589 100644 --- a/include/beman/big_int/detail/config.hpp +++ b/include/beman/big_int/detail/config.hpp @@ -424,16 +424,16 @@ // ever needed there, so they are defined only when usable in constant evaluation. // Every supported compiler has these, so they are always defined. -#define BEMAN_BIG_INT_BUILTIN_FILE() __builtin_FILE() -#define BEMAN_BIG_INT_BUILTIN_LINE() __builtin_LINE() -#define BEMAN_BIG_INT_BUILTIN_FUNCTION() __builtin_FUNCTION() +#define BEMAN_BIG_INT_BUILTIN_FILE(...) __builtin_FILE(__VA_ARGS__) +#define BEMAN_BIG_INT_BUILTIN_LINE(...) __builtin_LINE(__VA_ARGS__) +#define BEMAN_BIG_INT_BUILTIN_FUNCTION(...) __builtin_FUNCTION(__VA_ARGS__) #ifdef BEMAN_BIG_INT_HAS_BUILTIN_IS_INTEGRAL #define BEMAN_BIG_INT_BUILTIN_IS_INTEGRAL(...) __is_integral(__VA_ARGS__) #endif #ifdef BEMAN_BIG_INT_HAS_BUILTIN_IS_CONSTANT_EVALUATED - #define BEMAN_BIG_INT_BUILTIN_IS_CONSTANT_EVALUATED() __builtin_is_constant_evaluated() + #define BEMAN_BIG_INT_BUILTIN_IS_CONSTANT_EVALUATED(...) __builtin_is_constant_evaluated(__VA_ARGS__) #endif #ifdef BEMAN_BIG_INT_HAS_BUILTIN_CONSTANT_P @@ -441,7 +441,7 @@ #endif #ifdef BEMAN_BIG_INT_HAS_BUILTIN_TRAP - #define BEMAN_BIG_INT_BUILTIN_TRAP() __builtin_trap() + #define BEMAN_BIG_INT_BUILTIN_TRAP(...) __builtin_trap(__VA_ARGS__) #endif #ifdef BEMAN_BIG_INT_HAS_BUILTIN_ADD_OVERFLOW @@ -521,7 +521,7 @@ #endif #ifdef BEMAN_BIG_INT_HAS_BUILTIN_CPU_INIT - #define BEMAN_BIG_INT_BUILTIN_CPU_INIT() __builtin_cpu_init() + #define BEMAN_BIG_INT_BUILTIN_CPU_INIT(...) __builtin_cpu_init(__VA_ARGS__) #endif #ifdef BEMAN_BIG_INT_HAS_BUILTIN_CPU_SUPPORTS