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..65b4701d 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, 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. +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/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..c41aa589 100644 --- a/include/beman/big_int/detail/config.hpp +++ b/include/beman/big_int/detail/config.hpp @@ -4,6 +4,358 @@ #ifndef BEMAN_BIG_INT_CONFIG_HPP #define BEMAN_BIG_INT_CONFIG_HPP +// 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_* +#endif + +// A CMake build generates config_generated.hpp at configure time +#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_*, 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 + + #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 + + // 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 + // 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 + + // 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) + #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 + + // 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) + #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 + // Module support ============================================================== // `BEMAN_BIG_INT_BUILD_MODULE` is defined by module/big_int.cppm and propagated @@ -62,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(__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(__VA_ARGS__) +#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(__VA_ARGS__) +#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(__VA_ARGS__) +#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 @@ -137,32 +621,16 @@ #define BEMAN_BIG_INT_DIAGNOSTIC_IGNORED_MSVC(...) #endif -// _BitInt detection =========================================================== +// _BitInt aliases ============================================================= -// 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 - - // 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 +638,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 +649,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 +665,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,41 +747,21 @@ 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 -// 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) - #if defined(__BMI2__) && defined(__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(__AVX512IFMA__) && defined(__AVX512VL__) && defined(__AVX512BW__) && defined(__AVX512VBMI__) && \ - defined(__BMI2__) && defined(__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 @@ -376,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. @@ -573,12 +994,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 +1014,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 ================================================================= @@ -642,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 @@ -670,19 +1090,19 @@ 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. // 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 @@ -707,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 @@ -745,16 +1165,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/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/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..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 @@ -20,11 +19,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 +65,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 +78,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 +91,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; @@ -112,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; @@ -127,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; @@ -138,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,10 +140,10 @@ 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)); +#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,10 +162,10 @@ 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)); +#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,22 +181,22 @@ 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) { +#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,22 +232,22 @@ 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) { +#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,22 +266,22 @@ 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) { +#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/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..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,24 +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) - if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { - if constexpr (std::is_signed_v) { - return __mulh(x, y); - } else { - return __umulh(x, y); - } - } - #elif defined(_M_AMD64) + #ifdef BEMAN_BIG_INT_INTRINSIC_UMULH if BEMAN_BIG_INT_IS_NOT_CONSTEVAL { if constexpr (std::is_signed_v) { - __int64 result; - void(_mul128(x, y, &result)); - return result; + return BEMAN_BIG_INT_INTRINSIC_MULH(x, y); } else { - unsigned __int64 result; - void(_umul128(x, y, &result)); - return result; + return BEMAN_BIG_INT_INTRINSIC_UMULH(x, y); } } #endif @@ -252,17 +234,17 @@ template const auto product = static_cast>(x) * static_cast>(y); return wide::from_int(product); } else { - #if defined(_M_AMD64) + #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}; } } @@ -288,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; @@ -305,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; @@ -336,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) { @@ -350,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) { @@ -367,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 @@ -398,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 @@ -407,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); @@ -423,7 +405,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_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. @@ -431,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 _M_AMD64 + #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 @@ -478,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 @@ -487,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); @@ -503,31 +489,35 @@ 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_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 _M_AMD64 + #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 // _M_AMD64 + #endif // BEMAN_BIG_INT_INTRINSIC_SUBBORROW_U64 else { return borrowing_sub_portable(x, y, borrow); } @@ -595,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(__x86_64__) || defined(__i386__)) + #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 @@ -605,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(_WIN32) && defined(_M_X64) + #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/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/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/module/big_int.cppm b/module/big_int.cppm index 77486a78..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,16 +17,16 @@ 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` // 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 // 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 @@ -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/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/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/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/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/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") 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/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" 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};