diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index d40bdfe..5f6d480 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -135,6 +135,80 @@ jobs: - name: Run test suite on SPR run: sde -spr -- ./builddir/testexe + ADL-ASAN-clang18: + + runs-on: intel-ubuntu-24.04 + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Install dependencies + run: | + sudo apt update + sudo apt -y install clang-18 libomp-18-dev libgtest-dev meson curl git + + - name: Install Intel SDE + run: | + curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/784319/sde-external-9.24.0-2023-07-13-lin.tar.xz + mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/ + sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde + + - name: Build examples + env: + CXX: clang++-18 + run: | + cd examples + make all + + - name: Build + env: + CXX: clang++-18 + run: | + make clean + meson setup -Dbuild_tests=true -Duse_openmp=true -Db_sanitize=address,undefined -Dfatal_sanitizers=true -Dasan_ci_dont_validate=true -Db_lundef=false --warnlevel 0 --buildtype release builddir + cd builddir + ninja + + - name: Run test suite on SPR + run: sde -adl -- ./builddir/testexe + + SPR-ASAN-clang18: + + runs-on: intel-ubuntu-24.04 + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Install dependencies + run: | + sudo apt update + sudo apt -y install clang-18 libomp-18-dev libgtest-dev meson curl git + + - name: Install Intel SDE + run: | + curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/784319/sde-external-9.24.0-2023-07-13-lin.tar.xz + mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/ + sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde + + - name: Build examples + env: + CXX: clang++-18 + run: | + cd examples + make all + + - name: Build + env: + CXX: clang++-18 + run: | + make clean + meson setup -Dbuild_tests=true -Duse_openmp=true -Db_sanitize=address,undefined -Dfatal_sanitizers=true -Dasan_ci_dont_validate=true -Db_lundef=false --warnlevel 0 --buildtype release builddir + cd builddir + ninja + + - name: Run test suite on SPR + run: sde -spr -- ./builddir/testexe + SKX-SKL-openmp: runs-on: intel-ubuntu-24.04 diff --git a/Makefile b/Makefile index 023ace6..c51d658 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ test_openmp: meson setup -Dbuild_tests=true -Duse_openmp=true --warnlevel 2 --werror --buildtype release builddir cd builddir && ninja +test_asan: + meson setup -Dbuild_tests=true -Duse_openmp=true -Db_sanitize=address,undefined -Dfatal_sanitizers=true -Db_lundef=false -Dasan_ci_dont_validate=true --warnlevel 0 --buildtype debugoptimized builddir + cd builddir && ninja + bench: meson setup -Dbuild_benchmarks=true --warnlevel 2 --werror --buildtype release builddir cd builddir && ninja diff --git a/meson.build b/meson.build index 71b4468..3a9f68a 100644 --- a/meson.build +++ b/meson.build @@ -18,6 +18,11 @@ if get_option('build_ippbench') ipplink = ['-lipps', '-lippcore'] endif +# Essentially '-Werror' for the sanitizers; all problems become fatal with this set +if get_option('fatal_sanitizers') + add_project_arguments([ '-fno-sanitize-recover=all' ], language: 'cpp') +endif + # Add google vqsort to benchmarks: benchvq = false if get_option('build_vqsortbench') diff --git a/meson_options.txt b/meson_options.txt index b6e6349..6edeb4e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -10,4 +10,7 @@ option('use_openmp', type : 'boolean', value : false, description : 'Use OpenMP to accelerate key-value sort (default: "false").') option('lib_type', type : 'string', value : 'shared', description : 'Library type: shared or static (default: "shared").') - +option('fatal_sanitizers', type : 'boolean', value : 'false', + description : 'If sanitizers are enabled, should all issues be considered fatal? (default: "false").') +option('asan_ci_dont_validate', type : 'boolean', value : 'false', + description : 'Only for speeding up ASAN CI, do not turn on otherwise') diff --git a/src/xss-common-qsort.h b/src/xss-common-qsort.h index 801ec72..5108e38 100644 --- a/src/xss-common-qsort.h +++ b/src/xss-common-qsort.h @@ -715,6 +715,9 @@ xss_qselect(T *arr, arrsize_t k, arrsize_t arrsize, bool hasnan) Comparator, Comparator>::type; + // Exit early if no work would be done + if (arrsize <= 1) return; + arrsize_t index_first_elem = 0; arrsize_t index_last_elem = arrsize - 1; diff --git a/tests/meson.build b/tests/meson.build index 92c689b..b070bcc 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -4,23 +4,33 @@ if get_option('use_openmp') openmpflags = ['-DXSS_USE_OPENMP=true'] endif +# Add compile flags when needed for the ASAN CI run +testargs = [] +if get_option('asan_ci_dont_validate') + if get_option('fatal_sanitizers') + testargs = ['-DXSS_ASAN_CI_NOCHECK=true'] + else + error('asan_ci_dont_validate is only for the ASAN CI, should be false otherwise!') + endif +endif + libtests += static_library('tests_qsort', files('test-qsort.cpp', ), dependencies: gtest_dep, include_directories : [src, lib, utils], - cpp_args : [openmpflags], + cpp_args : [testargs, openmpflags], ) libtests += static_library('tests_kvsort', files('test-keyvalue.cpp', ), dependencies: gtest_dep, include_directories : [src, lib, utils], - cpp_args : [openmpflags], + cpp_args : [testargs, openmpflags], ) libtests += static_library('tests_objsort', files('test-objqsort.cpp', ), dependencies: gtest_dep, include_directories : [src, lib, utils], - cpp_args : [openmpflags], + cpp_args : [testargs, openmpflags], ) diff --git a/tests/test-keyvalue.cpp b/tests/test-keyvalue.cpp index c1386af..3e3f4b0 100644 --- a/tests/test-keyvalue.cpp +++ b/tests/test-keyvalue.cpp @@ -14,8 +14,8 @@ class simdkvsort : public ::testing::Test { public: simdkvsort() { - std::iota(arrsize.begin(), arrsize.end(), 1); - std::iota(arrsize_long.begin(), arrsize_long.end(), 1); + std::iota(arrsize.begin(), arrsize.end(), 0); + std::iota(arrsize_long.begin(), arrsize_long.end(), 0); #ifdef XSS_USE_OPENMP // These extended tests are only needed for the OpenMP logic arrsize_long.push_back(10'000); @@ -63,6 +63,9 @@ bool is_kv_sorted( { auto cmp_eq = compare>(); + // Always true for arrays of zero length + if (size == 0) return true; + // First check keys are exactly identical for (size_t i = 0; i < size; i++) { if (!cmp_eq(keys_comp[i], keys_ref[i])) { return false; } @@ -178,8 +181,10 @@ TYPED_TEST_P(simdkvsort, test_kvsort_ascending) std::vector val = get_array(type, size); std::vector key_bckp = key; std::vector val_bckp = val; + x86simdsort::keyvalue_qsort( key.data(), val.data(), size, hasnan, false); +#ifndef XSS_ASAN_CI_NOCHECK xss::scalar::keyvalue_qsort( key_bckp.data(), val_bckp.data(), size, hasnan, false); @@ -189,7 +194,7 @@ TYPED_TEST_P(simdkvsort, test_kvsort_ascending) val_bckp.data(), size); ASSERT_EQ(is_kv_sorted_, true); - +#endif key.clear(); val.clear(); key_bckp.clear(); @@ -209,8 +214,10 @@ TYPED_TEST_P(simdkvsort, test_kvsort_descending) std::vector val = get_array(type, size); std::vector key_bckp = key; std::vector val_bckp = val; + x86simdsort::keyvalue_qsort( key.data(), val.data(), size, hasnan, true); +#ifndef XSS_ASAN_CI_NOCHECK xss::scalar::keyvalue_qsort( key_bckp.data(), val_bckp.data(), size, hasnan, true); @@ -220,7 +227,7 @@ TYPED_TEST_P(simdkvsort, test_kvsort_descending) val_bckp.data(), size); ASSERT_EQ(is_kv_sorted_, true); - +#endif key.clear(); val.clear(); key_bckp.clear(); @@ -237,19 +244,21 @@ TYPED_TEST_P(simdkvsort, test_kvselect_ascending) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - size_t k = rand() % size; + size_t k = size != 0 ? rand() % size : 0; std::vector key = get_array(type, size); std::vector val = get_array(type, size); std::vector key_bckp = key; std::vector val_bckp = val; + x86simdsort::keyvalue_select( + key.data(), val.data(), k, size, hasnan, false); +#ifndef XSS_ASAN_CI_NOCHECK xss::scalar::keyvalue_qsort( key_bckp.data(), val_bckp.data(), size, hasnan, false); // Test select by using it as part of partial_sort - x86simdsort::keyvalue_select( - key.data(), val.data(), k, size, hasnan, false); + if (size == 0) continue; IS_ARR_PARTITIONED(key, k, key_bckp[k], type); xss::scalar::keyvalue_qsort( key.data(), val.data(), k, hasnan, false); @@ -264,7 +273,7 @@ TYPED_TEST_P(simdkvsort, test_kvselect_ascending) size, k); ASSERT_EQ(is_kv_partialsorted_, true); - +#endif key.clear(); val.clear(); key_bckp.clear(); @@ -281,19 +290,21 @@ TYPED_TEST_P(simdkvsort, test_kvselect_descending) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - size_t k = rand() % size; + size_t k = size != 0 ? rand() % size : 0; std::vector key = get_array(type, size); std::vector val = get_array(type, size); std::vector key_bckp = key; std::vector val_bckp = val; + x86simdsort::keyvalue_select( + key.data(), val.data(), k, size, hasnan, true); +#ifndef XSS_ASAN_CI_NOCHECK xss::scalar::keyvalue_qsort( key_bckp.data(), val_bckp.data(), size, hasnan, true); // Test select by using it as part of partial_sort - x86simdsort::keyvalue_select( - key.data(), val.data(), k, size, hasnan, true); + if (size == 0) continue; IS_ARR_PARTITIONED(key, k, key_bckp[k], type, true); xss::scalar::keyvalue_qsort( key.data(), val.data(), k, hasnan, true); @@ -308,7 +319,7 @@ TYPED_TEST_P(simdkvsort, test_kvselect_descending) size, k); ASSERT_EQ(is_kv_partialsorted_, true); - +#endif key.clear(); val.clear(); key_bckp.clear(); @@ -324,14 +335,17 @@ TYPED_TEST_P(simdkvsort, test_kvpartial_sort_ascending) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - size_t k = rand() % size; + size_t k = size != 0 ? rand() % size : 0; std::vector key = get_array(type, size); std::vector val = get_array(type, size); std::vector key_bckp = key; std::vector val_bckp = val; + x86simdsort::keyvalue_partial_sort( key.data(), val.data(), k, size, hasnan, false); +#ifndef XSS_ASAN_CI_NOCHECK + if (size == 0) continue; xss::scalar::keyvalue_qsort( key_bckp.data(), val_bckp.data(), size, hasnan, false); @@ -345,7 +359,7 @@ TYPED_TEST_P(simdkvsort, test_kvpartial_sort_ascending) size, k); ASSERT_EQ(is_kv_partialsorted_, true); - +#endif key.clear(); val.clear(); key_bckp.clear(); @@ -361,14 +375,17 @@ TYPED_TEST_P(simdkvsort, test_kvpartial_sort_descending) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - size_t k = rand() % size; + size_t k = size != 0 ? rand() % size : 0; std::vector key = get_array(type, size); std::vector val = get_array(type, size); std::vector key_bckp = key; std::vector val_bckp = val; + x86simdsort::keyvalue_partial_sort( key.data(), val.data(), k, size, hasnan, true); +#ifndef XSS_ASAN_CI_NOCHECK + if (size == 0) continue; xss::scalar::keyvalue_qsort( key_bckp.data(), val_bckp.data(), size, hasnan, true); @@ -382,7 +399,7 @@ TYPED_TEST_P(simdkvsort, test_kvpartial_sort_descending) size, k); ASSERT_EQ(is_kv_partialsorted_, true); - +#endif key.clear(); val.clear(); key_bckp.clear(); diff --git a/tests/test-objqsort.cpp b/tests/test-objqsort.cpp index 81aa7c8..a0c1ac9 100644 --- a/tests/test-objqsort.cpp +++ b/tests/test-objqsort.cpp @@ -25,7 +25,7 @@ class simdobjsort : public ::testing::Test { public: simdobjsort() { - std::iota(arrsize.begin(), arrsize.end(), 1); + std::iota(arrsize.begin(), arrsize.end(), 0); arrtype = {"random", "constant", "sorted", diff --git a/tests/test-qsort-common.h b/tests/test-qsort-common.h index 0d67b37..e894a86 100644 --- a/tests/test-qsort-common.h +++ b/tests/test-qsort-common.h @@ -29,6 +29,7 @@ inline bool is_nan_test(std::string type) template void IS_SORTED(std::vector sorted, std::vector arr, std::string type) { + if (arr.size() == 0) return; if (memcmp(arr.data(), sorted.data(), arr.size() * sizeof(T)) != 0) { REPORT_FAIL("Array not sorted", arr.size(), type, -1); } diff --git a/tests/test-qsort.cpp b/tests/test-qsort.cpp index 8a48207..3083f72 100644 --- a/tests/test-qsort.cpp +++ b/tests/test-qsort.cpp @@ -10,8 +10,8 @@ class simdsort : public ::testing::Test { public: simdsort() { - std::iota(arrsize.begin(), arrsize.end(), 1); - std::iota(arrsize_long.begin(), arrsize_long.end(), 1); + std::iota(arrsize.begin(), arrsize.end(), 0); + std::iota(arrsize_long.begin(), arrsize_long.end(), 0); #ifdef XSS_USE_OPENMP // These extended tests are only needed for the OpenMP logic arrsize_long.push_back(10'000); @@ -47,12 +47,14 @@ TYPED_TEST_P(simdsort, test_qsort_ascending) // Ascending order std::vector arr = basearr; std::vector sortedarr = arr; + + x86simdsort::qsort(arr.data(), arr.size(), hasnan); +#ifndef XSS_ASAN_CI_NOCHECK std::sort(sortedarr.begin(), sortedarr.end(), compare>()); - x86simdsort::qsort(arr.data(), arr.size(), hasnan); IS_SORTED(sortedarr, arr, type); - +#endif arr.clear(); sortedarr.clear(); } @@ -69,12 +71,14 @@ TYPED_TEST_P(simdsort, test_qsort_descending) // Descending order std::vector arr = basearr; std::vector sortedarr = arr; + + x86simdsort::qsort(arr.data(), arr.size(), hasnan, true); +#ifndef XSS_ASAN_CI_NOCHECK std::sort(sortedarr.begin(), sortedarr.end(), compare>()); - x86simdsort::qsort(arr.data(), arr.size(), hasnan, true); IS_SORTED(sortedarr, arr, type); - +#endif arr.clear(); sortedarr.clear(); } @@ -88,11 +92,14 @@ TYPED_TEST_P(simdsort, test_argsort_ascending) for (auto size : this->arrsize) { std::vector arr = get_array(type, size); std::vector sortedarr = arr; + + auto arg = x86simdsort::argsort(arr.data(), arr.size(), hasnan); +#ifndef XSS_ASAN_CI_NOCHECK std::sort(sortedarr.begin(), sortedarr.end(), compare>()); - auto arg = x86simdsort::argsort(arr.data(), arr.size(), hasnan); IS_ARG_SORTED(sortedarr, arr, arg, type); +#endif arr.clear(); arg.clear(); } @@ -106,12 +113,15 @@ TYPED_TEST_P(simdsort, test_argsort_descending) for (auto size : this->arrsize) { std::vector arr = get_array(type, size); std::vector sortedarr = arr; + + auto arg = x86simdsort::argsort( + arr.data(), arr.size(), hasnan, true); +#ifndef XSS_ASAN_CI_NOCHECK std::sort(sortedarr.begin(), sortedarr.end(), compare>()); - auto arg = x86simdsort::argsort( - arr.data(), arr.size(), hasnan, true); IS_ARG_SORTED(sortedarr, arr, arg, type); +#endif arr.clear(); arg.clear(); } @@ -123,19 +133,22 @@ TYPED_TEST_P(simdsort, test_qselect_ascending) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - size_t k = rand() % size; + size_t k = size != 0 ? rand() % size : 0; std::vector basearr = get_array(type, size); // Ascending order std::vector arr = basearr; std::vector sortedarr = arr; + + x86simdsort::qselect(arr.data(), k, arr.size(), hasnan); +#ifndef XSS_ASAN_CI_NOCHECK std::nth_element(sortedarr.begin(), sortedarr.begin() + k, sortedarr.end(), compare>()); - x86simdsort::qselect(arr.data(), k, arr.size(), hasnan); + if (size == 0) continue; IS_ARR_PARTITIONED(arr, k, sortedarr[k], type); - +#endif arr.clear(); sortedarr.clear(); } @@ -147,19 +160,22 @@ TYPED_TEST_P(simdsort, test_qselect_descending) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - size_t k = rand() % size; + size_t k = size != 0 ? rand() % size : 0; std::vector basearr = get_array(type, size); // Descending order std::vector arr = basearr; std::vector sortedarr = arr; + + x86simdsort::qselect(arr.data(), k, arr.size(), hasnan, true); +#ifndef XSS_ASAN_CI_NOCHECK std::nth_element(sortedarr.begin(), sortedarr.begin() + k, sortedarr.end(), compare>()); - x86simdsort::qselect(arr.data(), k, arr.size(), hasnan, true); + if (size == 0) continue; IS_ARR_PARTITIONED(arr, k, sortedarr[k], type, true); - +#endif arr.clear(); sortedarr.clear(); } @@ -171,15 +187,19 @@ TYPED_TEST_P(simdsort, test_argselect) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - size_t k = rand() % size; + size_t k = size != 0 ? rand() % size : 0; std::vector arr = get_array(type, size); std::vector sortedarr = arr; + + auto arg + = x86simdsort::argselect(arr.data(), k, arr.size(), hasnan); +#ifndef XSS_ASAN_CI_NOCHECK std::sort(sortedarr.begin(), sortedarr.end(), compare>()); - auto arg - = x86simdsort::argselect(arr.data(), k, arr.size(), hasnan); + if (size == 0) continue; IS_ARG_PARTITIONED(arr, arg, sortedarr[k], k, type); +#endif arr.clear(); sortedarr.clear(); } @@ -191,18 +211,21 @@ TYPED_TEST_P(simdsort, test_partial_qsort_ascending) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - size_t k = rand() % size; + size_t k = size != 0 ? rand() % size : 0; std::vector basearr = get_array(type, size); // Ascending order std::vector arr = basearr; std::vector sortedarr = arr; + + x86simdsort::partial_qsort(arr.data(), k, arr.size(), hasnan); +#ifndef XSS_ASAN_CI_NOCHECK std::sort(sortedarr.begin(), sortedarr.end(), compare>()); - x86simdsort::partial_qsort(arr.data(), k, arr.size(), hasnan); + if (size == 0) continue; IS_ARR_PARTIALSORTED(arr, k, sortedarr, type); - +#endif arr.clear(); sortedarr.clear(); } @@ -214,19 +237,21 @@ TYPED_TEST_P(simdsort, test_partial_qsort_descending) for (auto type : this->arrtype) { bool hasnan = is_nan_test(type); for (auto size : this->arrsize) { - // k should be at least 1 - size_t k = std::max((size_t)1, rand() % size); + size_t k = size != 0 ? rand() % size : 0; std::vector basearr = get_array(type, size); // Descending order std::vector arr = basearr; std::vector sortedarr = arr; + + x86simdsort::partial_qsort(arr.data(), k, arr.size(), hasnan, true); +#ifndef XSS_ASAN_CI_NOCHECK std::sort(sortedarr.begin(), sortedarr.end(), compare>()); - x86simdsort::partial_qsort(arr.data(), k, arr.size(), hasnan, true); + if (size == 0) continue; IS_ARR_PARTIALSORTED(arr, k, sortedarr, type); - +#endif arr.clear(); sortedarr.clear(); } diff --git a/utils/rand_array.h b/utils/rand_array.h index dcb0d01..9deb947 100644 --- a/utils/rand_array.h +++ b/utils/rand_array.h @@ -70,6 +70,7 @@ static std::vector get_array(std::string arrtype, T max = xss::fp::max()) { std::vector arr; + if (arrsize == 0) return arr; if (arrtype == "random") { arr = get_uniform_rand_array(arrsize, max, min); }