Files
pip/3rd/pocketfft/pocketfft-no-exceptions.patch

358 lines
14 KiB
Diff

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?= <andrey@signalmodelling.ru>
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: <what()>' 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 <mutex>
#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 <cstdio>
+ 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 <mutex>
#include <condition_variable>
@@ -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<void *>
((reinterpret_cast<uintptr_t>(ptr) & ~(uintptr_t(align-1))) + uintptr_t(align));
(reinterpret_cast<void**>(res))[-1] = ptr;
@@ -222,7 +246,7 @@ template<typename T> 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<T *>(res);
}
static void dealloc(T *ptr)
@@ -449,7 +473,7 @@ struct util // hack to avoid duplicate symbols
return static_cast<UIntT>(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<UIntT>(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<UIntT>(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<UIntT>(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; i<nthreads; ++i)
{
+#if defined(POCKETFFT_NO_EXCEPTIONS)
+ // Note: without exception handling a failure of std::thread
+ // already results in std::terminate().
+ auto *worker_i = &workers_[i];
+ worker_i->busy_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<nthreads; ++i)
+ {
+ pool.submit(
+ [&f, &counter, i, nthreads] {
+ thread_id() = i;
+ num_threads() = nthreads;
+ f();
+ counter.count_down();
+ });
+ }
+ counter.wait();
+#else
std::exception_ptr ex;
std::mutex ex_mut;
for (size_t i=0; i<nthreads; ++i)
@@ -997,6 +1046,7 @@ void thread_map(size_t nthreads, Func f)
counter.wait();
if (ex)
std::rethrow_exception(ex);
+#endif
}
#endif
@@ -1721,7 +1771,7 @@ template<bool fwd, typename T> 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<typename T> 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<typename T0> 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<typename T0> 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<size_t N> 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<additional) ? myshare : additional);
@@ -3101,7 +3151,7 @@ template<size_t N> class multi_iter
}
void advance(size_t n)
{
- if (rem<n) throw std::runtime_error("underrun");
+ if (rem<n) POCKETFFT_THROW(std::runtime_error("underrun"));
for (size_t i=0; i<n; ++i)
{
p_i[i] = p_ii;
@@ -3627,7 +3677,7 @@ template<typename T> 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<T> ain(data_in, shape, stride_in);
@@ -3645,7 +3695,7 @@ template<typename T> 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<T> ain(data_in, shape, stride_in);
--
2.54.0