Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0095a4f
Factor out the policy-free half of RadixN planning
HEnquist Sep 9, 2026
daeea1e
Make the generated prime butterflies reachable from sibling modules
HEnquist Sep 9, 2026
1007be3
Add a vectorised RadixN, and plan it for mixed-factor lengths on NEON
HEnquist Sep 9, 2026
4c01690
Port the RadixN work to SSE
HEnquist Sep 9, 2026
b5d0714
Port the RadixN work to wasm_simd
HEnquist Sep 9, 2026
9ce0980
Match the scalar RadixN dispatch shape
HEnquist Sep 10, 2026
415a29f
Precompute the RadixN transpose indices, and drop the per-layer divide
HEnquist Sep 16, 2026
c8c268e
Move the shared SIMD code into src/simd, and rename RadixNVector to S…
HEnquist Sep 16, 2026
453cd7f
Move split_cross_len to math_utils
HEnquist Sep 16, 2026
4649d7b
Inline the SimdRadixN perform methods, and count the unrolled columns…
HEnquist Sep 16, 2026
cf62565
Gather the cross layer rows with array::from_fn
HEnquist Sep 16, 2026
c089c2a
Merge remote-tracking branch 'upstream/master' into simd_estimating_p…
HEnquist Sep 16, 2026
ba8d179
Bring over the planner tuning harness from the counted cost spike
HEnquist Sep 16, 2026
cfb4185
Estimate the cheapest recipe in the NEON, SSE and wasm_simd planners
HEnquist Sep 16, 2026
6c86535
Drive the tuning harness from the library's planners and cost model
HEnquist Sep 16, 2026
6bbfc80
Rewrite the tuning harness README for the library-driven commands
HEnquist Sep 16, 2026
3418e88
Refit rader_index after the Rader's permutation became a table
HEnquist Sep 16, 2026
283fd6a
Charge a transpose that no longer fits in cache
HEnquist Sep 18, 2026
50edf18
Charge memory that no longer fits in cache, and RadixN per call
HEnquist Sep 18, 2026
f9fb9a3
Raise the out-of-cache transpose weight to 6, and document the model
HEnquist Sep 18, 2026
40c1513
Record what the f32 large-prime losses are, and why a fixed cache siz…
HEnquist Sep 19, 2026
a91c00e
Make rader_index per backend: 2 on NEON, 20 on SSE
HEnquist Sep 19, 2026
8d1ee42
Record the SSE campaign: rader_index is per backend, the rest holds
HEnquist Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ avx = []
sse = []
neon = []
wasm_simd = []
tuning = []


[dependencies]
Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ mod math_utils;
mod plan;
mod twiddles;

// Code shared by the SIMD backends: the `SimdVector` trait, the algorithms written against it, and
// the planner arithmetic that goes with them
#[cfg(any(
all(target_arch = "aarch64", feature = "neon"),
all(target_arch = "x86_64", feature = "sse"),
all(target_arch = "wasm32", feature = "wasm_simd"),
))]
mod simd;

use num_complex::Complex;
use num_traits::Zero;

Expand Down Expand Up @@ -602,5 +611,10 @@ mod wasm_simd {

pub use self::wasm_simd::wasm_simd_planner::FftPlannerWasmSimd;

// Internal support for the planner-tuning tools. Not part of the public API.
#[cfg(feature = "tuning")]
#[doc(hidden)]
pub mod tuning;

#[cfg(test)]
mod test_utils;
60 changes: 60 additions & 0 deletions src/math_utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use num_traits::{One, PrimInt, Zero};

use crate::common::RadixFactor;

pub fn primitive_root(prime: u64) -> Option<u64> {
let test_exponents: Vec<u64> = distinct_prime_factors(prime - 1)
.iter()
Expand Down Expand Up @@ -185,6 +187,26 @@ impl PrimeFactors {
pub fn get_other_factors(&self) -> &[PrimeFactor] {
&self.other_factors
}
/// How many times `value` divides this number, or zero if it isn't a factor at all.
/// `value` is assumed to be prime.
#[allow(unused)]
pub fn get_power_of(&self, value: usize) -> u32 {
match value {
2 => self.power_two,
3 => self.power_three,
_ => self
.other_factors
.iter()
.find_map(|f| {
if f.value == value {
Some(f.count)
} else {
None
}
})
.unwrap_or(0),
}
}
#[allow(unused)]
pub fn is_power_of_three(&self) -> bool {
self.power_three > 0 && self.power_two == 0 && self.other_factors.len() == 0
Expand Down Expand Up @@ -487,6 +509,44 @@ impl PartialFactors {
}
}

/// Split a RadixN cross-FFT length into the layers that make it up, or None if it has a
/// factor no layer can handle.
///
/// Every planner picks its base by its own rules, but once the base is divided out the rest
/// is the same arithmetic for all of them, so this is deliberately free of any policy.
pub fn split_cross_len(mut cross_len: usize) -> Option<Box<[RadixFactor]>> {
let mut factors = Vec::new();
while cross_len % 7 == 0 {
cross_len /= 7;
factors.push(RadixFactor::Factor7);
}
while cross_len % 6 == 0 {
cross_len /= 6;
factors.push(RadixFactor::Factor6);
}
while cross_len % 5 == 0 {
cross_len /= 5;
factors.push(RadixFactor::Factor5);
}
while cross_len % 3 == 0 {
cross_len /= 3;
factors.push(RadixFactor::Factor3);
}
if !cross_len.is_power_of_two() {
return None;
}

// benchmarking suggests that we want to add the 4s *last*, i suspect because 4 is a
// better-than-usual value for the transpose
let cross_bits = cross_len.trailing_zeros();
if cross_bits % 2 == 1 {
factors.push(RadixFactor::Factor2);
}
factors.extend(std::iter::repeat(RadixFactor::Factor4).take(cross_bits as usize / 2));

Some(factors.into_boxed_slice())
}

#[cfg(test)]
mod unit_tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions src/neon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod neon_vector;
pub mod neon_butterflies;
pub mod neon_prime_butterflies;
pub mod neon_radix4;
pub mod neon_radixn;

mod neon_utils;

Expand Down
Loading
Loading