Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
simd.hpp
Go to the documentation of this file.
1#pragma once
2
3// pulp::simd — portable block kernels for DSP.
4//
5// One API for vectorized buffer arithmetic, reductions and FIR correlation.
6// Every kernel exists in up to three backends, compiled into the pulp-simd
7// library when the target supports them:
8//
9// backend::scalar plain sequential loops; the numerical reference and
10// the fallback when no other backend is compiled.
11// backend::highway Google Highway with run-time dispatch to the best
12// instruction set the CPU offers (SSE2 through AVX-512
13// on x86, NEON/SVE on arm), independent of the
14// caller's baseline ISA.
15// backend::accelerate Apple Accelerate (vDSP); Apple platforms only.
16//
17// The unqualified pulp::simd:: names are the backend chosen at configure time
18// by the PULP_SIMD_BACKEND CMake option (default `auto`: Accelerate on Apple,
19// Highway elsewhere, scalar when Highway is unavailable such as MSVC
20// ARM64EC). The choice is a compile-time alias, so a call costs no run-time
21// branch. The explicitly qualified backends stay callable so tests and
22// benchmarks can compare them in one binary when they are compiled.
23//
24// Numerics: the backends agree to rounding, not to the bit. Reductions (sum,
25// dot, sum_squares, correlate, decimate2) accumulate in each backend's own
26// order, and Highway/Accelerate may fuse multiply-adds, so compare against the
27// scalar reference with a tolerance. Each backend is deterministic for a given
28// binary, dispatch target and OS. Accelerate is updated with the OS, so never
29// pin its output bits in a fixture. NaN ordering in maximum/minimum/max_abs/
30// clamp is unspecified.
31//
32// Realtime: no kernel allocates, locks, or throws.
33//
34// Aliasing: dst may equal a source pointer exactly where noted; partial
35// overlap is never supported.
36
37#include <cstddef>
38
39// Kernel signatures, declared once per backend below.
40//
41// add(a, b, dst, n) dst[i] = a[i] + b[i] (dst == a or b ok)
42// mul(a, b, dst, n) dst[i] = a[i] * b[i] (dst == a or b ok)
43// fma(a, b, c, dst, n) dst[i] = a[i] * b[i] + c[i]
44// set(value, dst, n) dst[i] = value
45// scale(a, s, dst, n) dst[i] = a[i] * s (dst == a ok)
46// add_scaled(a, s, dst, n) dst[i] += a[i] * s (dst == a ok)
47// sum(x, n) sum of x[i] (0 when n == 0)
48// maximum(x, n) largest x[i] (0 when n == 0)
49// minimum(x, n) smallest x[i] (0 when n == 0)
50// abs(a, dst, n) dst[i] = |a[i]| (dst == a ok)
51// clamp(a, lo, hi, dst, n) dst[i] = clamp(a[i], lo, hi) (dst == a ok)
52// dot(a, b, n) sum of a[i] * b[i] (0 when n == 0)
53// sum_squares(x, n) sum of x[i] * x[i] (0 when n == 0)
54// max_abs(x, n) largest |x[i]| (0 when n == 0)
55// ramp_mul(x, start, step, dst, n)
56// dst[i] = x[i] * (start + i * step) (dst == x ok)
57// correlate(x, h, y, n_out, taps)
58// y[i] = sum_{k < taps} x[i + k] * h[k]
59// x holds n_out + taps - 1 samples. A FIR filter
60// is correlate() with h the time-reversed impulse
61// response over a linear (unwrapped) history.
62// decimate2(x, h, y, n_out, taps)
63// y[i] = sum_{k < taps} x[2 * i + k] * h[k]
64// x holds 2 * (n_out - 1) + taps samples.
65//
66// y must not overlap x or h in correlate/decimate2.
67
68#define PULP_SIMD_DECLARE_KERNELS(T) \
69 void add(const T* a, const T* b, T* dst, std::size_t count) noexcept; \
70 void mul(const T* a, const T* b, T* dst, std::size_t count) noexcept; \
71 void fma(const T* a, const T* b, const T* c, T* dst, std::size_t count) noexcept; \
72 void set(T value, T* dst, std::size_t count) noexcept; \
73 void scale(const T* a, T scalar, T* dst, std::size_t count) noexcept; \
74 void add_scaled(const T* a, T scalar, T* dst, std::size_t count) noexcept; \
75 T sum(const T* data, std::size_t count) noexcept; \
76 T maximum(const T* data, std::size_t count) noexcept; \
77 T minimum(const T* data, std::size_t count) noexcept; \
78 void abs(const T* a, T* dst, std::size_t count) noexcept; \
79 void clamp(const T* a, T lo, T hi, T* dst, std::size_t count) noexcept; \
80 T dot(const T* a, const T* b, std::size_t count) noexcept; \
81 T sum_squares(const T* data, std::size_t count) noexcept; \
82 T max_abs(const T* data, std::size_t count) noexcept; \
83 void ramp_mul(const T* x, T start, T step, T* dst, std::size_t count) noexcept; \
84 void correlate(const T* x, const T* h, T* y, std::size_t n_out, std::size_t taps) noexcept; \
85 void decimate2(const T* x, const T* h, T* y, std::size_t n_out, std::size_t taps) noexcept;
86
87namespace pulp::simd {
88
89namespace backend {
90
91namespace scalar {
94} // namespace scalar
95
96#if defined(PULP_SIMD_HAS_HIGHWAY)
97namespace highway {
100} // namespace highway
101#endif
102
103#if defined(PULP_SIMD_HAS_ACCELERATE)
104namespace accelerate {
107} // namespace accelerate
108#endif
109
110} // namespace backend
111
112} // namespace pulp::simd
113
114// A translation unit compiled without pulp-simd's usage requirements (a
115// hand-listed source build, e.g. a wasm or VCV Rack plugin that compiles the
116// signal headers directly) gets the scalar reference inline, so it needs only
117// this include directory and no library.
118#if !defined(PULP_SIMD_DEFAULT_BACKEND_ACCELERATE) && \
119 !defined(PULP_SIMD_DEFAULT_BACKEND_HIGHWAY) && !defined(PULP_SIMD_DEFAULT_BACKEND_SCALAR)
122PULP_SIMD_SCALAR_DEFINE(inline, float)
123PULP_SIMD_SCALAR_DEFINE(inline, double)
124} // namespace pulp::simd::backend::inline_scalar
125#endif
126
127namespace pulp::simd {
128
129// The configure-time backend (see PULP_SIMD_BACKEND).
130#if defined(PULP_SIMD_DEFAULT_BACKEND_ACCELERATE)
131namespace active_backend = backend::accelerate;
132inline constexpr const char* active_backend_name = "accelerate";
133#elif defined(PULP_SIMD_DEFAULT_BACKEND_HIGHWAY)
134namespace active_backend = backend::highway;
135inline constexpr const char* active_backend_name = "highway";
136#elif defined(PULP_SIMD_DEFAULT_BACKEND_SCALAR)
137namespace active_backend = backend::scalar;
138inline constexpr const char* active_backend_name = "scalar";
139#else
140namespace active_backend = backend::inline_scalar;
141inline constexpr const char* active_backend_name = "inline-scalar";
142#endif
143
144using active_backend::abs;
145using active_backend::add;
146using active_backend::add_scaled;
147using active_backend::clamp;
148using active_backend::correlate;
149using active_backend::decimate2;
150using active_backend::dot;
151using active_backend::fma;
152using active_backend::max_abs;
153using active_backend::maximum;
154using active_backend::minimum;
155using active_backend::mul;
156using active_backend::ramp_mul;
157using active_backend::scale;
158using active_backend::set;
159using active_backend::sum;
160using active_backend::sum_squares;
161
162#if defined(PULP_SIMD_HAS_HIGHWAY)
164std::size_t float_lanes() noexcept;
165
167std::size_t double_lanes() noexcept;
168#else
169// The scalar and Accelerate backends do not have a Highway dispatch target
170// whose lane width can be queried. Keep the backend-independent API callable
171// for those configurations; one lane is the scalar/reference width.
172inline constexpr std::size_t float_lanes() noexcept {
173 return 1;
174}
175
176inline constexpr std::size_t double_lanes() noexcept {
177 return 1;
178}
179#endif
180
181} // namespace pulp::simd
182
183#undef PULP_SIMD_DECLARE_KERNELS
Definition simd.hpp:121
Definition simd.hpp:91
Definition simd.hpp:89
Definition lanes.hpp:30
constexpr const char * active_backend_name
Definition simd.hpp:141
constexpr std::size_t float_lanes() noexcept
Definition simd.hpp:172
constexpr std::size_t double_lanes() noexcept
Definition simd.hpp:176
STL namespace.
#define PULP_SIMD_SCALAR_DEFINE(SPEC, T)
Definition scalar_kernels.hpp:134
#define PULP_SIMD_DECLARE_KERNELS(T)
Definition simd.hpp:68