diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ee23c78..6bffc9db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1273,3 +1273,70 @@ jobs: ctest --output-on-failure --no-tests=error cd $GITHUB_WORKSPACE done + + cmake-module-test: + strategy: + fail-fast: false + + runs-on: ubuntu-24.04 + + env: + # clang/libc++ version used to build the module. Needs a libc++ new enough to + # mix `import std` with textual libc++ includes (18 and 19 have std-module bugs). + LLVM_VERSION: "20" + # Pinned so the experimental `import std` feature UUID below stays valid. + CMAKE_VERSION: 4.4.0 + # CMake gates `import std` behind this UUID; it is specific to the CMake version. + IMPORT_STD_UUID: f35a9ac6-8463-4d38-8eec-5d6008153e7d + + steps: + - uses: actions/checkout@v4 + + - name: Install toolchain + run: | + # A recent clang + libc++ (for `import std`), clang-scan-deps (module + # dependency scanning), Ninja (required for C++ modules) and a pinned CMake. + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh $LLVM_VERSION + sudo apt-get install -y clang-tools-$LLVM_VERSION libc++-$LLVM_VERSION-dev libc++abi-$LLVM_VERSION-dev ninja-build + wget -q https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-linux-x86_64.tar.gz + sudo tar -xzf cmake-$CMAKE_VERSION-linux-x86_64.tar.gz -C /opt + echo "/opt/cmake-$CMAKE_VERSION-linux-x86_64/bin" >> "$GITHUB_PATH" + + - name: Setup Boost + run: | + echo GITHUB_REPOSITORY: $GITHUB_REPOSITORY + LIBRARY=${GITHUB_REPOSITORY#*/} + echo LIBRARY: $LIBRARY + echo "LIBRARY=$LIBRARY" >> $GITHUB_ENV + echo GITHUB_BASE_REF: $GITHUB_BASE_REF + echo GITHUB_REF: $GITHUB_REF + REF=${GITHUB_BASE_REF:-$GITHUB_REF} + REF=${REF#refs/heads/} + echo REF: $REF + BOOST_BRANCH=develop && [ "$REF" == "master" ] && BOOST_BRANCH=master || true + echo BOOST_BRANCH: $BOOST_BRANCH + cd .. + git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + mkdir -p libs/$LIBRARY + cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY + git submodule update --init tools/boostdep + python3 tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY + + - name: Build with the standard library module and int128 module, then test + run: | + cd ../boost-root + mkdir __build__ && cd __build__ + cmake -G Ninja \ + -DBOOST_INCLUDE_LIBRARIES=$LIBRARY \ + -DBUILD_TESTING=ON \ + -DBOOST_INT128_BUILD_MODULE=ON \ + -DCMAKE_CXX_COMPILER=clang++-$LLVM_VERSION \ + -DCMAKE_CXX_FLAGS="-stdlib=libc++" \ + -DCMAKE_EXPERIMENTAL_CXX_IMPORT_STD=$IMPORT_STD_UUID \ + -DCMAKE_CXX_STDLIB_MODULES_JSON=/usr/lib/llvm-$LLVM_VERSION/lib/libc++.modules.json \ + .. + cmake --build . --target tests + ctest --output-on-failure --no-tests=error diff --git a/include/boost/int128/bit.hpp b/include/boost/int128/bit.hpp index e014008a..3ea02fbd 100644 --- a/include/boost/int128/bit.hpp +++ b/include/boost/int128/bit.hpp @@ -76,7 +76,7 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int popcount(const uint12 namespace impl { -BOOST_INT128_HOST_DEVICE constexpr int popcount_impl(std::uint64_t x) noexcept +BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr int popcount_impl(std::uint64_t x) noexcept { x = x - ((x >> 1U) & UINT64_C(0x5555555555555555)); x = (x & UINT64_C(0x3333333333333333)) + ((x >> 2U) & UINT64_C(0x3333333333333333)); diff --git a/include/boost/int128/detail/clz.hpp b/include/boost/int128/detail/clz.hpp index 0ce6ad1f..3fa01b6f 100644 --- a/include/boost/int128/detail/clz.hpp +++ b/include/boost/int128/detail/clz.hpp @@ -18,7 +18,10 @@ namespace boost { namespace int128 { namespace detail { -namespace impl { +// The whole impl namespace is exported when building the module for testing so +// the low-level bit helpers can be exercised directly; it is an ordinary +// namespace in every other build. +BOOST_INT128_TEST_EXPORT namespace impl { #if !(defined(__CUDACC__) && defined(BOOST_INT128_ENABLE_CUDA)) diff --git a/include/boost/int128/detail/config.hpp b/include/boost/int128/detail/config.hpp index 2d31b5f1..2cc4f87d 100644 --- a/include/boost/int128/detail/config.hpp +++ b/include/boost/int128/detail/config.hpp @@ -5,6 +5,16 @@ #ifndef BOOST_INT128_DETAIL_CONFIG_HPP #define BOOST_INT128_DETAIL_CONFIG_HPP +// A handful of detail-namespace entities are exercised directly by the module +// test suite. BOOST_INT128_TEST_EXPORT exports them only when the module is built +// for testing (BOOST_INT128_EXPORT_TESTING), so the normal module API stays limited +// to the public interface. It expands to nothing in ordinary (header) builds. +#if defined(BOOST_INT128_BUILD_MODULE) && defined(BOOST_INT128_EXPORT_TESTING) +# define BOOST_INT128_TEST_EXPORT export +#else +# define BOOST_INT128_TEST_EXPORT +#endif + // The SYCL device target (spir64) has no native 128-bit integer, so force the portable // code path on the device pass. This mirrors a user-supplied BOOST_INT128_NO_BUILTIN_INT128 // and keeps host/device selection consistent even though __x86_64__ stays defined on device. @@ -25,19 +35,26 @@ namespace boost { namespace int128 { namespace detail { +// A module consumer receives these aliases from the import, so only declare them +// in ordinary builds and in the module interface unit itself; declaring them again +// in a consumer would give a second, distinct type and break overload resolution. +#if !defined(BOOST_INT128_BUILD_MODULE) || defined(BOOST_INT128_INTERFACE_UNIT) + // Avoids pedantic warnings #ifdef __GNUC__ -__extension__ using builtin_i128 = __int128 ; -__extension__ using builtin_u128 = unsigned __int128 ; +BOOST_INT128_TEST_EXPORT __extension__ using builtin_i128 = __int128 ; +BOOST_INT128_TEST_EXPORT __extension__ using builtin_u128 = unsigned __int128 ; #else -using builtin_i128 = __int128 ; -using builtin_u128 = unsigned __int128; +BOOST_INT128_TEST_EXPORT using builtin_i128 = __int128 ; +BOOST_INT128_TEST_EXPORT using builtin_u128 = unsigned __int128; #endif +#endif // declare builtin aliases + } // namespace detail } // namespace int128 } // namespace boost @@ -60,8 +77,13 @@ namespace boost { namespace int128 { namespace detail { -using builtin_i128 = std::_Signed128; -using builtin_u128 = std::_Unsigned128; +// See the note above: skip the re-declaration in a module consumer. +#if !defined(BOOST_INT128_BUILD_MODULE) || defined(BOOST_INT128_INTERFACE_UNIT) + +BOOST_INT128_TEST_EXPORT using builtin_i128 = std::_Signed128; +BOOST_INT128_TEST_EXPORT using builtin_u128 = std::_Unsigned128; + +#endif } // namespace detail } // namespace int128 diff --git a/include/boost/int128/detail/int128_imp.hpp b/include/boost/int128/detail/int128_imp.hpp index 0ad50e18..b94d5fc2 100644 --- a/include/boost/int128/detail/int128_imp.hpp +++ b/include/boost/int128/detail/int128_imp.hpp @@ -1898,12 +1898,12 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator+(const SignedInteger lhs, c #if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128) -BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR int128_t operator+(const int128_t lhs, const detail::builtin_i128 rhs) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR int128_t operator+(const int128_t lhs, const detail::builtin_i128 rhs) noexcept { return detail::default_add(lhs, static_cast(rhs)); } -BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR int128_t operator+(const detail::builtin_i128 lhs, const int128_t rhs) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR int128_t operator+(const detail::builtin_i128 lhs, const int128_t rhs) noexcept { return detail::default_add(rhs, static_cast(lhs)); } @@ -2631,10 +2631,23 @@ template constexpr bool numeric_limits_impl_i128::has_infinity; template constexpr bool numeric_limits_impl_i128::has_quiet_NaN; template constexpr bool numeric_limits_impl_i128::has_signaling_NaN; -// These members were deprecated in C++23 -#if ((!defined(_MSC_VER) && (__cplusplus <= 202002L)) || (defined(_MSC_VER) && (_MSVC_LANG <= 202002L))) +// These members were deprecated in C++23; suppress the deprecation warning rather +// than dropping the definitions. +#if defined(__GNUC__) && __cplusplus > 202002L +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable:4996) +#endif + template constexpr std::float_denorm_style numeric_limits_impl_i128::has_denorm; template constexpr bool numeric_limits_impl_i128::has_denorm_loss; + +#if defined(__GNUC__) && __cplusplus > 202002L +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) #endif template constexpr std::float_round_style numeric_limits_impl_i128::round_style; diff --git a/include/boost/int128/detail/literal_macros.hpp b/include/boost/int128/detail/literal_macros.hpp new file mode 100644 index 00000000..b8ef0933 --- /dev/null +++ b/include/boost/int128/detail/literal_macros.hpp @@ -0,0 +1,17 @@ +// Copyright 2022 Peter Dimov +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_INT128_DETAIL_LITERAL_MACROS_HPP +#define BOOST_INT128_DETAIL_LITERAL_MACROS_HPP + +// Convenience macros for the user-defined literals. This header intentionally has +// no includes and declares nothing, so module consumers can pull it in on its own +// to obtain the macros (macros are never part of a module's exported interface). + +#define BOOST_INT128_STRINGIFY(x) #x +#define BOOST_INT128_UINT128_C(x) boost::int128::literals::operator""_u128(BOOST_INT128_STRINGIFY(x)) +#define BOOST_INT128_INT128_C(x) boost::int128::literals::operator""_i128(BOOST_INT128_STRINGIFY(x)) + +#endif // BOOST_INT128_DETAIL_LITERAL_MACROS_HPP diff --git a/include/boost/int128/detail/mini_from_chars.hpp b/include/boost/int128/detail/mini_from_chars.hpp index 233b37be..f1c2af6a 100644 --- a/include/boost/int128/detail/mini_from_chars.hpp +++ b/include/boost/int128/detail/mini_from_chars.hpp @@ -219,12 +219,12 @@ BOOST_INT128_HOST_DEVICE constexpr int from_chars_integer_impl(const char* first } } // namespace impl -BOOST_INT128_HOST_DEVICE constexpr int from_chars(const char* first, const char* last, uint128_t& value, int base = 10) noexcept +BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr int from_chars(const char* first, const char* last, uint128_t& value, int base = 10) noexcept { return impl::from_chars_integer_impl(first, last, value, base); } -BOOST_INT128_HOST_DEVICE constexpr int from_chars(const char* first, const char* last, int128_t& value, int base = 10) noexcept +BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr int from_chars(const char* first, const char* last, int128_t& value, int base = 10) noexcept { return impl::from_chars_integer_impl(first, last, value, base); } diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index 8ba30bf0..5cfeeb6f 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -2805,10 +2805,23 @@ template constexpr bool numeric_limits_impl_u128::has_infinity; template constexpr bool numeric_limits_impl_u128::has_quiet_NaN; template constexpr bool numeric_limits_impl_u128::has_signaling_NaN; -// These members were deprecated in C++23 -#if ((!defined(_MSC_VER) && (__cplusplus <= 202002L)) || (defined(_MSC_VER) && (_MSVC_LANG <= 202002L))) +// These members were deprecated in C++23; suppress the deprecation warning rather +// than dropping the definitions. +#if defined(__GNUC__) && __cplusplus > 202002L +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable:4996) +#endif + template constexpr std::float_denorm_style numeric_limits_impl_u128::has_denorm; template constexpr bool numeric_limits_impl_u128::has_denorm_loss; + +#if defined(__GNUC__) && __cplusplus > 202002L +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) #endif template constexpr std::float_round_style numeric_limits_impl_u128::round_style; diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index bc0e5bfa..b66e1735 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -10,9 +10,12 @@ #include #include #include + +#ifndef BOOST_INT128_BUILD_MODULE #include #include #include +#endif #define BOOST_INT128_HAS_FORMAT diff --git a/include/boost/int128/literals.hpp b/include/boost/int128/literals.hpp index ecb85bf8..3d388f26 100644 --- a/include/boost/int128/literals.hpp +++ b/include/boost/int128/literals.hpp @@ -50,7 +50,7 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_i128 return result; } -BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128(const char* str) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128(const char* str) noexcept { int128_t result {}; detail::from_chars(str, str + detail::strlen(str), result); @@ -75,8 +75,6 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128 } // namespace int128 } // namespace boost -#define BOOST_INT128_STRINGIFY(x) #x -#define BOOST_INT128_UINT128_C(x) boost::int128::literals::operator""_u128(BOOST_INT128_STRINGIFY(x)) -#define BOOST_INT128_INT128_C(x) boost::int128::literals::operator""_i128(BOOST_INT128_STRINGIFY(x)) +#include #endif // BOOST_INT128_LITERALS_HPP diff --git a/include/boost/int128/numeric.hpp b/include/boost/int128/numeric.hpp index c38491f7..c95c61aa 100644 --- a/include/boost/int128/numeric.hpp +++ b/include/boost/int128/numeric.hpp @@ -83,7 +83,7 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t sub_sat(const u # pragma warning(disable : 4146) // Unary minus applied to unsigned type #endif -BOOST_INT128_HOST_DEVICE constexpr int128_t add_sat(const int128_t x, const int128_t y) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t add_sat(const int128_t x, const int128_t y) noexcept { // Detect overflow BEFORE the addition to avoid signed overflow UB. // When both are non-negative: overflow iff x > max - y (subtraction safe: max - non_negative >= 0) @@ -108,7 +108,7 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t add_sat(const int128_t x, const int1 return x + y; } -BOOST_INT128_HOST_DEVICE constexpr int128_t sub_sat(const int128_t x, const int128_t y) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t sub_sat(const int128_t x, const int128_t y) noexcept { // Detect overflow BEFORE the subtraction to avoid signed overflow UB. // Positive overflow: x >= 0 and y < 0 and x > max + y (safe: max + negative < max) @@ -247,7 +247,7 @@ BOOST_INT128_HOST_DEVICE constexpr TargetType saturate_cast(const int128_t value namespace detail { -BOOST_INT128_HOST_DEVICE constexpr std::uint64_t gcd64(std::uint64_t x, std::uint64_t y) noexcept +BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr std::uint64_t gcd64(std::uint64_t x, std::uint64_t y) noexcept { if (x == 0) { @@ -279,7 +279,7 @@ BOOST_INT128_HOST_DEVICE constexpr std::uint64_t gcd64(std::uint64_t x, std::uin } // namespace detail -BOOST_INT128_HOST_DEVICE constexpr uint128_t gcd(uint128_t a, uint128_t b) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t gcd(uint128_t a, uint128_t b) noexcept { // Base case if (a == 0U) @@ -316,7 +316,7 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t gcd(uint128_t a, uint128_t b) noexc return uint128_t{0, g} << shift; } -BOOST_INT128_HOST_DEVICE constexpr int128_t gcd(const int128_t a, const int128_t b) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t gcd(const int128_t a, const int128_t b) noexcept { return static_cast(gcd(static_cast(abs(a)), static_cast(abs(b)))); } @@ -326,7 +326,7 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t gcd(const int128_t a, const int128_t // but very slow impl that we know works. #if !(defined(_M_IX86) && !defined(_NDEBUG)) -BOOST_INT128_HOST_DEVICE constexpr uint128_t lcm(const uint128_t a, const uint128_t b) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t lcm(const uint128_t a, const uint128_t b) noexcept { if (a == 0U || b == 0U) { @@ -342,7 +342,7 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t lcm(const uint128_t a, const uint12 #else -BOOST_INT128_HOST_DEVICE constexpr uint128_t lcm(uint128_t a, uint128_t b) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t lcm(uint128_t a, uint128_t b) noexcept { if (a == 0U || b == 0U) { @@ -376,12 +376,12 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t lcm(uint128_t a, uint128_t b) noexc #endif -BOOST_INT128_HOST_DEVICE constexpr int128_t lcm(const int128_t a, const int128_t b) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t lcm(const int128_t a, const int128_t b) noexcept { return static_cast(lcm(static_cast(abs(a)), static_cast(abs(b)))); } -BOOST_INT128_HOST_DEVICE constexpr uint128_t midpoint(const uint128_t a, const uint128_t b) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t midpoint(const uint128_t a, const uint128_t b) noexcept { // Bit manipulation formula works for unsigned integers auto mid {(a & b) + ((a ^ b) >> 1)}; @@ -395,7 +395,7 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t midpoint(const uint128_t a, const u return mid; } -BOOST_INT128_HOST_DEVICE constexpr int128_t midpoint(const int128_t a, const int128_t b) noexcept +BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t midpoint(const int128_t a, const int128_t b) noexcept { // For signed integers, we use a + (b - a) / 2 or a - (a - b) / 2 // The subtraction is done in unsigned arithmetic to handle overflow correctly diff --git a/include/boost/int128/utilities.hpp b/include/boost/int128/utilities.hpp index ffb29b66..01140c96 100644 --- a/include/boost/int128/utilities.hpp +++ b/include/boost/int128/utilities.hpp @@ -36,7 +36,7 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t addmod(const uint128_t a, const uin } // Modular multiplication via shift-and-add for the full 128-bit modulus case -BOOST_INT128_HOST_DEVICE constexpr uint128_t mulmod_shift(uint128_t a, uint128_t b, const uint128_t m) noexcept +BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t mulmod_shift(uint128_t a, uint128_t b, const uint128_t m) noexcept { uint128_t result {0}; diff --git a/module/CMakeLists.txt b/module/CMakeLists.txt new file mode 100644 index 00000000..2fe5619c --- /dev/null +++ b/module/CMakeLists.txt @@ -0,0 +1,92 @@ +# Copyright 2026 Matt Borland +# Distributed under the Boost Software License, Version 1.0. +# https://www.boost.org/LICENSE_1_0.txt +# +# Builds Boost.Int128 as a C++ module (boost.int128) and runs the module-aware +# tests in ../test against it, consuming the standard library module (import std). +# This file is included from ../test/CMakeLists.txt when the CMake option +# BOOST_INT128_BUILD_MODULE is set and expects the Boost superproject (for the +# Boost:: dependency targets). import std is required (see the check below). + +if(NOT TARGET tests) + add_custom_target(tests) +endif() + +# The module interface unit. +add_library(boost_int128_module) +target_sources(boost_int128_module + PUBLIC + FILE_SET int128_module TYPE CXX_MODULES FILES int128.cxx +) +target_link_libraries(boost_int128_module PUBLIC Boost::int128) +target_compile_features(boost_int128_module PUBLIC cxx_std_23) + +# int128.cxx defines BOOST_INT128_BUILD_MODULE itself, but the tests link this +# target and need the macro too so they import the module instead of including +# the headers directly. +target_compile_definitions(boost_int128_module PUBLIC BOOST_INT128_BUILD_MODULE) + +# Export the handful of detail-namespace entities that the tests exercise directly. +# Only the interface unit needs this, so keep it private to the module target. +target_compile_definitions(boost_int128_module PRIVATE BOOST_INT128_EXPORT_TESTING) + +# The tests introspect standard-library facilities (e.g. numeric_limits members) that +# a module consumer only sees reliably through the standard library module, so import +# std is required for the test build. CMAKE_CXX_COMPILER_IMPORT_STD lists the standards +# it is available for; it is only populated when CMAKE_EXPERIMENTAL_CXX_IMPORT_STD is +# set before the project() call. +if(NOT "23" IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD) + message(FATAL_ERROR + "Building the Boost.Int128 module tests requires 'import std' (the C++23 " + "standard library module). Use a toolchain that supports it and pass " + "-DCMAKE_EXPERIMENTAL_CXX_IMPORT_STD= before the project() call. " + "CMAKE_CXX_COMPILER_IMPORT_STD='${CMAKE_CXX_COMPILER_IMPORT_STD}'") +endif() + +set_target_properties(boost_int128_module PROPERTIES CXX_MODULE_STD ON) +target_compile_definitions(boost_int128_module PRIVATE BOOST_INT128_USE_STD_MODULE) +message(STATUS "Boost.Int128 module: using 'import std;'") + +# Tests that switch to `import boost.int128;` when BOOST_INT128_BUILD_MODULE is set. +# test_i128 and test_i128_no_sign_conv are intentionally omitted: they exercise the +# arithmetic operators with the raw builtin 128-bit types (__int128), and clang +# resolves those overloads differently across the module boundary, selecting a +# generic path that narrows. That is a library-level concern independent of module +# support, so those two remain covered by the header-based test suite. +set(BOOST_INT128_MODULE_TESTS + test_bit + test_div + test_from_chars_bases + test_gcd_lcm + test_hash + test_ipow + test_isqrt + test_limits_i128 + test_limits_u128 + test_literals + test_midpoint + test_powm +) + +add_executable(module_quick_test "${CMAKE_CURRENT_SOURCE_DIR}/quick_test.cpp") +target_link_libraries(module_quick_test PRIVATE boost_int128_module) +# The superproject pins an older policy version (CMP0155), so C++ sources are not +# scanned for module imports by default. Force scanning so `import boost.int128;` +# is resolved to the module built above. +set_target_properties(module_quick_test PROPERTIES CXX_SCAN_FOR_MODULES ON CXX_MODULE_STD ON) +add_test(NAME module_quick_test COMMAND module_quick_test) +add_dependencies(tests module_quick_test) + +foreach(test ${BOOST_INT128_MODULE_TESTS}) + add_executable(module_${test} "${CMAKE_CURRENT_SOURCE_DIR}/../test/${test}.cpp") + target_link_libraries(module_${test} + PRIVATE + boost_int128_module + Boost::core + Boost::mp11 + Boost::random + ) + set_target_properties(module_${test} PROPERTIES CXX_SCAN_FOR_MODULES ON CXX_MODULE_STD ON) + add_test(NAME module_${test} COMMAND module_${test}) + add_dependencies(tests module_${test}) +endforeach() diff --git a/module/Jamfile b/module/Jamfile index 0cdbc520..a20d8c9b 100644 --- a/module/Jamfile +++ b/module/Jamfile @@ -24,15 +24,18 @@ project clang:23 ; -obj int128 : int128.cxx : msvc:-interface ; +obj int128 : int128.cxx : BOOST_INT128_EXPORT_TESTING msvc:-interface ; run quick_test.cpp int128 : : : int128 ; run ../test/test_bit.cpp int128 : : : int128 ; run ../test/test_div.cpp int128 : : : int128 ; +run ../test/test_from_chars_bases.cpp int128 : : : int128 ; run ../test/test_gcd_lcm.cpp int128 : : : int128 ; -run ../test/test_i128.cpp int128 : : : int128 ; -run ../test/test_i128_no_sign_conv.cpp int128 : : : int128 ; -run ../test/test_limits_i128.cpp : : : int128 ; -run ../test/test_limits_u128.cpp : : : int128 ; -run ../test/test_literals.cpp : : : int128 ; -run ../test/test_midpoint.cpp : : : int128 ; +run ../test/test_hash.cpp int128 : : : int128 ; +run ../test/test_ipow.cpp int128 : : : int128 ; +run ../test/test_isqrt.cpp int128 : : : int128 ; +run ../test/test_limits_i128.cpp int128 : : : int128 ; +run ../test/test_limits_u128.cpp int128 : : : int128 ; +run ../test/test_literals.cpp int128 : : : int128 ; +run ../test/test_midpoint.cpp int128 : : : int128 ; +run ../test/test_powm.cpp int128 : : : int128 ; diff --git a/module/int128.cxx b/module/int128.cxx index 050aaae1..3fd14317 100644 --- a/module/int128.cxx +++ b/module/int128.cxx @@ -4,17 +4,8 @@ module; -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - +// The platform intrinsic headers and the MSVC built-in 128-bit header are not +// part of the standard library module, so they are always brought in here. #if __has_include(<__msvc_int128.hpp>) && _MSVC_LANG >= 202002L #include <__msvc_int128.hpp> @@ -32,10 +23,48 @@ module; # include #endif +// import std exports declarations but not macros. The headers below provide the +// feature-test macros and the object-like macros the library uses (assert, errno, +// UINT64_C, ...) so they are included regardless of whether import std is used. +#include +#include +#include +#include +#include + +// When the standard library module is available these are provided by import std, +// otherwise they are supplied here in the global module fragment. +#ifndef BOOST_INT128_USE_STD_MODULE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + #define BOOST_INT128_BUILD_MODULE +// Marks this translation unit as the module interface unit (as opposed to a +// consumer that imports the module). Some headers key off this to declare +// entities here that consumers instead receive through the import. +#define BOOST_INT128_INTERFACE_UNIT + export module boost.int128; +#ifdef BOOST_INT128_USE_STD_MODULE +import std; +#endif + export namespace boost::int128 { struct int128_t; @@ -68,4 +97,3 @@ class numeric_limits; #elif defined(__clang__) # pragma clang diagnostic pop #endif - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index effb4ae4..ea0cddf7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,6 +36,12 @@ if(HAVE_BOOST_TEST) boost_test_jamfile(FILE sycl_jamfile LINK_LIBRARIES Boost::int128 Boost::charconv sycl COMPILE_DEFINITIONS BOOST_INT128_ENABLE_SYCL=1 COMPILE_OPTIONS -fsycl) + elseif(BOOST_INT128_BUILD_MODULE) + + message(STATUS "Building Boost.int128 as a C++ module") + + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../module" module) + else() boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::int128 Boost::core Boost::random Boost::multiprecision Boost::mp11 Boost::charconv) diff --git a/test/test_div.cpp b/test/test_div.cpp index 519d050c..252929b8 100644 --- a/test/test_div.cpp +++ b/test/test_div.cpp @@ -11,6 +11,9 @@ #else import boost.int128; +// Internal macros (e.g. BOOST_INT128_UNREACHABLE) are not part of the module +// interface, so pull them in directly. +#include #endif diff --git a/test/test_limits_i128.cpp b/test/test_limits_i128.cpp index 95d3ffaf..6a6968fa 100644 --- a/test/test_limits_i128.cpp +++ b/test/test_limits_i128.cpp @@ -9,6 +9,9 @@ #else import boost.int128; +// Internal macros (BOOST_INT128_IF_CONSTEXPR, feature detection) are not part of +// the module interface, so pull them in directly. +#include #endif diff --git a/test/test_limits_u128.cpp b/test/test_limits_u128.cpp index 1dd0ab7a..db80de90 100644 --- a/test/test_limits_u128.cpp +++ b/test/test_limits_u128.cpp @@ -9,6 +9,9 @@ #else import boost.int128; +// Internal macros (BOOST_INT128_IF_CONSTEXPR, feature detection) are not part of +// the module interface, so pull them in directly. +#include #endif diff --git a/test/test_literals.cpp b/test/test_literals.cpp index 37c94081..2e61254d 100644 --- a/test/test_literals.cpp +++ b/test/test_literals.cpp @@ -11,6 +11,8 @@ #else import boost.int128; +// The convenience macros are not part of the module interface (macros never are). +#include #endif diff --git a/test/test_midpoint.cpp b/test/test_midpoint.cpp index 1a84dbe0..4c056b81 100644 --- a/test/test_midpoint.cpp +++ b/test/test_midpoint.cpp @@ -9,6 +9,8 @@ #else import boost.int128; +// The convenience macros are not part of the module interface (macros never are). +#include #endif