From e13f0825ec452734288f60673ee3baf4618fd2d3 Mon Sep 17 00:00:00 2001 From: serge-sans-paille Date: Sat, 14 Jun 2025 13:05:57 +0200 Subject: [PATCH] Add address and undefined sanitizers to CI To have them pass: - sanitize test inputs to avoid situations that trigger undefined behavior - silent undefined sanitizer on slow batch_cast --- .github/workflows/sanitizer.yml | 38 +++++++++++++++++++ .../xsimd/arch/common/xsimd_common_math.hpp | 8 +++- test/test_batch.cpp | 4 +- test/test_batch_cast.cpp | 15 ++++++-- test/test_batch_int.cpp | 8 ++-- test/test_power.cpp | 4 +- test/test_select.cpp | 8 +++- 7 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/sanitizer.yml diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml new file mode 100644 index 000000000..852019729 --- /dev/null +++ b/.github/workflows/sanitizer.yml @@ -0,0 +1,38 @@ +name: sanitizer +on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.job }}-${{ github.ref }} + cancel-in-progress: true +defaults: + run: + shell: bash -l {0} +jobs: + build: + runs-on: ubuntu-latest + name: 'sanitizer - ${{ matrix.sanitizer }}' + strategy: + matrix: + sanitizer: + - address + - undefined + steps: + - name: Checkout xsimd + uses: actions/checkout@v3 + - name: Configure build + run: | + mkdir _build + cd _build + cmake .. -DBUILD_TESTS=ON \ + -DBUILD_BENCHMARK=ON \ + -DBUILD_EXAMPLES=ON \ + -DDOWNLOAD_DOCTEST=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_CXX_FLAGS='-fsanitize=${{ matrix.sanitizer }}' \ + -G Ninja + - name: Build + run: ninja -C _build + - name: Test + run: | + cd _build/test + ./test_xsimd diff --git a/include/xsimd/arch/common/xsimd_common_math.hpp b/include/xsimd/arch/common/xsimd_common_math.hpp index 5962549c7..8fb23fd8a 100644 --- a/include/xsimd/arch/common/xsimd_common_math.hpp +++ b/include/xsimd/arch/common/xsimd_common_math.hpp @@ -116,6 +116,11 @@ namespace xsimd { return fast_cast(self, out, A {}); } +#if defined(__clang__) || __GNUC__ + template + XSIMD_INLINE batch batch_cast(batch const& self, batch const&, requires_arch, with_slow_conversion) noexcept + __attribute__((no_sanitize("undefined"))); +#endif template XSIMD_INLINE batch batch_cast(batch const& self, batch const&, requires_arch, with_slow_conversion) noexcept { @@ -126,7 +131,8 @@ namespace xsimd alignas(A::alignment()) T_in buffer_in[batch_type_in::size]; alignas(A::alignment()) T_out buffer_out[batch_type_out::size]; self.store_aligned(&buffer_in[0]); - std::copy(std::begin(buffer_in), std::end(buffer_in), std::begin(buffer_out)); + for (size_t i = 0; i < batch_type_in::size; ++i) + buffer_out[i] = static_cast(buffer_in[i]); return batch_type_out::load_aligned(buffer_out); } diff --git a/test/test_batch.cpp b/test/test_batch.cpp index dc6b1940d..05c13b4b8 100644 --- a/test/test_batch.cpp +++ b/test/test_batch.cpp @@ -879,12 +879,12 @@ struct batch_test for (size_t i = 0; i < size; ++i) { bool negative_lhs = std::is_signed::value && (i % 2 == 1); - lhs[i] = value_type(i) * (negative_lhs ? -10 : 10); + lhs[i] = value_type(i) * (negative_lhs ? -3 : 3); if (lhs[i] == value_type(0)) { lhs[i] += value_type(1); } - rhs[i] = value_type(i) + value_type(4); + rhs[i] = value_type(i) + value_type(2); } scalar = value_type(3); } diff --git a/test/test_batch_cast.cpp b/test/test_batch_cast.cpp index bc292d97f..8a638ed24 100644 --- a/test/test_batch_cast.cpp +++ b/test/test_batch_cast.cpp @@ -29,7 +29,7 @@ namespace detail is_convertible(T_in value) { int64_t signed_value = static_cast(value); - return signed_value <= static_cast(std::numeric_limits::max()) && signed_value >= static_cast(std::numeric_limits::lowest()); + return signed_value < static_cast(std::numeric_limits::max()) && signed_value >= static_cast(std::numeric_limits::lowest()); } template @@ -43,7 +43,7 @@ namespace detail inline typename std::enable_if::value && std::is_integral::value, bool>::type is_convertible(T_in value) { - return value <= static_cast(std::numeric_limits::max()) && value >= static_cast(std::numeric_limits::lowest()); + return value < static_cast(std::numeric_limits::max()) && value >= static_cast(std::numeric_limits::lowest()); } template @@ -328,12 +328,19 @@ struct batch_cast_test using B_common_in = xsimd::batch; using B_common_out = xsimd::batch; - T_in in_test_value = static_cast(test_value); + auto clamp = [](T v) + { + return static_cast(xsimd::min(v, static_cast(std::numeric_limits::max() - 1))); + }; + + T_in in_test_value = clamp(test_value); if (detail::is_convertible(in_test_value)) { B_common_out res = xsimd::batch_cast(B_common_in(in_test_value)); INFO(name); - CHECK_SCALAR_EQ(res.get(0), static_cast(in_test_value)); + T_out scalar_ref = static_cast(in_test_value); + T_out scalar_res = res.get(0); + CHECK_SCALAR_EQ(scalar_ref, scalar_res); } } diff --git a/test/test_batch_int.cpp b/test/test_batch_int.cpp index e63d120ed..9a992a566 100644 --- a/test/test_batch_int.cpp +++ b/test/test_batch_int.cpp @@ -239,8 +239,8 @@ struct batch_int_test array_type expected; std::transform(lhs.cbegin(), lhs.cend(), expected.begin(), [nb_sh](const value_type& v) - { return v << nb_sh; }); - batch_type res = batch_lhs() << nb_sh; + { return xsimd::abs(v) << nb_sh; }); + batch_type res = abs(batch_lhs()) << nb_sh; INFO("batch << scalar"); CHECK_BATCH_EQ(res, expected); } @@ -249,8 +249,8 @@ struct batch_int_test array_type expected; std::transform(lhs.cbegin(), lhs.cend(), shift.cbegin(), expected.begin(), [](const value_type& l, const value_type& r) - { return l << r; }); - batch_type res = batch_lhs() << batch_shift(); + { return xsimd::abs(l) << r; }); + batch_type res = abs(batch_lhs()) << batch_shift(); INFO("batch << batch"); CHECK_BATCH_EQ(res, expected); } diff --git a/test/test_power.cpp b/test/test_power.cpp index 20a847de4..4a81b0aa9 100644 --- a/test/test_power.cpp +++ b/test/test_power.cpp @@ -40,9 +40,9 @@ struct power_test for (size_t i = 0; i < nb_input; ++i) { zero_input[i] = 0; - lhs_input[i] = value_type(i) / 4 + value_type(1.2) * std::sqrt(value_type(i + 0.25)); + lhs_input[i] = value_type(i / 4 + 1.2 * std::sqrt(i + 0.25)); zlhs_input[i] = lhs_input[i] * (i % 2); - rhs_input[i] = value_type(10.2) / (i + 2) + value_type(0.25); + rhs_input[i] = value_type(10.2 / (i + 2) + 0.25); } expected.resize(nb_input); diff --git a/test/test_select.cpp b/test/test_select.cpp index f7e941753..6b450afd6 100644 --- a/test/test_select.cpp +++ b/test/test_select.cpp @@ -34,10 +34,14 @@ struct select_test nb_input = size * 10000; lhs_input.resize(nb_input); rhs_input.resize(nb_input); + auto clamp = [](double v) + { + return static_cast(std::min(v, static_cast(std::numeric_limits::max()))); + }; for (size_t i = 0; i < nb_input; ++i) { - lhs_input[i] = value_type(i) / 4 + value_type(1.2) * std::sqrt(value_type(i + 0.25)); - rhs_input[i] = value_type(10.2) / (i + 2) + value_type(0.25); + lhs_input[i] = clamp(i / 4 + 1.2 * std::sqrt(i + 0.25)); + rhs_input[i] = clamp(10.2 / (i + 2) + 0.25); } expected.resize(nb_input); res.resize(nb_input);