From 9e6078709438072b8e46e2922939af0c0555be83 Mon Sep 17 00:00:00 2001 From: Daniel Anderson Date: Mon, 11 Sep 2023 01:25:15 -0400 Subject: [PATCH] Disable exceptions when built with -fno-exceptions (#59) ParlayLib now removes all exception code when compiled with -fno-exceptions. You can also explicitly disable exceptions just inside ParlayLib with -DPARLAY_NO_EXCEPTIONS. --- .github/workflows/build.yml | 32 +++++++++++++ CMakeLists.txt | 2 +- analysis/CMakeLists.txt | 4 ++ include/parlay/alloc.h | 8 ++++ include/parlay/delayed_sequence.h | 12 ++--- include/parlay/internal/bucket_sort.h | 18 ++++++-- include/parlay/internal/counting_sort.h | 1 + include/parlay/internal/file_map.h | 2 + include/parlay/internal/integer_sort.h | 18 ++++++-- include/parlay/internal/pool_allocator.h | 2 +- include/parlay/internal/sample_sort.h | 13 +++++- .../internal/scheduler_plugins/sequential.h | 1 + .../parlay/internal/uninitialized_sequence.h | 11 +++-- include/parlay/internal/work_stealing_deque.h | 2 +- include/parlay/internal/work_stealing_job.h | 6 +-- include/parlay/portability.h | 46 +++++++++++++++++++ include/parlay/scheduler.h | 1 - include/parlay/sequence.h | 10 ++-- test/test_delayed_sequence.cpp | 32 ++++++++++++- test/test_sequence.cpp | 30 ++++++++++++ 20 files changed, 216 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45a47ce8..2ceb9389 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -465,6 +465,38 @@ jobs: cd build ctest -C Debug --no-tests=error --output-on-failure + noexcept: + name: ubuntu-22.04 GCC 12 Exceptions Disabled (Debug) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + + - name: Install Compiler + shell: bash + run: | + sudo add-apt-repository ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get -qq install gcc-12 g++-12 + + - name: Configure + shell: bash + run: | + mkdir build && cd build + CC=gcc-12 CXX=g++-12 cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fno-exceptions" -DPARLAY_TEST=On DPARLAY_BENCHMARK=On .. + + - name: Build + shell: bash + run: | + cd build + cmake --build . --config Debug + + - name: Test + shell: bash + run: | + cd build + ctest -C Debug --no-tests=error --output-on-failure + cppcheck: name: Cppcheck if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name diff --git a/CMakeLists.txt b/CMakeLists.txt index 69cb2a30..ee3ec393 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ # ------------------------------------------------------------------- cmake_minimum_required(VERSION 3.14) -project(PARLAY VERSION 2.2.0 +project(PARLAY VERSION 2.2.1 DESCRIPTION "A collection of parallel algorithms and other support for parallelism in C++" LANGUAGES CXX) diff --git a/analysis/CMakeLists.txt b/analysis/CMakeLists.txt index 8cb42bdd..bca753bb 100644 --- a/analysis/CMakeLists.txt +++ b/analysis/CMakeLists.txt @@ -6,14 +6,18 @@ configure_danalysis( ${PARLAY_INCLUDE_DIR} ${PARLAY_INCLUDE_DIR}/parlay FILES + ${PARLAY_INCLUDE_DIR}/parlay/internal/binary_search.h ${PARLAY_INCLUDE_DIR}/parlay/internal/block_allocator.h ${PARLAY_INCLUDE_DIR}/parlay/internal/block_delayed.h + ${PARLAY_INCLUDE_DIR}/parlay/internal/bucket_sort.h ${PARLAY_INCLUDE_DIR}/parlay/internal/collect_reduce.h ${PARLAY_INCLUDE_DIR}/parlay/internal/counting_sort.h ${PARLAY_INCLUDE_DIR}/parlay/internal/file_map.h ${PARLAY_INCLUDE_DIR}/parlay/internal/group_by.h ${PARLAY_INCLUDE_DIR}/parlay/internal/heap_tree.h + ${PARLAY_INCLUDE_DIR}/parlay/internal/integer_sort.h ${PARLAY_INCLUDE_DIR}/parlay/internal/pool_allocator.h + ${PARLAY_INCLUDE_DIR}/parlay/internal/sample_sort.h ${PARLAY_INCLUDE_DIR}/parlay/internal/sequence_base.h ${PARLAY_INCLUDE_DIR}/parlay/internal/sequence_ops.h ${PARLAY_INCLUDE_DIR}/parlay/internal/stream_delayed.h diff --git a/include/parlay/alloc.h b/include/parlay/alloc.h index f02003b0..41a5cd12 100644 --- a/include/parlay/alloc.h +++ b/include/parlay/alloc.h @@ -98,6 +98,11 @@ inline size_t alloc_padding_size(size_t n) { // in bytes } // namespace internal +// GCC doesn't like placement new into the offset buffer +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wplacement-new" +#endif // Allocate size bytes of uninitialized storage. Optionally ask for an // aligned buffer of memory with the given alignment. @@ -133,6 +138,9 @@ inline void p_free(void* ptr) { internal::get_default_allocator().deallocate(buffer, size_t{1} << size_t(h.log_size)); } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif // ---------------------------------------------------------------------------- // Container allocator diff --git a/include/parlay/delayed_sequence.h b/include/parlay/delayed_sequence.h index d94987c3..fc7055f9 100644 --- a/include/parlay/delayed_sequence.h +++ b/include/parlay/delayed_sequence.h @@ -271,11 +271,9 @@ class delayed_sequence { // Subscript access with bounds checking T at(size_t i) const { if (i < first || i >= last) { - throw std::out_of_range("Delayed sequence access out of" - "range at " + std::to_string(i) + - "for a sequence with bounds [" + - std::to_string(first) + ", " + - std::to_string(last) + ")"); + throw_exception_or_terminate("Delayed sequence access out of range at " + std::to_string(i) + + "for a sequence with bounds [" + std::to_string(first) + ", " + + std::to_string(last) + ")"); } return f(i); } @@ -297,13 +295,13 @@ class delayed_sequence { #endif // Size - size_t size() const { + [[nodiscard]] size_t size() const { assert(first <= last); return last - first; } // Is empty? - bool empty() const { + [[nodiscard]] bool empty() const { return size() == 0; } diff --git a/include/parlay/internal/bucket_sort.h b/include/parlay/internal/bucket_sort.h index fa97e3df..3665748d 100644 --- a/include/parlay/internal/bucket_sort.h +++ b/include/parlay/internal/bucket_sort.h @@ -2,10 +2,20 @@ #ifndef PARLAY_BUCKET_SORT_H_ #define PARLAY_BUCKET_SORT_H_ +#include +#include + +#include + #include "merge_sort.h" #include "quicksort.h" -#include "sequence_ops.h" +#include "uninitialized_sequence.h" + +#include "../parallel.h" +#include "../relocation.h" +#include "../sequence.h" +#include "../slice.h" #include "../utilities.h" namespace parlay { @@ -30,9 +40,9 @@ void radix_step_(slice A, counts[i] = s; } - for (std::ptrdiff_t j = n - 1; j >= 0; j--) { - auto x = --counts[keys[j]]; - uninitialized_relocate(&B[x], &A[j]); + for (size_t j = n; j > 0; j--) { + auto x = --counts[keys[j-1]]; + uninitialized_relocate(&B[x], &A[j-1]); } } diff --git a/include/parlay/internal/counting_sort.h b/include/parlay/internal/counting_sort.h index edf92009..28d1e5a0 100644 --- a/include/parlay/internal/counting_sort.h +++ b/include/parlay/internal/counting_sort.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "sequence_ops.h" diff --git a/include/parlay/internal/file_map.h b/include/parlay/internal/file_map.h index 9793f5aa..5f69a3c7 100644 --- a/include/parlay/internal/file_map.h +++ b/include/parlay/internal/file_map.h @@ -25,6 +25,8 @@ #if defined(PARLAY_NO_FILE_MAP) || defined(PARLAY_USE_FALLBACK_FILE_MAP) +#include + #include #include diff --git a/include/parlay/internal/integer_sort.h b/include/parlay/internal/integer_sort.h index 486134a8..cc8c67fb 100644 --- a/include/parlay/internal/integer_sort.h +++ b/include/parlay/internal/integer_sort.h @@ -2,17 +2,26 @@ #ifndef PARLAY_INTEGER_SORT_H_ #define PARLAY_INTEGER_SORT_H_ -#include -#include +#include #include +#include +#include +#include +#include +#include + #include "counting_sort.h" #include "sequence_ops.h" -#include "quicksort.h" #include "uninitialized_sequence.h" #include "get_time.h" #include "../delayed_sequence.h" +#include "../monoid.h" +#include "../parallel.h" +#include "../range.h" +#include "../relocation.h" +#include "../sequence.h" #include "../slice.h" #include "../utilities.h" @@ -283,8 +292,9 @@ sequence integer_sort_r(slice In, auto b = Tmp.cut(start, end); sequence r; + auto new_parallelism = (parallelism * static_cast(end - start)) / static_cast(n + 1); r = integer_sort_r::type, uninitialized_relocate_tag>( - a, b, a, g, shift_bits, num_inner_buckets, (parallelism * (end - start)) / (n + 1)); + a, b, a, g, shift_bits, num_inner_buckets, new_parallelism); if (return_offsets) { size_t bstart = (std::min)(i * num_inner_buckets, num_buckets); diff --git a/include/parlay/internal/pool_allocator.h b/include/parlay/internal/pool_allocator.h index b6835735..2b8c2ac1 100644 --- a/include/parlay/internal/pool_allocator.h +++ b/include/parlay/internal/pool_allocator.h @@ -15,6 +15,7 @@ #include #include +#include "../portability.h" #include "../utilities.h" #include "block_allocator.h" @@ -77,7 +78,6 @@ struct pool_allocator { } void* a = ::operator new(alloc_size, std::align_val_t{max_alignment}); - if (a == nullptr) throw std::bad_alloc(); large_allocated += n; return a; diff --git a/include/parlay/internal/sample_sort.h b/include/parlay/internal/sample_sort.h index 7add7db3..59175e0d 100644 --- a/include/parlay/internal/sample_sort.h +++ b/include/parlay/internal/sample_sort.h @@ -8,15 +8,24 @@ #ifndef PARLAY_SAMPLE_SORT_H_ #define PARLAY_SAMPLE_SORT_H_ -#include +#include #include -#include + +#include +#include +#include #include "bucket_sort.h" #include "quicksort.h" #include "sequence_ops.h" #include "transpose.h" +#include "uninitialized_sequence.h" +#include "../delayed_sequence.h" +#include "../parallel.h" +#include "../relocation.h" +#include "../sequence.h" +#include "../slice.h" #include "../utilities.h" namespace parlay { diff --git a/include/parlay/internal/scheduler_plugins/sequential.h b/include/parlay/internal/scheduler_plugins/sequential.h index c4454626..20e9b4d4 100644 --- a/include/parlay/internal/scheduler_plugins/sequential.h +++ b/include/parlay/internal/scheduler_plugins/sequential.h @@ -4,6 +4,7 @@ #include #include +#include namespace parlay { diff --git a/include/parlay/internal/uninitialized_sequence.h b/include/parlay/internal/uninitialized_sequence.h index 533e32d1..dcfc9c8b 100644 --- a/include/parlay/internal/uninitialized_sequence.h +++ b/include/parlay/internal/uninitialized_sequence.h @@ -6,6 +6,7 @@ #include #include "../alloc.h" +#include "../portability.h" #include "debug_uninitialized.h" @@ -156,7 +157,7 @@ class uninitialized_sequence { std::swap(impl.data, other.impl.data); } - size_type size() const { return impl.n; } + [[nodiscard]] size_type size() const { return impl.n; } value_type* data() { return impl.data; } @@ -167,8 +168,8 @@ class uninitialized_sequence { value_type& at(size_t i) { if (i >= size()) { - throw std::out_of_range("uninitialized_sequence access out of bounds: length = " + - std::to_string(size()) + ", index = " + std::to_string(i)); + throw_exception_or_terminate("uninitialized_sequence access out of bounds: length = " + + std::to_string(size()) + ", index = " + std::to_string(i)); } else { return impl.data[i]; @@ -177,8 +178,8 @@ class uninitialized_sequence { const value_type& at(size_t i) const { if (i >= size()) { - throw std::out_of_range("uninitialized_sequence access out of bounds: length = " + - std::to_string(size()) + ", index = " + std::to_string(i)); + throw_exception_or_terminate("uninitialized_sequence access out of bounds: length = " + + std::to_string(size()) + ", index = " + std::to_string(i)); } else { return impl.data[i]; diff --git a/include/parlay/internal/work_stealing_deque.h b/include/parlay/internal/work_stealing_deque.h index bc5eef23..9282c6c7 100644 --- a/include/parlay/internal/work_stealing_deque.h +++ b/include/parlay/internal/work_stealing_deque.h @@ -55,7 +55,7 @@ struct Deque { deq[local_bot].job.store(job, std::memory_order_release); // shared store local_bot += 1; if (local_bot == q_size) { - std::cerr << "internal error: scheduler queue overflow" << std::endl; + std::cerr << "internal error: scheduler queue overflow\n"; std::abort(); } bot.store(local_bot, std::memory_order_seq_cst); // shared store diff --git a/include/parlay/internal/work_stealing_job.h b/include/parlay/internal/work_stealing_job.h index 3d8cc96f..0b58c829 100644 --- a/include/parlay/internal/work_stealing_job.h +++ b/include/parlay/internal/work_stealing_job.h @@ -15,10 +15,8 @@ namespace parlay { // and return nothing. Could be a lambda, e.g. [] () {}. struct WorkStealingJob { - WorkStealingJob() { - done.store(false, std::memory_order_relaxed); - } - ~WorkStealingJob() = default; + WorkStealingJob() : done{false} { } + virtual ~WorkStealingJob() = default; void operator()() { assert(done.load(std::memory_order_relaxed) == false); execute(); diff --git a/include/parlay/portability.h b/include/parlay/portability.h index 88608970..88a37188 100644 --- a/include/parlay/portability.h +++ b/include/parlay/portability.h @@ -13,6 +13,10 @@ #endif #endif +#include + +#include + namespace parlay { // PARLAY_INLINE: Ask the compiler politely to inline the given function. @@ -24,6 +28,25 @@ namespace parlay { #define PARLAY_INLINE inline #endif +// PARLAY_NOINLINE: Ask the compiler to *not* inline the given function +#if defined(__GNUC__) +#define PARLAY_NOINLINE __attribute__((__noinline__)) +#elif defined(_MSC_VER) +#define PARLAY_NOINLINE __declspec(noinline) +#else +#define PARLAY_NOINLINE +#endif + +// PARLAY_COLD: Ask the compiler to place the given function far away from other code +#if defined(__GNUC__) +#define PARLAY_COLD __attribute__((__cold__)) +#elif defined(_MSC_VER) +#define PARLAY_COLD +#else +#define PARLAY_COLD +#endif + + // PARLAY_PACKED: Ask the compiler to pack a struct into less memory by not padding #if defined(__GNUC__) #define PARLAY_PACKED __attribute__((packed)) @@ -61,6 +84,29 @@ namespace parlay { #define PARLAY_UNLIKELY #endif +// Check for exceptions. The standard suggests __cpp_exceptions. Clang/GCC defined __EXCEPTIONS. +// MSVC disables them with _HAS_EXCEPTIONS=0. Might not cover obscure compilers/STLs. +// +// Exceptions can be explicitly disabled in Parlay with PARLAY_NO_EXCEPTIONS. +#if !defined(PARLAY_NO_EXCEPTIONS) && \ + ((defined(__cpp_exceptions) && __cpp_exceptions != 0) || \ + (defined(__EXCEPTIONS)) || \ + (defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS == 1) || \ + (defined(_MSC_VER) && !defined(_HAS_EXCEPTIONS))) +#define PARLAY_EXCEPTIONS_ENABLED +#endif + +template +[[noreturn]] PARLAY_NOINLINE PARLAY_COLD void throw_exception_or_terminate(Args&&... args) { +#if defined(PARLAY_EXCEPTIONS_ENABLED) + throw Exception{std::forward(args)...}; +#else + std::cerr << Exception{std::forward(args)...}.what() << "\n"; + std::terminate(); +#endif +} + + } // namespace parlay #endif // PARLAY_PORTABILITY_H_ diff --git a/include/parlay/scheduler.h b/include/parlay/scheduler.h index fc5e85ab..11cbd598 100644 --- a/include/parlay/scheduler.h +++ b/include/parlay/scheduler.h @@ -345,7 +345,6 @@ class fork_join_scheduler { execute_right(); } else { - //sched->wait_for(right_job, conservative); auto done = [&]() { return right_job.finished(); }; scheduler.wait_until(done, conservative); assert(right_job.finished()); diff --git a/include/parlay/sequence.h b/include/parlay/sequence.h index 0cdfad4e..03bd1903 100644 --- a/include/parlay/sequence.h +++ b/include/parlay/sequence.h @@ -14,6 +14,7 @@ #include #include // IWYU pragma: keep +#include #include #include #include @@ -30,6 +31,7 @@ #include "alloc.h" #include "parallel.h" +#include "portability.h" #include "range.h" #include "slice.h" #include "type_traits.h" // IWYU pragma: keep // for is_trivially_relocatable @@ -175,8 +177,8 @@ class sequence : protected sequence_internal::sequence_base= size()) { - throw std::out_of_range("sequence access out of bounds: length = " + std::to_string(size()) + - ", index = " + std::to_string(i)); + throw_exception_or_terminate( + "sequence access out of bounds: length = " + std::to_string(size()) + ", index = " + std::to_string(i)); } else { return storage.at(i); } @@ -184,8 +186,8 @@ class sequence : protected sequence_internal::sequence_base= size()) { - throw std::out_of_range("sequence access out of bounds: length = " + std::to_string(size()) + - ", index = " + std::to_string(i)); + throw_exception_or_terminate( + "sequence access out of bounds: length = " + std::to_string(size()) + ", index = " + std::to_string(i)); } else { return storage.at(i); } diff --git a/test/test_delayed_sequence.cpp b/test/test_delayed_sequence.cpp index 830709c6..e8beded1 100644 --- a/test/test_delayed_sequence.cpp +++ b/test/test_delayed_sequence.cpp @@ -13,7 +13,7 @@ TEST(TestDelayedSequence, TestConstruction) { struct MyFunctor { std::unique_ptr f; - MyFunctor(int _f) : f(std::make_unique(_f)) { } + explicit MyFunctor(int _f) : f(std::make_unique(_f)) { } MyFunctor(const MyFunctor& other) : f(std::make_unique(*(other.f))) {} MyFunctor& operator=(const MyFunctor& other) { f = std::make_unique(*(other.f)); @@ -180,3 +180,33 @@ TEST(TestDelayedSequence, TestDelayedSequenceOfMutableReferences) { ASSERT_EQ(*v[i], i+1); } } + +#if defined(PARLAY_EXCEPTIONS_ENABLED) + +TEST(TestDelayedSequence, TestAtThrow) { + auto s = parlay::delayed_seq(9, [](int i) -> int { return i; }); + EXPECT_THROW({ s.at(9); }, std::out_of_range); +} + +TEST(TestDelayedSequence, TestAtThrowConst) { + const auto s = parlay::delayed_seq(9, [](int i) -> int { return i; }); + EXPECT_THROW({ s.at(9); }, std::out_of_range); +} + +#else + +TEST(TestDelayedSequenceDeathTest, TestAt) { + ASSERT_EXIT({ + auto s = parlay::delayed_seq(9, [](int i) -> int { return i; }); + s.at(9); + }, testing::KilledBySignal(SIGABRT), "Delayed sequence access out of range"); +} + +TEST(TestDelayedSequenceDeathTest, TestAtConst) { + ASSERT_EXIT({ + const auto s = parlay::delayed_seq(9, [](int i) -> int { return i; }); + s.at(9); + }, testing::KilledBySignal(SIGABRT), "Delayed sequence access out of range"); +} + +#endif // defined(PARLAY_EXCEPTIONS_ENABLED) diff --git a/test/test_sequence.cpp b/test/test_sequence.cpp index fc650468..c22698f6 100644 --- a/test/test_sequence.cpp +++ b/test/test_sequence.cpp @@ -900,5 +900,35 @@ TEST(TestSequence, TestLessThan) { ASSERT_LT(s, s4); } +#if defined(PARLAY_EXCEPTIONS_ENABLED) + +TEST(TestSequence, TestAtThrow) { + auto s = parlay::sequence{1,2,3,4,5,6,7,8,9}; + EXPECT_THROW({ s.at(9); }, std::out_of_range); +} + +TEST(TestSequence, TestAtThrowConst) { + const auto s = parlay::sequence{1,2,3,4,5,6,7,8,9}; + EXPECT_THROW({ s.at(9); }, std::out_of_range); +} + +#else + +TEST(TestSequenceDeathTest, TestAt) { + ASSERT_EXIT({ + auto s = parlay::sequence({1,2,3,4,5,6,7,8,9}); + s.at(9); + }, testing::KilledBySignal(SIGABRT), "sequence access out of bounds"); +} + +TEST(TestSequenceDeathTest, TestAtConst) { + ASSERT_EXIT({ + const auto s = parlay::sequence({1,2,3,4,5,6,7,8,9}); + s.at(9); + }, testing::KilledBySignal(SIGABRT), "sequence access out of bounds"); +} + +#endif // defined(PARLAY_EXCEPTIONS_ENABLED) + // TODO: More thorough tests with custom allocators // to validate allocator usage.