From 700c0ff0ad5f8722c3e6778594264a67e3e6c984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=8B=D1=87=D0=BA=D0=BE=D0=B2=20=D0=90=D0=BD=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D0=B9?= Date: Sun, 18 Oct 2026 18:53:16 +0000 Subject: [PATCH] Add POCKETFFT_NO_EXCEPTIONS option for builds without exception handling pocketfft reports all errors via C++ exceptions (invalid arguments, allocation failure, internal consistency checks), which makes the header unusable in programs compiled with exception handling disabled, e.g. with GCC/Clang -fno-exceptions. Compiling it in such a setup fails at every throw site and at the catch clauses in the thread pool. This adds an opt-in POCKETFFT_NO_EXCEPTIONS macro (following the POCKETFFT_NO_VECTORS / POCKETFFT_NO_MULTITHREADING convention): - every 'throw std::...;' site is wrapped in POCKETFFT_THROW(exception): without the macro this expands to 'throw exception' (behavior is unchanged, exceptions remain catchable); with the macro it calls POCKETFFT_NAMESPACE::detail::handle_fatal_error(), which prints 'pocketfft: ' to stderr and calls abort(); - the two try/catch regions of the thread pool are #if-guarded, since GCC rejects catch clauses outright under -fno-exceptions. All error conditions are unrecoverable (programmer errors or OOM), so abort-with-diagnostic is the appropriate no-exception behavior. Verified with GCC and Clang: demo builds and produces identical results with and without the macro; with the macro, an invalid axis request aborts with 'pocketfft: bad axis number' in both -fno-exceptions and mixed mode; multi-threaded runs (nthreads=4) remain bit-identical to the single-threaded reference. --- README.md | 8 ++++ pocketfft_hdronly.h | 102 +++++++++++++++++++++++++++++++++----------- 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index ff4ef30..062141c 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,14 @@ POCKETFFT_NO_MULTITHREADING:\ if defined, multi-threading will be disabled.\ Default: undefined +POCKETFFT_NO_EXCEPTIONS:\ +if defined, exception handling is disabled: error conditions that would\ +normally throw an exception (invalid arguments, allocation failure,\ +internal consistency errors) instead print a diagnostic message to\ +stderr and call abort(). This allows using pocketfft in programs that\ +are compiled with exception handling disabled (e.g. -fno-exceptions).\ +Default: undefined + POCKETFFT_NAMESPACE:\ if defined, this name will be used as the namespace enclosing all of the pocketfft functionality.\ diff --git a/pocketfft_hdronly.h b/pocketfft_hdronly.h index de6c8d6..e4f53af 100644 --- a/pocketfft_hdronly.h +++ b/pocketfft_hdronly.h @@ -77,6 +77,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #endif +/* + If POCKETFFT_NO_EXCEPTIONS is defined, error conditions that would normally + throw an exception (invalid arguments, allocation failure, internal + consistency errors) instead call handle_fatal_error(), which prints a + diagnostic message to stderr and aborts. This allows using pocketfft in + programs that are compiled with exception handling disabled, e.g. with + GCC's -fno-exceptions. +*/ +#if defined(POCKETFFT_NO_EXCEPTIONS) +# include + namespace POCKETFFT_NAMESPACE { + namespace detail { + [[noreturn]] inline void handle_fatal_error(const std::exception & e) + { + std::fprintf(stderr, "pocketfft: %s\n", e.what()); + std::abort(); + } + } + } +# define POCKETFFT_THROW(exception) POCKETFFT_NAMESPACE::detail::handle_fatal_error(exception) +#else +# define POCKETFFT_THROW(exception) throw exception +#endif + #ifndef POCKETFFT_NO_MULTITHREADING #include #include @@ -178,7 +202,7 @@ inline void *aligned_alloc(size_t align, size_t size) align = std::max(align, sizeof(void*)); // posix_memalign requires align >= sizeof(void*) void *ptr = nullptr; if (posix_memalign(&ptr, align, size) != 0) - throw std::bad_alloc(); + POCKETFFT_THROW(std::bad_alloc()); return ptr; } inline void aligned_dealloc(void *ptr) @@ -190,7 +214,7 @@ inline void *aligned_alloc(size_t align, size_t size) { // aligned_alloc() requires that the requested size is a multiple of "align" void *ptr = ::aligned_alloc(align,(size+align-1)&(~(align-1))); - if (!ptr) throw std::bad_alloc(); + if (!ptr) POCKETFFT_THROW(std::bad_alloc()); return ptr; } inline void aligned_dealloc(void *ptr) @@ -200,7 +224,7 @@ inline void *aligned_alloc(size_t align, size_t size) { align = std::max(align, alignof(max_align_t)); void *ptr = malloc(size+align); - if (!ptr) throw std::bad_alloc(); + if (!ptr) POCKETFFT_THROW(std::bad_alloc()); void *res = reinterpret_cast ((reinterpret_cast(ptr) & ~(uintptr_t(align-1))) + uintptr_t(align)); (reinterpret_cast(res))[-1] = ptr; @@ -222,7 +246,7 @@ template class arr { if (num==0) return nullptr; void *res = malloc(num*sizeof(T)); - if (!res) throw std::bad_alloc(); + if (!res) POCKETFFT_THROW(std::bad_alloc()); return reinterpret_cast(res); } static void dealloc(T *ptr) @@ -449,7 +473,7 @@ struct util // hack to avoid duplicate symbols return static_cast(res); } // Otherwise, this size is ridiculously large, people shouldn't be computing FFTs this large. - throw std::runtime_error("FFT size is too large."); + POCKETFFT_THROW(std::runtime_error("FFT size is too large.")); } UIntT bestfac=2*n; @@ -486,7 +510,7 @@ struct util // hack to avoid duplicate symbols size_t required_factor) { if (required_factor<1) - throw std::runtime_error("required factor must not be 0"); + POCKETFFT_THROW(std::runtime_error("required factor must not be 0")); return good_size_cmplx((n+required_factor-1)/required_factor) * required_factor; } @@ -508,7 +532,7 @@ struct util // hack to avoid duplicate symbols return static_cast(res); } // Otherwise, this size is ridiculously large, people shouldn't be computing FFTs this large. - throw std::runtime_error("FFT size is too large."); + POCKETFFT_THROW(std::runtime_error("FFT size is too large.")); } UIntT bestfac=2*n; @@ -543,7 +567,7 @@ struct util // hack to avoid duplicate symbols size_t required_factor) { if (required_factor<1) - throw std::runtime_error("required factor must not be 0"); + POCKETFFT_THROW(std::runtime_error("required factor must not be 0")); return good_size_real((n+required_factor-1)/required_factor) * required_factor; } @@ -565,7 +589,7 @@ struct util // hack to avoid duplicate symbols return static_cast(res); } // Otherwise, this size is ridiculously large, people shouldn't be computing FFTs this large. - throw std::runtime_error("FFT size is too large."); + POCKETFFT_THROW(std::runtime_error("FFT size is too large.")); } UIntT bestfound = 1; @@ -611,7 +635,7 @@ struct util // hack to avoid duplicate symbols return static_cast(res); } // Otherwise, this size is ridiculously large, people shouldn't be computing FFTs this large. - throw std::runtime_error("FFT size is too large."); + POCKETFFT_THROW(std::runtime_error("FFT size is too large.")); } UIntT bestfound = 1; @@ -649,11 +673,11 @@ struct util // hack to avoid duplicate symbols const stride_t &stride_in, const stride_t &stride_out, bool inplace) { auto ndim = shape.size(); - if (ndim<1) throw std::runtime_error("ndim must be >= 1"); + if (ndim<1) POCKETFFT_THROW(std::runtime_error("ndim must be >= 1")); if ((stride_in.size()!=ndim) || (stride_out.size()!=ndim)) - throw std::runtime_error("stride dimension mismatch"); + POCKETFFT_THROW(std::runtime_error("stride dimension mismatch")); if (inplace && (stride_in!=stride_out)) - throw std::runtime_error("stride mismatch"); + POCKETFFT_THROW(std::runtime_error("stride mismatch")); } static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, @@ -665,8 +689,8 @@ struct util // hack to avoid duplicate symbols shape_t tmp(ndim,0); for (auto ax : axes) { - if (ax>=ndim) throw std::invalid_argument("bad axis number"); - if (++tmp[ax]>1) throw std::invalid_argument("axis specified repeatedly"); + if (ax>=ndim) POCKETFFT_THROW(std::invalid_argument("bad axis number")); + if (++tmp[ax]>1) POCKETFFT_THROW(std::invalid_argument("axis specified repeatedly")); } } @@ -675,7 +699,7 @@ struct util // hack to avoid duplicate symbols size_t axis) { sanity_check(shape, stride_in, stride_out, inplace); - if (axis>=shape.size()) throw std::invalid_argument("bad axis number"); + if (axis>=shape.size()) POCKETFFT_THROW(std::invalid_argument("bad axis number")); } #ifdef POCKETFFT_NO_MULTITHREADING @@ -871,6 +895,17 @@ class thread_pool size_t nthreads=workers_.size(); for (size_t i=0; ibusy_flag.clear(); + worker_i->work = nullptr; + worker_i->thread = std::thread([worker_i, this] + { + worker_i->worker_main(shutdown_, unscheduled_tasks_, overflow_work_); + }); +#else try { auto *worker_i = &workers_[i]; @@ -886,6 +921,7 @@ class thread_pool shutdown_locked(); throw; } +#endif } } @@ -913,7 +949,7 @@ class thread_pool { lock_t lock(mut_); if (shutdown_) - throw std::runtime_error("Work item submitted after shutdown"); + POCKETFFT_THROW(std::runtime_error("Work item submitted after shutdown")); ++unscheduled_tasks_; @@ -977,6 +1013,19 @@ void thread_map(size_t nthreads, Func f) auto & pool = get_pool(); latch counter(nthreads); +#if defined(POCKETFFT_NO_EXCEPTIONS) + for (size_t i=0; i void pass_all(T c[], T0 fct) const POCKETFFT_NOINLINE explicit cfftp(size_t length_) : length(length_) { - if (length==0) throw std::runtime_error("zero-length FFT requested"); + if (length==0) POCKETFFT_THROW(std::runtime_error("zero-length FFT requested")); if (length==1) return; factorize(); mem.resize(twsize()); @@ -2530,7 +2580,7 @@ template void radbg(size_t ido, size_t ip, size_t l1, POCKETFFT_NOINLINE explicit rfftp(size_t length_) : length(length_) { - if (length==0) throw std::runtime_error("zero-length FFT requested"); + if (length==0) POCKETFFT_THROW(std::runtime_error("zero-length FFT requested")); if (length==1) return; factorize(); mem.resize(twsize()); @@ -2655,7 +2705,7 @@ template class pocketfft_c POCKETFFT_NOINLINE explicit pocketfft_c(size_t length) : len(length) { - if (length==0) throw std::runtime_error("zero-length FFT requested"); + if (length==0) POCKETFFT_THROW(std::runtime_error("zero-length FFT requested")); size_t tmp = (length<50) ? 0 : util::largest_prime_factor(length); if (tmp*tmp <= length) { @@ -2692,7 +2742,7 @@ template class pocketfft_r POCKETFFT_NOINLINE explicit pocketfft_r(size_t length) : len(length) { - if (length==0) throw std::runtime_error("zero-length FFT requested"); + if (length==0) POCKETFFT_THROW(std::runtime_error("zero-length FFT requested")); size_t tmp = (length<50) ? 0 : util::largest_prime_factor(length); if (tmp*tmp <= length) { @@ -3077,9 +3127,9 @@ template class multi_iter { auto nshares = threading::num_threads(); if (nshares==1) return; - if (nshares==0) throw std::runtime_error("can't run with zero threads"); + if (nshares==0) POCKETFFT_THROW(std::runtime_error("can't run with zero threads")); auto myshare = threading::thread_id(); - if (myshare>=nshares) throw std::runtime_error("impossible share requested"); + if (myshare>=nshares) POCKETFFT_THROW(std::runtime_error("impossible share requested")); size_t nbase = rem/nshares; size_t additional = rem%nshares; size_t lo = myshare*nbase + ((myshare class multi_iter } void advance(size_t n) { - if (rem void dct(const shape_t &shape, const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, int type, const T *data_in, T *data_out, T fct, bool ortho, size_t nthreads=1) { - if ((type<1) || (type>4)) throw std::invalid_argument("invalid DCT type"); + if ((type<1) || (type>4)) POCKETFFT_THROW(std::invalid_argument("invalid DCT type")); if (util::prod(shape)==0) return; util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); cndarr ain(data_in, shape, stride_in); @@ -3645,7 +3695,7 @@ template void dst(const shape_t &shape, const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, int type, const T *data_in, T *data_out, T fct, bool ortho, size_t nthreads=1) { - if ((type<1) || (type>4)) throw std::invalid_argument("invalid DST type"); + if ((type<1) || (type>4)) POCKETFFT_THROW(std::invalid_argument("invalid DST type")); if (util::prod(shape)==0) return; util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); cndarr ain(data_in, shape, stride_in); -- 2.54.0