358 lines
14 KiB
Diff
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
|
|
|