Skip to main content

poulpy_cpu_arm/
lib.rs

1//! NEON/ASIMD-accelerated CPU backend for the Poulpy lattice cryptography library.
2//!
3//! This crate provides `FFT64Neon` and `NTT4x30Neon`, high-performance backend
4//! implementations for [`poulpy_hal`] that leverage AArch64 SIMD (NEON / ASIMD)
5//! to accelerate cryptographic operations in fully homomorphic encryption (FHE)
6//! schemes based on Module-LWE.
7//!
8//! # Architecture
9//!
10//! `poulpy_hal` defines a hardware abstraction layer (HAL) via the [`Backend`](poulpy_hal::layouts::Backend)
11//! trait and a family of _open extension point_ (OEP) traits in [`poulpy_hal::oep`]. This crate
12//! implements every OEP trait for `FFT64Neon` and `NTT4x30Neon` using hand-tuned NEON intrinsics
13//! and inline assembly where profiling demonstrates performance benefits over compiler-generated code.
14//!
15//! The internal modules are organized by operation domain:
16//!
17//! | Module          | Domain                                                    |
18//! |-----------------|-----------------------------------------------------------|
19//! | `module`        | Backend handle lifecycle, FFT/NTT table management        |
20//! | `neon`          | Low-level NEON kernels (FFT, NTT, mat-vec, conversions)   |
21//! | `fft64`         | f64 complex-FFT backend wiring (`FFT64Neon`)              |
22//! | `ntt4x30`        | Q120 NTT backend wiring (`NTT4x30Neon`), VMP                |
23//! | `znx`           | Single ring element (`Z[X]/(X^n+1)`) SIMD arithmetic      |
24//! | `vec_znx_big`   | Large-coefficient (i128) ring element vectors             |
25//!
26//! # Scalar types
27//!
28//! - `FFT64Neon`: `ScalarPrep = f64`, `ScalarBig = i64`.
29//! - `NTT4x30Neon`: `ScalarPrep = Q120bScalar` (4 × u64 CRT residues over `Primes30`), `ScalarBig = i128`.
30//!
31//! # CPU requirements
32//!
33//! This backend **requires** AArch64 CPUs. NEON / ASIMD is part of the AArch64
34//! architectural baseline, so no runtime feature detection is needed: any
35//! AArch64 target supports the full SIMD instruction set used by this crate.
36//!
37//! # Compile-time requirements
38//!
39//! NEON / ASIMD is enabled by default on AArch64 targets, so no `RUSTFLAGS`
40//! target-feature flag is required:
41//!
42//! ```text
43//! cargo build --features enable-neon
44//! ```
45//!
46//! If `enable-neon` is enabled but the target architecture is not `aarch64`,
47//! the build fails immediately with a `compile_error!`.
48//!
49//! # Correctness guarantees
50//!
51//! ## Determinism
52//!
53//! Integer / modular operations (`Znx*`, `I128BigOps`, `Ntt*`, `NttDFTExecute`) produce
54//! **bit-identical results** against `poulpy-cpu-ref`. Floating-point operations in FFT
55//! match the reference within ULP tolerance — NEON kernels use FMA (`vfmaq_f64`) where
56//! the scalar reference does not, so individual rounding bits may differ.
57//!
58//! ## Overflow handling
59//!
60//! Integer overflow is **intentional** and managed through bivariate polynomial representation.
61//! The normalization functions (`znx_normalize_*`) use wrapping arithmetic to propagate carries
62//! correctly across limbs in base-2^k representation.
63//!
64//! ## Memory alignment
65//!
66//! All data layouts enforce 64-byte alignment (matching cache line size) as specified by
67//! `poulpy_hal::DEFAULTALIGN`. This alignment enables aligned SIMD loads/stores and
68//! the use of `stnp` non-temporal pair stores in the VMP overwrite-apply path.
69//!
70//! ## Safety invariants
71//!
72//! Many functions are marked `unsafe` and require:
73//! - Target architecture is AArch64 (verified at compile time via `compile_error!`).
74//! - Input slices have matching lengths where documented.
75//! - Input values satisfy documented bounds (e.g., `|x| < 2^50` for IEEE 754 conversions).
76//! - Buffers are properly aligned (enforced by HAL allocators).
77//!
78//! Violating these invariants may result in:
79//! - Undefined behavior (e.g., out-of-bounds memory access).
80//! - Silent incorrect results (e.g., exceeding numeric bounds in FP conversion).
81//! - Panics (in debug mode via assertions, or unconditionally for critical invariants).
82//!
83//! # Performance characteristics
84//!
85//! ## Asymptotic complexity
86//!
87//! - **FFT/IFFT**: O(n log n) for polynomial degree n.
88//! - **Convolution**: O(n log n) via FFT-based approach.
89//! - **VMP**: O(n · nrows · ncols) over a prime-major prepared-matrix layout.
90//! - **Normalization**: O(n) per limb with vectorized digit extraction.
91//!
92//! ## Speedup over reference backend
93//!
94//! Speedups depend on the host micro-architecture and on the operation profile of the
95//! workload. Run the benches in `poulpy-bench` (or the bundled `bench_neon_vs_ref` example)
96//! on the target host for representative numbers. Qualitative trends:
97//!
98//! - **Ring element arithmetic** (add/sub/negate): bandwidth-bound, modest gains.
99//! - **NTT/INTT, mat-vec, and convolution**: noticeable gains from hand-tuned NEON kernels;
100//!   the convolution apply path dispatches to the fused canonical `ntt_mul_bbc_tile4_x2` kernel.
101//! - **VMP** (large degree): the largest gains, scaling with coefficient size;
102//!   uses `stnp` non-temporal stores to avoid cache pollution.
103//!
104//! ## Memory layout
105//!
106//! - **Vectorized storage**: Elements packed in groups of 4 (matching the AVX backend's
107//!   `i64` block stride for cross-backend layout compatibility). NEON's 128-bit registers
108//!   process each block as two `int64x2_t` / `uint64x2_t` per iteration.
109//! - **Tail handling**: Scalar fallback for lengths not divisible by 4.
110//! - **Cache-friendly**: 64-byte alignment ensures single cache line per vector load.
111//! - **Q120 layout**: a q120 vector packs four u64 lanes (one per `Primes30` prime) across
112//!   two NEON registers — `lo` for primes 0/1, `hi` for primes 2/3.
113//!
114//! # Threading and concurrency
115//!
116//! - **`FFT64Neon` / `NTT4x30Neon` are `Send + Sync`**: zero-sized marker types, no internal state.
117//! - **`Module<FFT64Neon>` / `Module<NTT4x30Neon>` are `Send + Sync`**: FFT/NTT tables are immutable after construction.
118//! - **Operations require `&mut` for outputs**: prevents data races at the API level.
119//! - **No internal locking**: all synchronization is the caller's responsibility.
120//!
121//! # Feature flags
122//!
123//! - `enable-neon` (required): opt-in compilation of the backend. Without this feature,
124//!   the crate is an empty shell, allowing the workspace to build on non-aarch64 targets.
125//! - `enable-ckks` (optional): wires the CKKS scheme OEP impls for both backends.
126//!
127//! # Platform support
128//!
129//! - **Required**: AArch64 (Apple Silicon, ARMv8-A and later).
130//! - **Tested under**: native AArch64 hosts.
131//! - **Not supported**: 32-bit ARM, x86, RISC-V, or any other architecture.
132//!
133//! # Threat model
134//!
135//! This library assumes an **"honest but curious"** adversary model:
136//! - **No malicious inputs**: callers are trusted to provide well-formed data within documented bounds.
137//! - **No timing attack mitigation**: operations are not constant-time (performance is prioritized).
138//! - **Memory safety**: bounds are validated to prevent crashes and corruption, but not for security.
139//!
140//! # Usage
141//!
142//! This crate exports two public marker types, `FFT64Neon` and `NTT4x30Neon`, used as type
143//! parameters to the HAL generic types. Application code typically does not import this
144//! crate directly, but instead uses it via `poulpy_core` or `poulpy_bin_fhe` with
145//! runtime backend selection.
146//!
147//! # Versioning and stability
148//!
149//! This crate follows semantic versioning. The public API consists of the `FFT64Neon` and
150//! `NTT4x30Neon` marker types, the `FFT64NeonReimTable` and `ReimFFT(I)Neon` FFT executors,
151//! and their trait implementations from `poulpy_hal::oep`. All other items are
152//! implementation details subject to change without notice.
153
154#[cfg(all(feature = "enable-neon", not(target_arch = "aarch64")))]
155compile_error!("feature `enable-neon` requires target_arch = \"aarch64\".");
156
157#[cfg(all(feature = "enable-neon", feature = "enable-ckks"))]
158mod ckks_impl;
159#[cfg(feature = "enable-neon")]
160mod core_impl;
161#[cfg(feature = "enable-neon")]
162mod fft64;
163#[cfg(feature = "enable-neon")]
164mod hal_impl;
165#[cfg(all(feature = "enable-neon", target_arch = "aarch64"))]
166mod neon;
167#[cfg(feature = "enable-neon")]
168mod ntt4x30;
169#[cfg(all(test, feature = "enable-neon", feature = "enable-ckks"))]
170mod tests;
171
172#[cfg(feature = "enable-neon")]
173pub use fft64::{FFT64Neon, FFT64NeonReimTable, ReimFFTNeon, ReimIFFTNeon};
174#[cfg(feature = "enable-neon")]
175pub use ntt4x30::NTT4x30Neon;
176
177// --- TransferFrom impls ---
178#[cfg(feature = "enable-neon")]
179mod transfer_impls {
180    use poulpy_cpu_ref::{FFT64Ref, NTT4x30Ref};
181    use poulpy_hal::layouts::{Backend, TransferFrom};
182
183    use crate::{FFT64Neon, NTT4x30Neon};
184
185    impl TransferFrom<FFT64Neon> for FFT64Neon {
186        fn transfer_buf(src: &Vec<u8>) -> Vec<u8> {
187            FFT64Neon::from_host_bytes(&FFT64Neon::to_host_bytes(src))
188        }
189    }
190    impl TransferFrom<FFT64Ref> for FFT64Neon {
191        fn transfer_buf(src: &Vec<u8>) -> Vec<u8> {
192            FFT64Neon::from_host_bytes(&FFT64Ref::to_host_bytes(src))
193        }
194    }
195
196    impl TransferFrom<NTT4x30Neon> for NTT4x30Neon {
197        fn transfer_buf(src: &Vec<u8>) -> Vec<u8> {
198            NTT4x30Neon::from_host_bytes(&NTT4x30Neon::to_host_bytes(src))
199        }
200    }
201    impl TransferFrom<NTT4x30Ref> for NTT4x30Neon {
202        fn transfer_buf(src: &Vec<u8>) -> Vec<u8> {
203            NTT4x30Neon::from_host_bytes(&NTT4x30Ref::to_host_bytes(src))
204        }
205    }
206
207    // Cross-family: coefficient-domain buffers are compatible.
208    // Prepared layouts must not be transferred directly; transfer the
209    // non-prepared form and re-prepare on the destination backend.
210    impl TransferFrom<NTT4x30Ref> for FFT64Neon {
211        fn transfer_buf(src: &Vec<u8>) -> Vec<u8> {
212            FFT64Neon::from_host_bytes(&NTT4x30Ref::to_host_bytes(src))
213        }
214    }
215    impl TransferFrom<NTT4x30Neon> for FFT64Neon {
216        fn transfer_buf(src: &Vec<u8>) -> Vec<u8> {
217            FFT64Neon::from_host_bytes(&NTT4x30Neon::to_host_bytes(src))
218        }
219    }
220    impl TransferFrom<FFT64Ref> for NTT4x30Neon {
221        fn transfer_buf(src: &Vec<u8>) -> Vec<u8> {
222            NTT4x30Neon::from_host_bytes(&FFT64Ref::to_host_bytes(src))
223        }
224    }
225    impl TransferFrom<FFT64Neon> for NTT4x30Neon {
226        fn transfer_buf(src: &Vec<u8>) -> Vec<u8> {
227            NTT4x30Neon::from_host_bytes(&FFT64Neon::to_host_bytes(src))
228        }
229    }
230}