diff --git a/config/Jamfile b/config/Jamfile index d4f562fc..1d2527f2 100644 --- a/config/Jamfile +++ b/config/Jamfile @@ -13,6 +13,8 @@ # requirement is inherited by the test subprojects too, and a relative # reference would be resolved from each of their directories. +import testing ; + project /boost/openmethod/config ; obj has_reflection : has_reflection.cpp : -freflection ; @@ -22,5 +24,11 @@ explicit has_reflection ; # needs. Probing beats naming an architecture - a x86 conditional # does not match every toolset spelling, and a compiler that rejects -mbmi2 # outright would take the directory down with it. -obj has_bmi2 : has_bmi2.cpp : -mbmi2 ../include ; +# +# `run`, not `obj`: the compiler accepts -mbmi2 - and emits `pext` - whenever +# the toolchain supports the extension, whether or not the machine running the +# build implements it in hardware. A handful of CI runners pair a BMI2-capable +# compiler with pre-Haswell (or feature-masked) silicon, where the instruction +# traps with SIGILL; a compile-only check cannot see that; see has_bmi2.cpp. +run has_bmi2.cpp : : : -mbmi2 ../include ; explicit has_bmi2 ; diff --git a/config/has_bmi2.cpp b/config/has_bmi2.cpp index 65c0f87a..d67f35db 100644 --- a/config/has_bmi2.cpp +++ b/config/has_bmi2.cpp @@ -11,6 +11,19 @@ // what the test suite needs to know is whether that header will let the policy // be used, which is a slightly narrower question than whether some spelling of // pext compiles. +// +// The probe runs, it does not just compile. GCC and clang accept -mbmi2, and +// therefore emit `pext`, whenever the *toolchain* supports the extension - +// that says nothing about whether the machine actually running the build +// implements it in hardware. A CI runner can advertise a BMI2-capable +// compiler while executing on pre-Haswell (or virtualized/feature-masked) +// silicon, where the instruction traps with SIGILL. ../Jamfile turns this +// into a `run` target for exactly that reason: UPDATE_NOW executes the +// program, and a trap fails the check the same way a compile error would. +// +// `argc` keeps the operands from being folded to a compile-time constant, so +// optimization cannot turn the one `pext` this program executes into a +// no-op. #include @@ -18,6 +31,9 @@ static_assert(BOOST_OPENMETHOD_HAS_PEXT); -auto probe(std::uint64_t value, std::uint64_t mask) -> std::uint64_t { - return boost::openmethod::detail::pext64(value, mask); +auto main(int argc, char**) -> int { + auto value = std::uint64_t(argc) | (std::uint64_t(1) << 63); + auto mask = std::uint64_t(argc) * 0x5555555555555555ull; + + return boost::openmethod::detail::pext64(value, mask) == 0xdeadbeef ? 1 : 0; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e493cb5e..0d59f772 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -87,6 +87,28 @@ set(BOOST_OPENMETHOD_TEST_PCH_HEADERS set(boost_openmethod_pch_owner "") +# minimal_cover_hash dispatches with BMI2's pext. A compiler accepts the +# enabling flag - and therefore emits `pext` - whenever the toolchain supports +# the extension, whatever the machine actually running the build implements in +# hardware; on pre-Haswell (or feature-masked) x86-64 the instruction traps +# with SIGILL. check_cxx_source_runs actually executes the probe, once, for +# exactly that reason - a plain try_compile cannot see the difference. See +# config/has_bmi2.cpp, and config/Jamfile for the b2 equivalent and the CI +# failure that motivated both. +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") + set(BOOST_OPENMETHOD_BMI2_FLAG /arch:AVX2) +else() + set(BOOST_OPENMETHOD_BMI2_FLAG -mbmi2) +endif() + +include(CheckCXXSourceRuns) +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/../config/has_bmi2.cpp" BOOST_OPENMETHOD_HAS_BMI2_SOURCE) +set(CMAKE_REQUIRED_FLAGS ${BOOST_OPENMETHOD_BMI2_FLAG}) +set(CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/../include") +check_cxx_source_runs("${BOOST_OPENMETHOD_HAS_BMI2_SOURCE}" BOOST_OPENMETHOD_HAS_BMI2) +unset(CMAKE_REQUIRED_FLAGS) +unset(CMAKE_REQUIRED_INCLUDES) + file(GLOB test_cpp_files "test_*.cpp") foreach(test_cpp ${test_cpp_files}) @@ -107,21 +129,15 @@ foreach(test_cpp ${test_cpp_files}) # minimal_cover_hash dispatches with BMI2's pext, and because `hash` is # inlined into every call the instruction has to be enabled for the whole - # translation unit. Only the tests that name the policy need it, and only on - # x86-64 - the 64-bit pext intrinsic does not exist in 32-bit mode: - # elsewhere BOOST_OPENMETHOD_HAS_PEXT is 0 and the policy drops out of both. + # translation unit. Only the tests that name the policy need it, and only + # where BOOST_OPENMETHOD_HAS_BMI2 (computed above) found the instruction + # both compiles *and runs* - elsewhere BOOST_OPENMETHOD_HAS_PEXT is 0 and + # the policy drops out of both. set(test_needs_bmi2 FALSE) - if (test MATCHES "minimal_cover_hash|hash_policies" AND - CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$") + if (test MATCHES "minimal_cover_hash|hash_policies" AND BOOST_OPENMETHOD_HAS_BMI2) set(test_needs_bmi2 TRUE) - - if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR - CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") - target_compile_options(${test_target} PRIVATE /arch:AVX2) - else() - target_compile_options(${test_target} PRIVATE -mbmi2) - endif() + target_compile_options(${test_target} PRIVATE ${BOOST_OPENMETHOD_BMI2_FLAG}) endif() file(READ ${test_cpp} test_cpp_contents) diff --git a/test/test_hash_policies.cpp b/test/test_hash_policies.cpp index 5e8a8a03..215509cc 100644 --- a/test/test_hash_policies.cpp +++ b/test/test_hash_policies.cpp @@ -120,6 +120,7 @@ auto base_of(std::size_t module, std::size_t spread) -> std::uintptr_t { auto ids_over(std::size_t n, std::size_t modules, std::size_t spread) -> std::vector { std::vector at; + at.reserve(modules); for (std::size_t module = 0; module != modules; ++module) { at.push_back(base_of(module, spread));