From 033782df3a4e9ab2a8ec709db9c9a25edefff691 Mon Sep 17 00:00:00 2001 From: AntoinePrv Date: Mon, 23 Mar 2026 14:45:29 +0100 Subject: [PATCH 1/7] Add highest func parameter --- .../xsimd/config/xsimd_cpu_features_x86.hpp | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/include/xsimd/config/xsimd_cpu_features_x86.hpp b/include/xsimd/config/xsimd_cpu_features_x86.hpp index 4ed361f46..a0aa6afe7 100644 --- a/include/xsimd/config/xsimd_cpu_features_x86.hpp +++ b/include/xsimd/config/xsimd_cpu_features_x86.hpp @@ -16,6 +16,9 @@ #include #include #include +#if __cplusplus >= 201703L +#include +#endif #include "../utils/bits.hpp" #include "./xsimd_config.hpp" @@ -95,8 +98,85 @@ namespace xsimd typename T::ebx, typename T::ecx, typename T::edx>; + + template + struct x86_cpuid_highest_func + { + private: + using x86_reg32_t = detail::x86_reg32_t; + using manufacturer_str = std::array; + + public: + static constexpr int leaf = extended ? 0x80000000 : 0x0; + + inline static x86_cpuid_highest_func read() + { + auto regs = detail::x86_cpuid(0); + x86_cpuid_highest_func out {}; + // Highest function parameter in EAX + out.m_highest_leaf = regs[0]; + + // Manufacturer string in EBX, EDX, ECX (in that order) + char* manuf = out.m_manufacturer_id.data(); + std::memcpy(manuf + 0 * sizeof(x86_reg32_t), ®s[1], sizeof(x86_reg32_t)); + std::memcpy(manuf + 1 * sizeof(x86_reg32_t), ®s[3], sizeof(x86_reg32_t)); + std::memcpy(manuf + 2 * sizeof(x86_reg32_t), ®s[2], sizeof(x86_reg32_t)); + + return out; + } + + constexpr x86_cpuid_highest_func() noexcept = default; + + /** + * Highest available leaf in CPUID non-extended range. + * + * This is the highest function parameter (EAX) that can be passed to CPUID. + * This is valid in the specified range: + * - if `extended` is `false`, that is below `0x80000000`, + * - if `extended` is `true`, that is above `0x80000000`, + */ + constexpr x86_reg32_t highest_leaf() const noexcept + { + return m_highest_leaf; + } + + /** + * The manufacturer ID string in a static array. + * + * This raw character array is case specific and may contain both leading + * and trailing whitespaces. + * It cannot be assumed to be null terminated. + * This is not implemented for all manufacturer when `extended` is `true`. + */ + constexpr manufacturer_str manufacturer_id_raw() const noexcept + { + return m_manufacturer_id; + } + +#if __cplusplus >= 201703L + constexpr std::string_view manufacturer_id() const noexcept + { + return { m_manufacturer_id.data(), m_manufacturer_id.size() }; + } +#endif + + private: + manufacturer_str m_manufacturer_id {}; + x86_reg32_t m_highest_leaf {}; + }; + } + /** + * Highest CPUID Function Parameter and Manufacturer ID (EAX=0). + * + * Returns the highest leaf value supported by CPUID in the standard range + * (below 0x80000000), and the processor manufacturer ID string. + * + * @see https://en.wikipedia.org/wiki/CPUID + */ + using x86_cpuid_leaf0 = detail::x86_cpuid_highest_func; + struct x86_cpuid_leaf1_traits { static constexpr detail::x86_reg32_t leaf = 1; @@ -236,6 +316,16 @@ namespace xsimd */ using x86_cpuid_leaf7sub1 = detail::make_x86_cpuid_regs; + /** + * Highest Extended CPUID Function Parameter (EAX=0x80000000). + * + * Returns the highest leaf value supported by CPUID in the extended range + * (at or above 0x80000000), and the processor manufacturer ID string. + * + * @see https://en.wikipedia.org/wiki/CPUID + */ + using x86_cpuid_leaf80000000 = detail::x86_cpuid_highest_func; + struct x86_cpuid_leaf80000001_traits { static constexpr detail::x86_reg32_t leaf = 0x80000001; From 48cd2dd255c694d9970d788f010aa5017cf3b674 Mon Sep 17 00:00:00 2001 From: AntoinePrv Date: Mon, 23 Mar 2026 15:25:48 +0100 Subject: [PATCH 2/7] Properly check highest leaf --- .../xsimd/config/xsimd_cpu_features_x86.hpp | 75 ++++++++++++++++--- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/include/xsimd/config/xsimd_cpu_features_x86.hpp b/include/xsimd/config/xsimd_cpu_features_x86.hpp index a0aa6afe7..924703b80 100644 --- a/include/xsimd/config/xsimd_cpu_features_x86.hpp +++ b/include/xsimd/config/xsimd_cpu_features_x86.hpp @@ -55,8 +55,13 @@ namespace xsimd template using x86_reg32_bitset = utils::uint_bitset; - template - class x86_cpuid_regs : private x86_reg32_bitset, x86_reg32_bitset, x86_reg32_bitset, x86_reg32_bitset + template + class x86_cpuid_regs + : private x86_reg32_bitset, + private x86_reg32_bitset, + private x86_reg32_bitset, + private x86_reg32_bitset { private: using eax_bitset = x86_reg32_bitset; @@ -78,6 +83,8 @@ namespace xsimd using ebx = B; using ecx = C; using edx = D; + static constexpr x86_reg32_t leaf = leaf_num; + static constexpr x86_reg32_t subleaf = subleaf_num; inline static x86_cpuid_regs read() { @@ -539,20 +546,24 @@ namespace xsimd private: enum class status { - leaf1_valid = 0, - leaf7_valid = 1, - leaf7sub1_valid = 2, - leaf80000001_valid = 3, - xcr0_valid = 4, + leaf0_valid = 0, + leaf1_valid = 1, + leaf7_valid = 2, + leaf7sub1_valid = 3, + leaf80000000_valid = 4, + leaf80000001_valid = 5, + xcr0_valid = 6, }; using status_bitset = utils::uint_bitset; - mutable x86_xcr0 m_xcr0 {}; + mutable x86_cpuid_leaf0 m_leaf0 {}; mutable x86_cpuid_leaf1 m_leaf1 {}; mutable x86_cpuid_leaf7 m_leaf7 {}; mutable x86_cpuid_leaf7sub1 m_leaf7sub1 {}; + mutable x86_cpuid_leaf80000000 m_leaf80000000 {}; mutable x86_cpuid_leaf80000001 m_leaf80000001 {}; + mutable x86_xcr0 m_xcr0 {}; mutable status_bitset m_status {}; inline x86_xcr0 const& xcr0() const noexcept @@ -565,11 +576,26 @@ namespace xsimd return m_xcr0; } + inline x86_cpuid_leaf0 const& leaf0() const + { + if (!m_status.bit_is_set()) + { + m_leaf0 = x86_cpuid_leaf0::read(); + m_status.set_bit(); + } + return m_leaf0; + } + inline x86_cpuid_leaf1 const& leaf1() const { if (!m_status.bit_is_set()) { - m_leaf1 = x86_cpuid_leaf1::read(); + // Check if safe to call CPUID with this value + if (leaf0().highest_leaf() >= x86_cpuid_leaf1::leaf) + { + m_leaf1 = x86_cpuid_leaf1::read(); + } + // Otherwise leave it filled with zeros and mark as valid m_status.set_bit(); } return m_leaf1; @@ -579,7 +605,12 @@ namespace xsimd { if (!m_status.bit_is_set()) { - m_leaf7 = x86_cpuid_leaf7::read(); + // Check if safe to call CPUID with this value + if (leaf0().highest_leaf() >= x86_cpuid_leaf7::leaf) + { + m_leaf7 = x86_cpuid_leaf7::read(); + } + // Otherwise leave it filled with zeros and mark as valid m_status.set_bit(); } return m_leaf7; @@ -589,17 +620,37 @@ namespace xsimd { if (!m_status.bit_is_set()) { - m_leaf7sub1 = x86_cpuid_leaf7sub1::read(); + // Check if safe to call CPUID with this value + if (leaf0().highest_leaf() >= x86_cpuid_leaf7::leaf) + { + m_leaf7sub1 = x86_cpuid_leaf7sub1::read(); + } + // Otherwise leave it filled with zeros and mark as valid m_status.set_bit(); } return m_leaf7sub1; } + inline x86_cpuid_leaf80000000 const& leaf80000000() const + { + if (!m_status.bit_is_set()) + { + m_leaf80000000 = x86_cpuid_leaf80000000::read(); + m_status.set_bit(); + } + return m_leaf80000000; + } + inline x86_cpuid_leaf80000001 const& leaf80000001() const { if (!m_status.bit_is_set()) { - m_leaf80000001 = x86_cpuid_leaf80000001::read(); + // Check if safe to call CPUID with this value + if (leaf80000000().highest_leaf() >= x86_cpuid_leaf80000001::leaf) + { + m_leaf80000001 = x86_cpuid_leaf80000001::read(); + } + // Otherwise leave it filled with zeros and mark as valid m_status.set_bit(); } return m_leaf80000001; From 81a8da8e5c9a805b78ff5218f8886811b7f420e3 Mon Sep 17 00:00:00 2001 From: AntoinePrv Date: Mon, 23 Mar 2026 15:34:29 +0100 Subject: [PATCH 3/7] Refactor leaf check --- .../xsimd/config/xsimd_cpu_features_x86.hpp | 92 +++++++++---------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/include/xsimd/config/xsimd_cpu_features_x86.hpp b/include/xsimd/config/xsimd_cpu_features_x86.hpp index 924703b80..4b3105c65 100644 --- a/include/xsimd/config/xsimd_cpu_features_x86.hpp +++ b/include/xsimd/config/xsimd_cpu_features_x86.hpp @@ -586,74 +586,72 @@ namespace xsimd return m_leaf0; } - inline x86_cpuid_leaf1 const& leaf1() const + inline x86_cpuid_leaf80000000 const& leaf80000000() const { - if (!m_status.bit_is_set()) + if (!m_status.bit_is_set()) { - // Check if safe to call CPUID with this value - if (leaf0().highest_leaf() >= x86_cpuid_leaf1::leaf) - { - m_leaf1 = x86_cpuid_leaf1::read(); - } - // Otherwise leave it filled with zeros and mark as valid - m_status.set_bit(); + m_leaf80000000 = x86_cpuid_leaf80000000::read(); + m_status.set_bit(); } - return m_leaf1; + return m_leaf80000000; } - inline x86_cpuid_leaf7 const& leaf7() const + template + inline auto const& safe_get_leaf(L& leaf) const { - if (!m_status.bit_is_set()) + // Check if already initialized + if (m_status.bit_is_set()) { - // Check if safe to call CPUID with this value - if (leaf0().highest_leaf() >= x86_cpuid_leaf7::leaf) + return leaf; + } + + // Limit where we need to check leaf0 or leaf 80000000. + constexpr auto extended_threshold = x86_cpuid_leaf80000000::leaf; + + // Check if safe to call CPUID with this value. + // First we identify if the leaf is in the regular or extended range. + // TODO(C++17): if constexpr + if (L::leaf < extended_threshold) + { + // Check leaf0 in regular range + if (L::leaf <= leaf0().highest_leaf()) { - m_leaf7 = x86_cpuid_leaf7::read(); + leaf = L::read(); } - // Otherwise leave it filled with zeros and mark as valid - m_status.set_bit(); } - return m_leaf7; - } - - inline x86_cpuid_leaf7sub1 const& leaf7sub1() const - { - if (!m_status.bit_is_set()) + else { - // Check if safe to call CPUID with this value - if (leaf0().highest_leaf() >= x86_cpuid_leaf7::leaf) + // Check leaf80000000 in extended range + if (L::leaf <= leaf80000000().highest_leaf()) { - m_leaf7sub1 = x86_cpuid_leaf7sub1::read(); + leaf = L::read(); } - // Otherwise leave it filled with zeros and mark as valid - m_status.set_bit(); } - return m_leaf7sub1; + + // Mark as valid in all cases, including if it was not read. + // In this case it will be filled with zeros (all false). + m_status.set_bit(); + return leaf; } - inline x86_cpuid_leaf80000000 const& leaf80000000() const + inline x86_cpuid_leaf1 const& leaf1() const { - if (!m_status.bit_is_set()) - { - m_leaf80000000 = x86_cpuid_leaf80000000::read(); - m_status.set_bit(); - } - return m_leaf80000000; + return safe_get_leaf(m_leaf1); + } + + inline x86_cpuid_leaf7 const& leaf7() const + { + return safe_get_leaf(m_leaf7); + } + + inline x86_cpuid_leaf7sub1 const& leaf7sub1() const + { + return safe_get_leaf(m_leaf7sub1); } inline x86_cpuid_leaf80000001 const& leaf80000001() const { - if (!m_status.bit_is_set()) - { - // Check if safe to call CPUID with this value - if (leaf80000000().highest_leaf() >= x86_cpuid_leaf80000001::leaf) - { - m_leaf80000001 = x86_cpuid_leaf80000001::read(); - } - // Otherwise leave it filled with zeros and mark as valid - m_status.set_bit(); - } - return m_leaf80000001; + return safe_get_leaf(m_leaf80000001); } }; From eac501831235a867d546cc49203137f804d6737f Mon Sep 17 00:00:00 2001 From: AntoinePrv Date: Mon, 23 Mar 2026 16:22:19 +0100 Subject: [PATCH 4/7] Fix leaf type --- include/xsimd/config/xsimd_cpu_features_x86.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xsimd/config/xsimd_cpu_features_x86.hpp b/include/xsimd/config/xsimd_cpu_features_x86.hpp index 4b3105c65..f487becae 100644 --- a/include/xsimd/config/xsimd_cpu_features_x86.hpp +++ b/include/xsimd/config/xsimd_cpu_features_x86.hpp @@ -114,7 +114,7 @@ namespace xsimd using manufacturer_str = std::array; public: - static constexpr int leaf = extended ? 0x80000000 : 0x0; + static constexpr x86_reg32_t leaf = extended ? 0x80000000 : 0x0; inline static x86_cpuid_highest_func read() { From d3d3b749ce77037707512b5dea3bf88e60123991 Mon Sep 17 00:00:00 2001 From: AntoinePrv Date: Mon, 23 Mar 2026 17:05:27 +0100 Subject: [PATCH 5/7] Fmt --- include/xsimd/config/xsimd_cpu_features_x86.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/xsimd/config/xsimd_cpu_features_x86.hpp b/include/xsimd/config/xsimd_cpu_features_x86.hpp index f487becae..0d8431833 100644 --- a/include/xsimd/config/xsimd_cpu_features_x86.hpp +++ b/include/xsimd/config/xsimd_cpu_features_x86.hpp @@ -171,7 +171,6 @@ namespace xsimd manufacturer_str m_manufacturer_id {}; x86_reg32_t m_highest_leaf {}; }; - } /** From 26b6f180a417365e7171e2aaeaed73367d118d5d Mon Sep 17 00:00:00 2001 From: AntoinePrv Date: Tue, 24 Mar 2026 10:43:55 +0100 Subject: [PATCH 6/7] Properly check if subleaf is available --- .../xsimd/config/xsimd_cpu_features_x86.hpp | 28 ++++++++++++++++++- include/xsimd/utils/bits.hpp | 26 +++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/include/xsimd/config/xsimd_cpu_features_x86.hpp b/include/xsimd/config/xsimd_cpu_features_x86.hpp index 0d8431833..f3fc48293 100644 --- a/include/xsimd/config/xsimd_cpu_features_x86.hpp +++ b/include/xsimd/config/xsimd_cpu_features_x86.hpp @@ -94,9 +94,13 @@ namespace xsimd constexpr x86_cpuid_regs() noexcept = default; using eax_bitset::all_bits_set; + using eax_bitset::get_range; using ebx_bitset::all_bits_set; + using ebx_bitset::get_range; using ecx_bitset::all_bits_set; + using ecx_bitset::get_range; using edx_bitset::all_bits_set; + using edx_bitset::get_range; }; template @@ -239,6 +243,10 @@ namespace xsimd enum class eax { + /* Start bit for the encoding of the highest subleaf available. */ + highest_subleaf_start = 0, + /* End bit for the encoding of the highest subleaf available. */ + highest_subleaf_end = 32, }; enum class ebx { @@ -645,7 +653,25 @@ namespace xsimd inline x86_cpuid_leaf7sub1 const& leaf7sub1() const { - return safe_get_leaf(m_leaf7sub1); + // Check if already initialized + if (m_status.bit_is_set()) + { + return m_leaf7sub1; + } + + // Check if safe to call CPUID with this value as subleaf. + constexpr auto start = x86_cpuid_leaf7::eax::highest_subleaf_start; + constexpr auto end = x86_cpuid_leaf7::eax::highest_subleaf_end; + const auto highest_subleaf7 = leaf7().get_range(); + if (x86_cpuid_leaf7sub1::subleaf <= highest_subleaf7) + { + m_leaf7sub1 = x86_cpuid_leaf7sub1::read(); + } + + // Mark as valid in all cases, including if it was not read. + // In this case it will be filled with zeros (all false). + m_status.set_bit(); + return m_leaf7sub1; } inline x86_cpuid_leaf80000001 const& leaf80000001() const diff --git a/include/xsimd/utils/bits.hpp b/include/xsimd/utils/bits.hpp index ffa09f8e7..c7123224f 100644 --- a/include/xsimd/utils/bits.hpp +++ b/include/xsimd/utils/bits.hpp @@ -46,6 +46,21 @@ namespace xsimd return value | mask; } + /** + * Return a mask with the `width` lowest bits set. + */ + template + constexpr I make_low_mask(I width) noexcept + { + assert(width >= 0); + assert(width <= static_cast(8 * sizeof(I))); + if (width == static_cast(8 * sizeof(I))) + { + return ~I { 0 }; + } + return (I { 1 } << width) - I { 1 }; + } + /* A bitset over an unsigned integer type, indexed by an enum key type. */ template struct uint_bitset @@ -82,6 +97,17 @@ namespace xsimd m_bitset = utils::set_bit(bit)>(m_bitset); } + /* Extract the bits in [start, end[, shifted down to start at bit 0. */ + template + constexpr storage_type get_range() const noexcept + { + constexpr storage_type start_bit = static_cast(start); + constexpr storage_type end_bit = static_cast(end); + constexpr storage_type width = end_bit - start_bit; + constexpr storage_type mask = make_low_mask(width); + return (m_bitset >> start_bit) & mask; + } + private: storage_type m_bitset = { 0 }; }; From 344a25443c88566284cc644c86d8a0f76b2455cc Mon Sep 17 00:00:00 2001 From: AntoinePrv Date: Tue, 24 Mar 2026 11:20:42 +0100 Subject: [PATCH 7/7] Add bit manip tests --- test/CMakeLists.txt | 1 + test/test_utils_bits.cpp | 122 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 test/test_utils_bits.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2513ffdc2..873f37ee2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -164,6 +164,7 @@ set(XSIMD_TESTS test_sum.cpp test_traits.cpp test_trigonometric.cpp + test_utils_bits.cpp test_xsimd_api.cpp test_utils.hpp ) diff --git a/test/test_utils_bits.cpp b/test/test_utils_bits.cpp new file mode 100644 index 000000000..f61a018ff --- /dev/null +++ b/test/test_utils_bits.cpp @@ -0,0 +1,122 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and * + * Martin Renou * + * Copyright (c) QuantStack * + * Copyright (c) Serge Guelton * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ***************************************************************************/ + +#include + +#include + +#include "xsimd/utils/bits.hpp" + +TEST_CASE("[utils::make_bit_mask] single bit") +{ + CHECK_EQ(xsimd::utils::make_bit_mask(0), 0x01); + CHECK_EQ(xsimd::utils::make_bit_mask(7), 0x80); + CHECK_EQ(xsimd::utils::make_bit_mask(0), 0x01u); + CHECK_EQ(xsimd::utils::make_bit_mask(31), 0x80000000u); + CHECK_EQ(xsimd::utils::make_bit_mask(0), 0x01); + CHECK_EQ(xsimd::utils::make_bit_mask(16), 0x00010000); +} + +TEST_CASE("[utils::make_bit_mask] multiple bits") +{ + CHECK_EQ(xsimd::utils::make_bit_mask(0, 1), 0b11); + CHECK_EQ(xsimd::utils::make_bit_mask(0, 2, 4), 0b00010101); +} + +TEST_CASE("[utils::all_bits_set] basic") +{ + CHECK(xsimd::utils::all_bits_set<0>(0x01)); + CHECK(xsimd::utils::all_bits_set<7>(0x80)); + CHECK_FALSE(xsimd::utils::all_bits_set<0>(0x00)); + CHECK_FALSE(xsimd::utils::all_bits_set<1>(0x01)); +} + +TEST_CASE("[utils::all_bits_set] multiple bits") +{ + CHECK((xsimd::utils::all_bits_set<0, 1>(0x03))); + CHECK((xsimd::utils::all_bits_set<0, 1>(0xFF))); + CHECK_FALSE((xsimd::utils::all_bits_set<0, 1>(0x01))); +} + +TEST_CASE("[utils::set_bit]") +{ + CHECK_EQ(xsimd::utils::set_bit<0>(0), 0x01); + CHECK_EQ(xsimd::utils::set_bit<3>(0), 0x08); + // Idempotent: setting an already-set bit + CHECK_EQ(xsimd::utils::set_bit<0>(0x01), 0x01); + // Does not clear other bits + CHECK_EQ(xsimd::utils::set_bit<1>(0b1101), 0b1111); +} + +TEST_CASE("[utils::make_low_mask]") +{ + CHECK_EQ(xsimd::utils::make_low_mask(0), 0x00); + CHECK_EQ(xsimd::utils::make_low_mask(1), 0x01); + CHECK_EQ(xsimd::utils::make_low_mask(4), 0x0F); + CHECK_EQ(xsimd::utils::make_low_mask(7), 0x7F); + // Full width + CHECK_EQ(xsimd::utils::make_low_mask(8), 0xFF); + CHECK_EQ(xsimd::utils::make_low_mask(32), 0xFFFFFFFFu); + CHECK_EQ(xsimd::utils::make_low_mask(64), 0xFFFFFFFFFFFFFFFFu); +} + +enum class flag : std::uint32_t +{ + A = 0, + B = 1, + C = 4, + D = 31, +}; + +TEST_CASE("[utils::uint_bitset] default construction") +{ + xsimd::utils::uint_bitset bs; + CHECK_FALSE(bs.bit_is_set()); + CHECK_FALSE(bs.bit_is_set()); +} + +TEST_CASE("[utils::uint_bitset] construction from raw value") +{ + xsimd::utils::uint_bitset bs(0b11); + CHECK(bs.bit_is_set()); + CHECK(bs.bit_is_set()); + CHECK_FALSE(bs.bit_is_set()); +} + +TEST_CASE("[utils::uint_bitset] set_bit") +{ + xsimd::utils::uint_bitset bs; + bs.set_bit(); + CHECK(bs.bit_is_set()); + CHECK_FALSE(bs.bit_is_set()); + bs.set_bit(); + CHECK(bs.bit_is_set()); +} + +TEST_CASE("[utils::uint_bitset] all_bits_set") +{ + xsimd::utils::uint_bitset bs(0b11); + CHECK((bs.all_bits_set())); + CHECK_FALSE((bs.all_bits_set())); +} + +TEST_CASE("[utils::uint_bitset] get_range") +{ + enum class rk : std::uint32_t + { + lo = 0, + mid = 4, + hi = 8 + }; + xsimd::utils::uint_bitset bs(0b10101011); + CHECK_EQ((bs.get_range()), 0b1011); + CHECK_EQ((bs.get_range()), 0b1010); +}