Skip to main content

p3_util/transpose/
rectangular.rs

1//! High-performance matrix transpose for generic `Copy` types.
2//!
3//! This module provides an optimized **out-of-place** matrix transpose.
4//!
5//! # Overview
6//!
7//! Matrix transposition converts a row-major matrix into its column-major equivalent.
8//! For a matrix `A` with dimensions `height × width`:
9//!
10//! ```text
11//!     A[i][j] → A^T[j][i]
12//! ```
13//!
14//! In memory (row-major layout), element at position `(row, col)` is stored at:
15//! - **Input**: `input[row * width + col]`
16//! - **Output**: `output[col * height + row]`
17//!
18//! # Architecture-Specific Optimizations
19//!
20//! On **ARM64** (aarch64), this module uses NEON SIMD intrinsics for:
21//! - **4-byte elements** (typical for 32-bit field elements like `MontyField31`, `BabyBear`)
22//!   using a 2-stage butterfly (`vtrn1q_u32`/`vtrn2q_u32` then `vtrn1q_u64`/`vtrn2q_u64`).
23//! - **8-byte elements** (typical for 64-bit field elements like `Goldilocks`)
24//!   using a simpler 1-stage butterfly (`vtrn1q_u64`/`vtrn2q_u64`) on pairs of registers.
25//!
26//! On other architectures, or for other element sizes, it falls back to the portable engine.
27//!
28//! # Key Optimizations
29//!
30//! ## NEON SIMD Registers (128-bit)
31//!
32//! ARM64 NEON provides 32 vector registers, each holding 128 bits.
33//! - For 32-bit (4-byte) elements, each register holds exactly **`BLOCK_SIZE` elements**.
34//! - A `BLOCK_SIZE`×`BLOCK_SIZE` block (`BLOCK_SIZE`^2 elements) fits perfectly in **`BLOCK_SIZE` registers**.
35//!
36//! ```text
37//!     ┌─────────────────────────────────┐
38//!     │  q0 = [ a00, a01, a02, a03 ]    │  ← 128 bits = 4 × 32-bit
39//!     │  q1 = [ a10, a11, a12, a13 ]    │
40//!     │  q2 = [ a20, a21, a22, a23 ]    │
41//!     │  q3 = [ a30, a31, a32, a33 ]    │
42//!     └─────────────────────────────────┘
43//! ```
44//!
45//! ## In-Register Transpose (Butterfly Network)
46//!
47//! We transpose a `BLOCK_SIZE`×`BLOCK_SIZE` block entirely in registers using a 2-stage butterfly:
48//!
49//! **Stage 1**: Swap pairs of 32-bit elements using `TRN1`/`TRN2`
50//! **Stage 2**: Swap pairs of 64-bit elements using `TRN1`/`TRN2` on reinterpreted u64
51//!
52//! ```text
53//!     Input:          After Stage 1:      After Stage 2 (Output):
54//!     ┌─────────────┐ ┌─────────────┐     ┌─────────────┐
55//!     │ a b │ c d   │ │ a e │ c g   │     │ a e │ i m   │
56//!     │ e f │ g h   │ │ b f │ d h   │     │ b f │ j n   │
57//!     │─────┼───────│ │─────┼───────│     │─────┼───────│
58//!     │ i j │ k l   │ │ i m │ k o   │     │ c g │ k o   │
59//!     │ m n │ o p   │ │ j n │ l p   │     │ d h │ l p   │
60//!     └─────────────┘ └─────────────┘     └─────────────┘
61//! ```
62//!
63//! ## Multi-Level Tiling Strategy
64//!
65//! Different strategies for different matrix sizes:
66//! - **Small (<`SMALL_LEN` elements)**: Scalar transpose - fits in L1, no overhead
67//! - **Medium (<`MEDIUM_LEN` elements)**: `TILE_SIZE`×`TILE_SIZE` Tiled - L2-friendly tiles
68//! - **Large (≥`MEDIUM_LEN` elements)**: Recursive + Tiled - Cache-oblivious
69
70#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
71use core::arch::aarch64::*;
72#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
73use core::mem::MaybeUninit;
74#[cfg(all(target_arch = "aarch64", feature = "parallel"))]
75use core::sync::atomic::{AtomicUsize, Ordering};
76
77/// Software prefetch for write (PRFM PSTL1KEEP).
78///
79/// Brings the cache line containing `ptr` into the L1 data cache in exclusive
80/// state, preparing for a subsequent store. This avoids Read-For-Ownership
81/// (RFO) stalls when writing to memory not already in L1.
82#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
83#[inline(always)]
84unsafe fn prefetch_write(ptr: *const u8) {
85    // PRFM PSTL1KEEP: Prefetch for Store, L1 cache, temporal (keep in cache).
86    unsafe {
87        core::arch::asm!(
88            "prfm pstl1keep, [{ptr}]",
89            ptr = in(reg) ptr,
90            options(readonly, nostack, preserves_flags),
91        );
92    }
93}
94
95/// Maximum number of elements for the simple scalar transpose.
96///
97/// For matrices with fewer than `SMALL_LEN` elements (~1KB for 4-byte elements),
98/// the overhead of tiling isn't worth it.
99///
100/// Direct element-by-element copy is faster because:
101/// - The entire matrix fits in L1 cache (32-64KB on most CPUs)
102/// - No tile boundary calculations needed
103/// - Branch prediction works well for small loops
104#[cfg(any(target_arch = "aarch64", test))]
105const SMALL_LEN: usize = 255;
106
107/// Maximum number of elements for the single-level tiled transpose.
108///
109/// For matrices up to `MEDIUM_LEN` elements (~4MB for 4-byte elements), we use
110/// a simple tiled approach with `TILE_SIZE`×`TILE_SIZE` tiles.
111///
112/// This fits comfortably within L2 cache (256KB-512KB) with good spatial locality.
113///
114/// Beyond this threshold, we switch to recursive subdivision to ensure
115/// cache-oblivious behavior for very large matrices.
116#[cfg(any(target_arch = "aarch64", test))]
117const MEDIUM_LEN: usize = 1024 * 1024;
118
119/// Side length of a tile in elements.
120///
121/// We use `TILE_SIZE`×`TILE_SIZE` tiles because:
122/// - `TILE_SIZE`×`TILE_SIZE` × 4 bytes = 1KB per tile, fitting in L1 cache
123/// - `TILE_SIZE` is divisible by `BLOCK_SIZE`, allowing exactly (`TILE_SIZE`/`BLOCK_SIZE`)^2 NEON blocks per tile
124/// - Good balance between tile overhead and cache utilization
125#[cfg(any(target_arch = "aarch64", test))]
126const TILE_SIZE: usize = 16;
127
128/// Maximum dimension for recursive base case.
129///
130/// When recursively subdividing large matrices, we stop when both
131/// dimensions are ≤ `RECURSIVE_LIMIT` elements.
132///
133/// At this point, the sub-matrix (up to `RECURSIVE_LIMIT`×`RECURSIVE_LIMIT` elements)
134/// fits in L2 cache, so we switch to tiled transpose.
135#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
136const RECURSIVE_LIMIT: usize = 128;
137
138/// Minimum number of elements before enabling parallel processing.
139///
140/// Parallel transpose only pays off for large matrices because:
141/// - Thread spawn/join overhead (~1-10μs)
142/// - Cache coherency traffic between cores
143/// - Memory bandwidth becomes the bottleneck, not compute
144///
145/// At `PARALLEL_THRESHOLD` elements, the work per thread is large enough that
146/// parallelism overhead is amortized.
147#[cfg(all(target_arch = "aarch64", feature = "parallel"))]
148const PARALLEL_THRESHOLD: usize = 4 * 1024 * 1024;
149
150/// Transpose a matrix from row-major `input` to row-major `output`.
151///
152/// Given an input matrix with `height` rows and `width` columns, produces
153/// an output matrix with `width` rows and `height` columns.
154///
155/// # Memory Layout
156///
157/// Both input and output are stored in **row-major order**.
158///
159/// ```text
160///     Input (height=2, width=3):       Output (height=3, width=2):
161///
162///     Row 0: [ a, b, c ]               Row 0: [ a, d ]
163///     Row 1: [ d, e, f ]               Row 1: [ b, e ]
164///                                      Row 2: [ c, f ]
165///
166///     Memory: [a, b, c, d, e, f]       Memory: [a, d, b, e, c, f]
167/// ```
168///
169/// # Index Transformation
170///
171/// - Initial element at `input[row * width + col]`,
172/// - Transposed position is `output[col * height + row]`.
173///
174/// # Arguments
175///
176/// * `input` - Source matrix in row-major order
177/// * `output` - Destination buffer in row-major order
178/// * `width` - Number of columns in the input matrix
179/// * `height` - Number of rows in the input matrix
180///
181/// # Panics
182///
183/// Panics if:
184/// - `input.len() != width * height`
185/// - `output.len() != width * height`
186#[inline]
187pub fn transpose<T: Copy + Send + Sync>(
188    input: &[T],
189    output: &mut [T],
190    width: usize,
191    height: usize,
192) {
193    // Input validation
194    assert_eq!(
195        input.len(),
196        width * height,
197        "Input length {} doesn't match width*height = {}",
198        input.len(),
199        width * height
200    );
201    assert_eq!(
202        output.len(),
203        width * height,
204        "Output length {} doesn't match width*height = {}",
205        output.len(),
206        width * height
207    );
208
209    // Handle empty matrices
210    if width == 0 || height == 0 {
211        return;
212    }
213
214    // Architecture dispatch
215    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
216    {
217        // Use NEON-optimized path for 4-byte elements.
218        //
219        // This covers common field types like MontyField31.
220        //
221        // The alignment check matters as much as the size check: a type like
222        // `Complex<Mersenne31>` is 8 bytes but only 4-byte aligned, so it must
223        // not take the 8-byte path below, which assumes u64 alignment.
224        if core::mem::size_of::<T>() == 4 && core::mem::align_of::<T>() == 4 {
225            // SAFETY:
226            // - input/output lengths verified above
227            // - T is 4 bytes and 4-byte aligned, matching u32 size and alignment
228            // - Pointers derived from valid slices
229            unsafe {
230                transpose_neon_4b(
231                    input.as_ptr().cast::<u32>(),
232                    output.as_mut_ptr().cast::<u32>(),
233                    width,
234                    height,
235                );
236            }
237            return;
238        }
239
240        // Use NEON-optimized path for 8-byte elements.
241        //
242        // This covers 64-bit field types like Goldilocks.
243        // A 128-bit NEON register holds 2 u64 elements, so we use
244        // pairs of registers per row and a 1-stage butterfly.
245        if core::mem::size_of::<T>() == 8 && core::mem::align_of::<T>() == 8 {
246            // SAFETY:
247            // - input/output lengths verified above
248            // - T is 8 bytes and 8-byte aligned, matching u64 size and alignment
249            // - Pointers derived from valid slices
250            unsafe {
251                transpose_neon_8b(
252                    input.as_ptr().cast::<u64>(),
253                    output.as_mut_ptr().cast::<u64>(),
254                    width,
255                    height,
256                );
257            }
258            return;
259        }
260    }
261
262    // Fallback for non-ARM64 or unsupported element sizes.
263    super::portable::transpose(input, output, width, height);
264}
265
266/// Top-level NEON transpose dispatcher for 4-byte elements.
267///
268/// Selects the appropriate strategy based on matrix size:
269///
270/// ```text
271///     ┌───────────────────────────────────────────────────────────────────────────────────┐
272///     │                              transpose_neon_4b                                    │
273///     │                                     │                                             │
274///     │    ┌────────────────────────────────┼───────────────────────────────┐             │
275///     │    ▼                                ▼                               ▼             │
276///     │  len < SMALL_LEN     SMALL_LEN ≤ len < MEDIUM_LEN          len ≥ MEDIUM_LEN       │
277///     │    │                                │                               │             │
278///     │    ▼                                ▼                               ▼             │
279///     │  scalar              tiled TILE_SIZE×TILE_SIZE                 recursive          │
280///     │                                     │                        (→ tiled at          │
281///     │                                     │                          leaves)            │
282///     │                                     │                               │             │
283///     │                                     └───────────────┬───────────────┘             │
284///     │                                                     ▼                             │
285///     │                                       parallel (if ≥ PARALLEL_THRESHOLD           │
286///     │                                         and feature enabled)                      │
287///     └───────────────────────────────────────────────────────────────────────────────────┘
288/// ```
289///
290/// # Safety
291///
292/// Caller must ensure `input` and `output` point to valid memory regions
293/// of at least `width * height` elements each.
294#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
295#[inline]
296unsafe fn transpose_neon_4b(input: *const u32, output: *mut u32, width: usize, height: usize) {
297    // Total number of elements in the matrix.
298    let len = width * height;
299
300    #[cfg(feature = "parallel")]
301    {
302        // Parallel path (if enabled and matrix is large enough)
303        if len >= PARALLEL_THRESHOLD {
304            // SAFETY: Caller guarantees valid pointers.
305            unsafe {
306                transpose_neon_4b_parallel(input, output, width, height);
307            }
308            return;
309        }
310    }
311
312    // Sequential path - choose strategy based on size
313    if len <= SMALL_LEN {
314        // Small matrix: simple scalar transpose.
315        //
316        // SAFETY: Caller guarantees valid pointers.
317        unsafe {
318            transpose_small_4b(input, output, width, height);
319        }
320    } else if len <= MEDIUM_LEN {
321        // Medium matrix: single-level `TILE_SIZE`×`TILE_SIZE` tiling.
322        //
323        // SAFETY: Caller guarantees valid pointers.
324        unsafe {
325            transpose_tiled_4b(input, output, width, height);
326        }
327    } else {
328        // Large matrix: recursive subdivision then tiling.
329        //
330        // This is the cache-oblivious approach.
331        // SAFETY: Caller guarantees valid pointers.
332        unsafe {
333            transpose_recursive_4b(input, output, 0, height, 0, width, width, height);
334        }
335    }
336}
337
338/// Parallel transpose for very large matrices (at least `PARALLEL_THRESHOLD` elements).
339///
340/// The work is split into stripes, one per thread, along the longer input dimension.
341///
342/// # Stripe Division
343///
344/// The output holds the transpose in row-major order.
345/// Input element `(r, c)` lands at output index `c * height + r`.
346///
347/// A wide input (more columns than rows) is split by columns.
348/// - Each thread owns a column band over every row.
349/// - Its writes form one contiguous block of output rows.
350///
351/// A tall or square input is split by rows.
352/// - Each thread owns a row band over every column.
353/// - Its input reads stay contiguous, one full row at a time.
354///
355/// ```text
356///     wide input: split by columns        tall input: split by rows
357///     ┌──────┬──────┬──────┐              ┌────────────────────┐
358///     │  t0  │  t1  │  t2  │              │         t0         │
359///     │ cols │ cols │ cols │              ├────────────────────┤
360///     └──────┴──────┴──────┘              │         t1         │
361///                                         ├────────────────────┤
362///                                         │         t2         │
363///                                         └────────────────────┘
364/// ```
365///
366/// # Why the longer dimension
367///
368/// Splitting a wide input by rows would scatter each thread's writes:
369/// - Each thread gets a thin column band, written down the tall output
370///   with stride `height`.
371/// - Scattered stores stall on read-for-ownership and TLB traffic.
372///
373/// Splitting by columns keeps each thread's writes in one contiguous block.
374///
375/// # Data Race Safety
376///
377/// Each thread writes a disjoint output region, so no synchronization is needed.
378/// - A column band maps to a contiguous run of output rows, unique per thread.
379/// - A row band maps to a unique set of output columns.
380///
381/// # Safety
382///
383/// Caller must ensure valid pointers for `width * height` elements.
384#[cfg(all(target_arch = "aarch64", feature = "parallel"))]
385#[inline]
386unsafe fn transpose_neon_4b_parallel(
387    input: *const u32,
388    output: *mut u32,
389    width: usize,
390    height: usize,
391) {
392    use rayon::prelude::*;
393
394    // Number of available threads in the rayon thread pool.
395    let num_threads = rayon::current_num_threads();
396
397    // We use `AtomicUsize` to pass pointer addresses to threads.
398    //
399    // This is safe because:
400    // 1. We only read the addresses (Relaxed ordering is fine)
401    // 2. Each thread writes to a disjoint output region
402    let inp = AtomicUsize::new(input as usize);
403    let out = AtomicUsize::new(output as usize);
404
405    // Split the longer input dimension so each thread's output stays contiguous.
406    //
407    // A wide input is split by columns, a tall or square input by rows.
408    let split_cols = width > height;
409
410    // Length of the dimension being split.
411    let stripe_len = if split_cols { width } else { height };
412
413    // Share handed to each thread.
414    //
415    // The ceiling keeps the final thread from getting an oversized chunk.
416    let stripe_per_thread = stripe_len.div_ceil(num_threads);
417
418    (0..num_threads).into_par_iter().for_each(|thread_idx| {
419        // Half-open stripe `[start, end)` of the split dimension owned here.
420        let start = thread_idx * stripe_per_thread;
421        let end = (start + stripe_per_thread).min(stripe_len);
422
423        // Empty when there are more threads than stripe units.
424        if start < end {
425            // Recover the pointers from their atomic addresses.
426            let input_ptr = inp.load(Ordering::Relaxed) as *const u32;
427            let output_ptr = out.load(Ordering::Relaxed) as *mut u32;
428
429            // Map the stripe to an input region.
430            //
431            // A column stripe spans every row.
432            // A row stripe spans every column.
433            let (row_start, row_end, col_start, col_end) = if split_cols {
434                (0, height, start, end)
435            } else {
436                (start, end, 0, width)
437            };
438
439            // SAFETY:
440            // - Pointers are valid for `width * height` elements (from caller).
441            // - Stripes partition one dimension, so the per-thread output
442            //   regions are disjoint and never aliased.
443            unsafe {
444                transpose_region_tiled_4b(
445                    input_ptr, output_ptr, row_start, row_end, col_start, col_end, width, height,
446                );
447            }
448        }
449    });
450}
451
452/// Simple element-by-element transpose for small matrices.
453///
454/// For matrices with <= `SMALL_LEN` elements, the overhead of tiling isn't justified.
455/// Direct copying with good cache behavior is faster.
456///
457/// # Algorithm
458///
459/// For each position `(x, y)`:
460/// - Read from `input[y * width + x]`
461/// - Write to `output[x * height + y]`
462///
463/// # Loop Order
464///
465/// We iterate `x` in the outer loop to improve **output locality**.
466///
467/// This means consecutive writes go to consecutive memory addresses,
468/// which is better for the write-combining buffers.
469///
470/// # Safety
471///
472/// Caller must ensure valid pointers for `width * height` elements.
473#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
474#[inline]
475unsafe fn transpose_small_4b(input: *const u32, output: *mut u32, width: usize, height: usize) {
476    // Outer loop over columns (output rows).
477    for x in 0..width {
478        // Inner loop over rows (output columns).
479        for y in 0..height {
480            // Input index: row-major position of element (y, x).
481            let input_index = x + y * width;
482
483            // Output index: row-major position of element (x, y).
484            let output_index = y + x * height;
485
486            // SAFETY: Indices are within bounds by loop construction.
487            unsafe {
488                *output.add(output_index) = *input.add(input_index);
489            }
490        }
491    }
492}
493
494/// Tiled transpose using `TILE_SIZE`×`TILE_SIZE` tiles composed of `BLOCK_SIZE`×`BLOCK_SIZE` NEON blocks.
495///
496/// This is important for medium-sized matrices (`SMALL_LEN` to `MEDIUM_LEN` elements).
497///
498/// # Tiling Strategy
499///
500/// - The matrix is divided into `TILE_SIZE`×`TILE_SIZE` tiles.
501/// - Each tile is further divided into (`TILE_SIZE`/`BLOCK_SIZE`)^2 blocks that are transposed using NEON SIMD.
502///
503/// ```text
504///     Matrix (e.g., 64×48):
505///     ┌─────────────────────────────────────────────────────────────────────────────────────┐
506///     │    Tile(0,0)       │    Tile(1,0)       │    Tile(2,0)       │    Tile(3,0)  │rem_x │
507///     │ TILE_SIZE×TILE_SIZE│ TILE_SIZE×TILE_SIZE│ TILE_SIZE×TILE_SIZE│ TILE_SIZE×..  │      │
508///     ├────────────────────┼────────────────────┼────────────────────┼───────────────┼──────┤
509///     │    Tile(0,1)       │    Tile(1,1)       │    Tile(2,1)       │    Tile(3,1)  │rem_x │
510///     │ TILE_SIZE×TILE_SIZE│ TILE_SIZE×TILE_SIZE│ TILE_SIZE×TILE_SIZE│ TILE_SIZE×..  │      │
511///     ├────────────────────┼────────────────────┼────────────────────┼───────────────┼──────┤
512///     │    Tile(0,2)       │    Tile(1,2)       │    Tile(2,2)       │    Tile(3,2)  │rem_x │
513///     │ TILE_SIZE×TILE_SIZE│ TILE_SIZE×TILE_SIZE│ TILE_SIZE×TILE_SIZE│ TILE_SIZE×..  │      │
514///     └────────────────────┴────────────────────┴────────────────────┴───────────────┴──────┘
515///                                                                                     rem_y
516/// ```
517///
518/// Remainders (`rem_x`, `rem_y`) are handled with scalar transpose.
519///
520/// # Safety
521///
522/// Caller must ensure valid pointers for `width * height` elements.
523#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
524#[inline]
525unsafe fn transpose_tiled_4b(input: *const u32, output: *mut u32, width: usize, height: usize) {
526    // Compute tile counts and remainders
527
528    // Number of complete `TILE_SIZE`×`TILE_SIZE` tiles in each dimension.
529    let x_tile_count = width / TILE_SIZE;
530    let y_tile_count = height / TILE_SIZE;
531
532    // Leftover elements that don't fit in complete tiles.
533    let remainder_x = width - x_tile_count * TILE_SIZE;
534    let remainder_y = height - y_tile_count * TILE_SIZE;
535
536    // Process complete `TILE_SIZE`×`TILE_SIZE` tiles
537
538    // Iterate over tile rows.
539    for y_tile in 0..y_tile_count {
540        // Iterate over tile columns.
541        for x_tile in 0..x_tile_count {
542            // Top-left corner of this tile.
543            let x_start = x_tile * TILE_SIZE;
544            let y_start = y_tile * TILE_SIZE;
545
546            // Transpose this `TILE_SIZE`×`TILE_SIZE` tile
547            //
548            // SAFETY: Tile coordinates are within bounds.
549            unsafe {
550                transpose_tile_16x16_neon(input, output, width, height, x_start, y_start);
551            }
552        }
553
554        // Handle partial column tiles (right edge)
555
556        // Elements in columns [x_tile_count * TILE_SIZE, width) don't form a complete tile.
557        // Use scalar transpose for these.
558        if remainder_x > 0 {
559            // SAFETY: Coordinates are within bounds.
560            unsafe {
561                transpose_block_scalar(
562                    input,
563                    output,
564                    width,
565                    height,
566                    x_tile_count * TILE_SIZE, // x_start
567                    y_tile * TILE_SIZE,       // y_start
568                    remainder_x,              // block_width
569                    TILE_SIZE,                // block_height
570                );
571            }
572        }
573    }
574
575    // Handle partial row tiles (bottom edge)
576
577    // Elements in rows [y_tile_count * TILE_SIZE, height) don't form complete tiles.
578    if remainder_y > 0 {
579        // Process bottom edge tiles (except corner).
580        for x_tile in 0..x_tile_count {
581            // SAFETY: Coordinates are within bounds.
582            unsafe {
583                transpose_block_scalar(
584                    input,
585                    output,
586                    width,
587                    height,
588                    x_tile * TILE_SIZE,       // x_start
589                    y_tile_count * TILE_SIZE, // y_start
590                    TILE_SIZE,                // block_width
591                    remainder_y,              // block_height
592                );
593            }
594        }
595
596        // Handle corner block (bottom-right)
597
598        // The corner block is the intersection of right and bottom remainders.
599        if remainder_x > 0 {
600            // SAFETY: Coordinates are within bounds.
601            unsafe {
602                transpose_block_scalar(
603                    input,
604                    output,
605                    width,
606                    height,
607                    x_tile_count * TILE_SIZE, // x_start
608                    y_tile_count * TILE_SIZE, // y_start
609                    remainder_x,              // block_width
610                    remainder_y,              // block_height
611                );
612            }
613        }
614    }
615}
616
617/// Recursive cache-oblivious transpose for large matrices.
618///
619/// This algorithm recursively subdivides the matrix until sub-blocks fit
620/// in cache, then uses tiled transpose on the leaves.
621///
622/// # Cache-Oblivious Design
623///
624/// The key insight is that we don't need to know cache sizes explicitly.
625///
626/// By recursively halving the problem, we eventually reach a size that
627/// fits in any level of cache (L1, L2, or L3).
628///
629/// # Recursion Pattern
630///
631/// At each level, we split along the **longer dimension**:
632///
633/// ```text
634///     Wide matrix (cols > rows):      Tall matrix (rows ≥ cols):
635///     Split vertically                Split horizontally
636///
637///     ┌─────────┬─────────┐           ┌───────────────────┐
638///     │         │         │           │                   │
639///     │  Left   │  Right  │           │       Top         │
640///     │         │         │           │                   │
641///     │         │         │           ├───────────────────┤
642///     │         │         │           │                   │
643///     └─────────┴─────────┘           │      Bottom       │
644///                                     │                   │
645///                                     └───────────────────┘
646/// ```
647///
648/// # Base Case
649///
650/// We stop recursing when both dimensions are ≤ `RECURSIVE_LIMIT` elements (or ≤ 2,
651/// which is a degenerate case). At this point, the sub-matrix fits in
652/// L2 cache (~64KB for `RECURSIVE_LIMIT`×`RECURSIVE_LIMIT`×4 bytes), so we use tiled transpose.
653///
654/// # Parameters
655///
656/// The function uses coordinate ranges rather than creating sub-arrays:
657/// - `row_start..row_end`: Row range in the original matrix
658/// - `col_start..col_end`: Column range in the original matrix
659/// - `total_cols`, `total_rows`: Original matrix dimensions (for stride calculations)
660///
661/// # Safety
662///
663/// Caller must ensure valid pointers and that coordinate ranges are within bounds.
664#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
665#[allow(clippy::too_many_arguments)]
666unsafe fn transpose_recursive_4b(
667    input: *const u32,
668    output: *mut u32,
669    row_start: usize,
670    row_end: usize,
671    col_start: usize,
672    col_end: usize,
673    total_cols: usize,
674    total_rows: usize,
675) {
676    // Compute sub-matrix dimensions
677    let nbr_rows = row_end - row_start;
678    let nbr_cols = col_end - col_start;
679
680    // Base case: small enough to use tiled transpose
681
682    // Stop recursing when:
683    // 1. Both dimensions ≤ RECURSIVE_LIMIT (fits in cache), OR
684    // 2. Either dimension ≤ 2 (degenerate case, no benefit from recursion)
685    if (nbr_rows <= RECURSIVE_LIMIT && nbr_cols <= RECURSIVE_LIMIT)
686        || nbr_rows <= 2
687        || nbr_cols <= 2
688    {
689        // SAFETY: Caller ensures valid pointers and bounds.
690        unsafe {
691            transpose_region_tiled_4b(
692                input, output, row_start, row_end, col_start, col_end, total_cols, total_rows,
693            );
694        }
695        return;
696    }
697
698    // Recursive case: split along the longer dimension
699    if nbr_rows >= nbr_cols {
700        // Split horizontally (by rows)
701
702        // Midpoint of the row range.
703        let mid = row_start + (nbr_rows / 2);
704
705        // Recurse on top half.
706        // SAFETY: mid is within [row_start, row_end].
707        unsafe {
708            transpose_recursive_4b(
709                input, output, row_start, mid, col_start, col_end, total_cols, total_rows,
710            );
711        }
712
713        // Recurse on bottom half.
714        // SAFETY: mid is within [row_start, row_end].
715        unsafe {
716            transpose_recursive_4b(
717                input, output, mid, row_end, col_start, col_end, total_cols, total_rows,
718            );
719        }
720    } else {
721        // Split vertically (by columns)
722
723        // Midpoint of the column range.
724        let mid = col_start + (nbr_cols / 2);
725
726        // Recurse on left half.
727        // SAFETY: mid is within [col_start, col_end].
728        unsafe {
729            transpose_recursive_4b(
730                input, output, row_start, row_end, col_start, mid, total_cols, total_rows,
731            );
732        }
733
734        // Recurse on right half.
735        // SAFETY: mid is within [col_start, col_end].
736        unsafe {
737            transpose_recursive_4b(
738                input, output, row_start, row_end, mid, col_end, total_cols, total_rows,
739            );
740        }
741    }
742}
743
744/// Tiled transpose for a rectangular region within a larger matrix.
745///
746/// It operates on a sub-region defined by coordinate ranges.
747///
748/// Used as the base case of recursive transpose and for parallel stripe processing.
749///
750/// # Coordinate System
751///
752/// ```text
753///     Original matrix (total_cols × total_rows):
754///     ┌─────────────────────────────────────────────────────────┐
755///     │                                                         │
756///     │    (col_start, row_start)                               │
757///     │           ┌─────────────────────┐                       │
758///     │           │                     │                       │
759///     │           │       Region to     │                       │
760///     │           │       transpose     │                       │
761///     │           │                     │                       │
762///     │           └─────────────────────┘                       │
763///     │                    (col_end, row_end)                   │
764///     │                                                         │
765///     └─────────────────────────────────────────────────────────┘
766/// ```
767///
768/// # Safety
769///
770/// Caller must ensure:
771/// - Valid pointers for `total_cols * total_rows` elements
772/// - `row_start < row_end <= total_rows`
773/// - `col_start < col_end <= total_cols`
774#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
775#[inline]
776#[allow(clippy::too_many_arguments)]
777unsafe fn transpose_region_tiled_4b(
778    input: *const u32,
779    output: *mut u32,
780    row_start: usize,
781    row_end: usize,
782    col_start: usize,
783    col_end: usize,
784    total_cols: usize,
785    total_rows: usize,
786) {
787    // Compute region dimensions and tile counts
788
789    // Dimensions of the region to transpose.
790    let nbr_cols = col_end - col_start;
791    let nbr_rows = row_end - row_start;
792
793    // Number of complete `TILE_SIZE`×`TILE_SIZE` tiles in each dimension.
794    let x_tile_count = nbr_cols / TILE_SIZE;
795    let y_tile_count = nbr_rows / TILE_SIZE;
796
797    // Leftover elements that don't fit in complete tiles.
798    let remainder_x = nbr_cols - x_tile_count * TILE_SIZE;
799    let remainder_y = nbr_rows - y_tile_count * TILE_SIZE;
800
801    // Process complete `TILE_SIZE`×`TILE_SIZE` tiles
802    for y_tile in 0..y_tile_count {
803        for x_tile in 0..x_tile_count {
804            // Coordinates of this tile's top-left corner in the original matrix.
805            let col = col_start + x_tile * TILE_SIZE;
806            let row = row_start + y_tile * TILE_SIZE;
807
808            // SAFETY: Tile coordinates are within the region bounds.
809            // Uses the buffered tile function: for large matrices the output
810            // is likely in L3/RAM, so L1 buffering + write prefetching avoids
811            // RFO stalls on scattered output writes.
812            unsafe {
813                transpose_tile_16x16_neon_buffered(input, output, total_cols, total_rows, col, row);
814            }
815        }
816
817        // Handle partial column tiles (right edge of region)
818        if remainder_x > 0 {
819            // SAFETY: Coordinates are within region bounds.
820            unsafe {
821                transpose_block_scalar(
822                    input,
823                    output,
824                    total_cols,
825                    total_rows,
826                    col_start + x_tile_count * TILE_SIZE, // x_start
827                    row_start + y_tile * TILE_SIZE,       // y_start
828                    remainder_x,                          // block_width
829                    TILE_SIZE,                            // block_height
830                );
831            }
832        }
833    }
834
835    // Handle partial row tiles (bottom edge of region)
836    if remainder_y > 0 {
837        for x_tile in 0..x_tile_count {
838            // SAFETY: Coordinates are within region bounds.
839            unsafe {
840                transpose_block_scalar(
841                    input,
842                    output,
843                    total_cols,
844                    total_rows,
845                    col_start + x_tile * TILE_SIZE,       // x_start
846                    row_start + y_tile_count * TILE_SIZE, // y_start
847                    TILE_SIZE,                            // block_width
848                    remainder_y,                          // block_height
849                );
850            }
851        }
852
853        // Handle corner block (bottom-right of region)
854        if remainder_x > 0 {
855            // SAFETY: Coordinates are within region bounds.
856            unsafe {
857                transpose_block_scalar(
858                    input,
859                    output,
860                    total_cols,
861                    total_rows,
862                    col_start + x_tile_count * TILE_SIZE, // x_start
863                    row_start + y_tile_count * TILE_SIZE, // y_start
864                    remainder_x,                          // block_width
865                    remainder_y,                          // block_height
866                );
867            }
868        }
869    }
870}
871
872/// Transpose a complete 16×16 tile using NEON SIMD (direct-to-output).
873///
874/// A 16×16 tile is processed as a 4×4 grid of 4×4 NEON blocks.
875/// This function is fully unrolled for maximum performance.
876///
877/// Used by the **medium tiled path** where the output likely fits in L2 cache
878/// and the overhead of L1 buffering isn't justified.
879///
880/// # Safety
881///
882/// Caller must ensure:
883/// - Valid pointers for the full matrix
884#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
885/// - `y_start + 16 <= height`
886#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
887#[inline]
888unsafe fn transpose_tile_16x16_neon(
889    input: *const u32,
890    output: *mut u32,
891    width: usize,
892    height: usize,
893    x_start: usize,
894    y_start: usize,
895) {
896    unsafe {
897        // Block Row 0 (input rows y_start..y_start+4)
898        let inp = input.add(y_start * width + x_start);
899        let out = output.add(x_start * height + y_start);
900        transpose_4x4_neon(inp, out, width, height);
901        transpose_4x4_neon(inp.add(4), out.add(4 * height), width, height);
902        transpose_4x4_neon(inp.add(8), out.add(8 * height), width, height);
903        transpose_4x4_neon(inp.add(12), out.add(12 * height), width, height);
904
905        // Block Row 1 (input rows y_start+4..y_start+8)
906        let inp = input.add((y_start + 4) * width + x_start);
907        let out = output.add(x_start * height + y_start + 4);
908        transpose_4x4_neon(inp, out, width, height);
909        transpose_4x4_neon(inp.add(4), out.add(4 * height), width, height);
910        transpose_4x4_neon(inp.add(8), out.add(8 * height), width, height);
911        transpose_4x4_neon(inp.add(12), out.add(12 * height), width, height);
912
913        // Block Row 2 (input rows y_start+8..y_start+12)
914        let inp = input.add((y_start + 8) * width + x_start);
915        let out = output.add(x_start * height + y_start + 8);
916        transpose_4x4_neon(inp, out, width, height);
917        transpose_4x4_neon(inp.add(4), out.add(4 * height), width, height);
918        transpose_4x4_neon(inp.add(8), out.add(8 * height), width, height);
919        transpose_4x4_neon(inp.add(12), out.add(12 * height), width, height);
920
921        // Block Row 3 (input rows y_start+12..y_start+16)
922        let inp = input.add((y_start + 12) * width + x_start);
923        let out = output.add(x_start * height + y_start + 12);
924        transpose_4x4_neon(inp, out, width, height);
925        transpose_4x4_neon(inp.add(4), out.add(4 * height), width, height);
926        transpose_4x4_neon(inp.add(8), out.add(8 * height), width, height);
927        transpose_4x4_neon(inp.add(12), out.add(12 * height), width, height);
928    }
929}
930
931/// Transpose a complete 16×16 tile using NEON SIMD with L1 buffering.
932///
933/// Same grid of 4×4 NEON blocks, but transposed into a stack-allocated buffer
934/// first, then flushed to the output with write prefetching (`PRFM PSTL1KEEP`).
935///
936/// Used by the **recursive/parallel path** for large matrices (≥ `MEDIUM_LEN`)
937/// where the output is in L3/RAM and direct scattered writes would stall on
938/// Read-For-Ownership (RFO) cache line fetches.
939///
940/// # Safety
941///
942/// Caller must ensure:
943/// - Valid pointers for the full matrix
944/// - `x_start + 16 <= width`
945/// - `y_start + 16 <= height`
946#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
947#[inline]
948unsafe fn transpose_tile_16x16_neon_buffered(
949    input: *const u32,
950    output: *mut u32,
951    width: usize,
952    height: usize,
953    x_start: usize,
954    y_start: usize,
955) {
956    // Stack buffer for L1-hot transpose (1 KB for u32).
957    // MaybeUninit avoids unnecessary zero-initialization; every element
958    // is written by the NEON blocks before the copy reads it.
959    let mut buffer = MaybeUninit::<[u32; TILE_SIZE * TILE_SIZE]>::uninit();
960    let buf = buffer.as_mut_ptr().cast::<u32>();
961
962    unsafe {
963        // Transpose 4×4 grid of NEON blocks into the buffer.
964        // Buffer layout: buf[col * TILE_SIZE + row] = transposed element.
965        // Buffer write stride is TILE_SIZE (contiguous in L1) vs. `height` (scattered).
966
967        // Block Row 0 (input rows y_start..y_start+4)
968        let inp = input.add(y_start * width + x_start);
969        transpose_4x4_neon(inp, buf, width, TILE_SIZE);
970        transpose_4x4_neon(inp.add(4), buf.add(4 * TILE_SIZE), width, TILE_SIZE);
971        transpose_4x4_neon(inp.add(8), buf.add(8 * TILE_SIZE), width, TILE_SIZE);
972        transpose_4x4_neon(inp.add(12), buf.add(12 * TILE_SIZE), width, TILE_SIZE);
973
974        // Block Row 1 (input rows y_start+4..y_start+8)
975        let inp = input.add((y_start + 4) * width + x_start);
976        transpose_4x4_neon(inp, buf.add(4), width, TILE_SIZE);
977        transpose_4x4_neon(inp.add(4), buf.add(4 * TILE_SIZE + 4), width, TILE_SIZE);
978        transpose_4x4_neon(inp.add(8), buf.add(8 * TILE_SIZE + 4), width, TILE_SIZE);
979        transpose_4x4_neon(inp.add(12), buf.add(12 * TILE_SIZE + 4), width, TILE_SIZE);
980
981        // Block Row 2 (input rows y_start+8..y_start+12)
982        let inp = input.add((y_start + 8) * width + x_start);
983        transpose_4x4_neon(inp, buf.add(8), width, TILE_SIZE);
984        transpose_4x4_neon(inp.add(4), buf.add(4 * TILE_SIZE + 8), width, TILE_SIZE);
985        transpose_4x4_neon(inp.add(8), buf.add(8 * TILE_SIZE + 8), width, TILE_SIZE);
986        transpose_4x4_neon(inp.add(12), buf.add(12 * TILE_SIZE + 8), width, TILE_SIZE);
987
988        // Block Row 3 (input rows y_start+12..y_start+16)
989        let inp = input.add((y_start + 12) * width + x_start);
990        transpose_4x4_neon(inp, buf.add(12), width, TILE_SIZE);
991        transpose_4x4_neon(inp.add(4), buf.add(4 * TILE_SIZE + 12), width, TILE_SIZE);
992        transpose_4x4_neon(inp.add(8), buf.add(8 * TILE_SIZE + 12), width, TILE_SIZE);
993        transpose_4x4_neon(inp.add(12), buf.add(12 * TILE_SIZE + 12), width, TILE_SIZE);
994
995        // Flush buffer to output with write prefetching.
996        // Each iteration copies TILE_SIZE u32s (64 bytes = 1 cache line) from the
997        // L1-hot buffer to one output row. Prefetch brings the next output cache
998        // line into exclusive state, avoiding RFO stalls.
999        prefetch_write(output.add(x_start * height + y_start) as *const u8);
1000        for c in 0..TILE_SIZE {
1001            if c + 1 < TILE_SIZE {
1002                prefetch_write(output.add((x_start + c + 1) * height + y_start) as *const u8);
1003            }
1004            core::ptr::copy_nonoverlapping(
1005                buf.add(c * TILE_SIZE),
1006                output.add((x_start + c) * height + y_start),
1007                TILE_SIZE,
1008            );
1009        }
1010    }
1011}
1012
1013/// Scalar transpose for an arbitrary rectangular block.
1014///
1015/// Used for handling edge cases where dimensions don't align to tile boundaries.
1016/// Falls back to simple element-by-element copying.
1017///
1018/// # When Used
1019///
1020/// - Right edge: `block_width < TILE_SIZE`
1021/// - Bottom edge: `block_height < TILE_SIZE`
1022/// - Bottom-right corner: both dimensions < `TILE_SIZE`
1023///
1024/// # Safety
1025///
1026#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1027/// - Valid pointers for the full matrix
1028/// - `x_start + block_width <= width`
1029/// - `y_start + block_height <= height`
1030#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1031#[inline]
1032#[allow(clippy::too_many_arguments)]
1033unsafe fn transpose_block_scalar(
1034    input: *const u32,
1035    output: *mut u32,
1036    width: usize,
1037    height: usize,
1038    x_start: usize,
1039    y_start: usize,
1040    block_width: usize,
1041    block_height: usize,
1042) {
1043    // Iterate over block columns (becomes output rows).
1044    for inner_x in 0..block_width {
1045        // Iterate over block rows (becomes output columns).
1046        for inner_y in 0..block_height {
1047            // Absolute coordinates in the original matrix.
1048            let x = x_start + inner_x;
1049            let y = y_start + inner_y;
1050
1051            // Input index: row-major position of (y, x).
1052            let input_index = x + y * width;
1053
1054            // Output index: row-major position of (x, y) in transposed matrix.
1055            let output_index = y + x * height;
1056
1057            // SAFETY: Indices are within bounds by construction.
1058            unsafe {
1059                *output.add(output_index) = *input.add(input_index);
1060            }
1061        }
1062    }
1063}
1064
1065/// Transpose a 4×4 block of 32-bit elements using NEON SIMD.
1066///
1067/// This is the fundamental building block of the entire transpose algorithm.
1068///
1069/// It transposes a 4×4 block entirely within NEON registers
1070/// using a two-stage butterfly network.
1071///
1072/// # Memory Layout
1073///
1074/// Input (4 rows, stride = `src_stride`):
1075/// ```text
1076///     src + 0*stride:  [ a00, a01, a02, a03 ]  → q0
1077///     src + 1*stride:  [ a10, a11, a12, a13 ]  → q1
1078///     src + 2*stride:  [ a20, a21, a22, a23 ]  → q2
1079///     src + 3*stride:  [ a30, a31, a32, a33 ]  → q3
1080/// ```
1081///
1082/// Output (4 rows, stride = `dst_stride`):
1083/// ```text
1084///     dst + 0*stride:  [ a00, a10, a20, a30 ]  ← r0
1085///     dst + 1*stride:  [ a01, a11, a21, a31 ]  ← r1
1086///     dst + 2*stride:  [ a02, a12, a22, a32 ]  ← r2
1087///     dst + 3*stride:  [ a03, a13, a23, a33 ]  ← r3
1088/// ```
1089///
1090/// # Butterfly Network Algorithm
1091///
1092/// The transpose is performed in two stages using `TRN1`/`TRN2` instructions:
1093///
1094/// ## Stage 1: 32-bit Transpose
1095///
1096/// - `TRN1` takes **even-indexed** elements,
1097/// - `TRN2` takes **odd-indexed** elements.
1098///
1099/// ```text
1100///     TRN1(q0, q1) = [ a00, a10, a02, a12 ]  (even indices: 0, 2)
1101///     TRN2(q0, q1) = [ a01, a11, a03, a13 ]  (odd indices: 1, 3)
1102///     TRN1(q2, q3) = [ a20, a30, a22, a32 ]
1103///     TRN2(q2, q3) = [ a21, a31, a23, a33 ]
1104/// ```
1105///
1106/// ## Stage 2: 64-bit Transpose
1107///
1108/// Reinterpret as 64-bit elements and transpose again:
1109///
1110/// ```text
1111///     TRN1_64(t0, t2) = [ a00, a10 | a20, a30 ]  → r0
1112///     TRN2_64(t0, t2) = [ a02, a12 | a22, a32 ]  → r2
1113///     TRN1_64(t1, t3) = [ a01, a11 | a21, a31 ]  → r1
1114///     TRN2_64(t1, t3) = [ a03, a13 | a23, a33 ]  → r3
1115/// ```
1116///
1117/// # Explanations
1118///
1119/// The butterfly network swaps elements at progressively larger distances:
1120/// - Stage 1: Swaps elements 1 apart (within 64-bit pairs)
1121/// - Stage 2: Swaps elements 2 apart (between 64-bit halves)
1122///
1123/// This is analogous to the bit-reversal pattern in FFT algorithms.
1124///
1125/// # Performance
1126///
1127/// - **4 loads** (vld1q_u32): 4 cycles
1128/// - **8 permutes** (vtrn): ~8 cycles (pipelined)
1129/// - **4 stores** (vst1q_u32): 4 cycles
1130/// - **Total**: ~16 cycles for 16 elements = **1 cycle/element**
1131///
1132/// # Safety
1133#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1134/// Caller must ensure:
1135/// - `src` is valid for reading 4 rows of `src_stride` elements each
1136/// - `dst` is valid for writing 4 rows of `dst_stride` elements each
1137/// - The first 4 elements of each row are accessible
1138#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1139#[inline(always)]
1140unsafe fn transpose_4x4_neon(src: *const u32, dst: *mut u32, src_stride: usize, dst_stride: usize) {
1141    unsafe {
1142        // Phase 1: Load 4 rows into NEON registers
1143        //
1144        // Each vld1q_u32 loads 4 consecutive u32s (16 bytes = 128 bits).
1145        // Total: 64 bytes = one cache line on most ARM64 CPUs.
1146
1147        // Row 0: [a00, a01, a02, a03]
1148        let q0 = vld1q_u32(src);
1149        // Row 1: [a10, a11, a12, a13]
1150        let q1 = vld1q_u32(src.add(src_stride));
1151        // Row 2: [a20, a21, a22, a23]
1152        let q2 = vld1q_u32(src.add(2 * src_stride));
1153        // Row 3: [a30, a31, a32, a33]
1154        let q3 = vld1q_u32(src.add(3 * src_stride));
1155
1156        // Phase 2: Stage 1 - Transpose 2×2 blocks of 32-bit elements
1157        //
1158        // vtrn1q_u32(a, b): Takes elements at even indices from a and b
1159        // - Result: [a[0], b[0], a[2], b[2]]
1160        //
1161        // vtrn2q_u32(a, b): Takes elements at odd indices from a and b
1162        // - Result: [a[1], b[1], a[3], b[3]]
1163
1164        let t0_0 = vtrn1q_u32(q0, q1); // [a00, a10, a02, a12]
1165        let t0_1 = vtrn2q_u32(q0, q1); // [a01, a11, a03, a13]
1166        let t0_2 = vtrn1q_u32(q2, q3); // [a20, a30, a22, a32]
1167        let t0_3 = vtrn2q_u32(q2, q3); // [a21, a31, a23, a33]
1168
1169        // Phase 3: Stage 2 - Transpose 2×2 blocks of 64-bit elements
1170        //
1171        // Reinterpret u32x4 as u64x2, then transpose.
1172        // This swaps the 64-bit halves of the vectors.
1173        //
1174        // vtrn1q_u64(a, b): [a.lo, b.lo]
1175        // vtrn2q_u64(a, b): [a.hi, b.hi]
1176
1177        // r0 = [a00, a10, a20, a30] (column 0 of input → row 0 of output)
1178        let r0 = vreinterpretq_u32_u64(vtrn1q_u64(
1179            vreinterpretq_u64_u32(t0_0),
1180            vreinterpretq_u64_u32(t0_2),
1181        ));
1182
1183        // r2 = [a02, a12, a22, a32] (column 2 of input → row 2 of output)
1184        let r2 = vreinterpretq_u32_u64(vtrn2q_u64(
1185            vreinterpretq_u64_u32(t0_0),
1186            vreinterpretq_u64_u32(t0_2),
1187        ));
1188
1189        // r1 = [a01, a11, a21, a31] (column 1 of input → row 1 of output)
1190        let r1 = vreinterpretq_u32_u64(vtrn1q_u64(
1191            vreinterpretq_u64_u32(t0_1),
1192            vreinterpretq_u64_u32(t0_3),
1193        ));
1194
1195        // r3 = [a03, a13, a23, a33] (column 3 of input → row 3 of output)
1196        let r3 = vreinterpretq_u32_u64(vtrn2q_u64(
1197            vreinterpretq_u64_u32(t0_1),
1198            vreinterpretq_u64_u32(t0_3),
1199        ));
1200
1201        // Phase 4: Store 4 transposed rows
1202        //
1203        // Store row 0 of output
1204        vst1q_u32(dst, r0);
1205        // Store row 1 of output
1206        vst1q_u32(dst.add(dst_stride), r1);
1207        // Store row 2 of output
1208        vst1q_u32(dst.add(2 * dst_stride), r2);
1209        // Store row 3 of output
1210        vst1q_u32(dst.add(3 * dst_stride), r3);
1211    }
1212}
1213
1214// ============================================================================
1215// 8-byte (u64) NEON transpose functions
1216//
1217// These are analogous to the 4-byte functions above, but operate on u64
1218// elements. Since a 128-bit NEON register holds 2 u64 elements, each row
1219// of a 4×4 block requires 2 registers (8 total for a block). The transpose
1220// uses a single-stage butterfly with vtrn1q_u64/vtrn2q_u64 on four 2×2
1221// sub-blocks.
1222// ============================================================================
1223
1224/// Top-level NEON transpose dispatcher for 8-byte elements.
1225///
1226/// Selects the appropriate strategy based on matrix size, mirroring
1227/// `transpose_neon_4b` but for u64 elements.
1228///
1229/// # Safety
1230///
1231/// Caller must ensure `input` and `output` point to valid memory regions
1232/// of at least `width * height` elements each.
1233#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1234#[inline]
1235unsafe fn transpose_neon_8b(input: *const u64, output: *mut u64, width: usize, height: usize) {
1236    let len = width * height;
1237
1238    #[cfg(feature = "parallel")]
1239    {
1240        if len >= PARALLEL_THRESHOLD {
1241            unsafe {
1242                transpose_neon_8b_parallel(input, output, width, height);
1243            }
1244            return;
1245        }
1246    }
1247
1248    if len <= SMALL_LEN {
1249        unsafe {
1250            transpose_small_8b(input, output, width, height);
1251        }
1252    } else if len <= MEDIUM_LEN {
1253        unsafe {
1254            transpose_tiled_8b(input, output, width, height);
1255        }
1256    } else {
1257        unsafe {
1258            transpose_recursive_8b(input, output, 0, height, 0, width, width, height);
1259        }
1260    }
1261}
1262
1263/// Parallel transpose for very large matrices of 8-byte elements.
1264///
1265/// The work is split into stripes, one per thread, along the longer input dimension.
1266///
1267/// # Stripe Division
1268///
1269/// The output holds the transpose in row-major order.
1270/// Input element `(r, c)` lands at output index `c * height + r`.
1271///
1272/// A wide input (more columns than rows) is split by columns.
1273/// - Each thread owns a column band over every row.
1274/// - Its writes form one contiguous block of output rows.
1275///
1276/// A tall or square input is split by rows.
1277/// - Each thread owns a row band over every column.
1278/// - Its input reads stay contiguous, one full row at a time.
1279///
1280/// ```text
1281///     wide input: split by columns        tall input: split by rows
1282///     ┌──────┬──────┬──────┐              ┌────────────────────┐
1283///     │  t0  │  t1  │  t2  │              │         t0         │
1284///     │ cols │ cols │ cols │              ├────────────────────┤
1285///     └──────┴──────┴──────┘              │         t1         │
1286///                                         ├────────────────────┤
1287///                                         │         t2         │
1288///                                         └────────────────────┘
1289/// ```
1290///
1291/// # Why the longer dimension
1292///
1293/// Splitting a wide input by rows would scatter each thread's writes:
1294/// - Each thread gets a thin column band, written down the tall output
1295///   with stride `height`.
1296/// - Scattered stores stall on read-for-ownership and TLB traffic.
1297///
1298/// Splitting by columns keeps each thread's writes in one contiguous block.
1299///
1300/// # Data Race Safety
1301///
1302/// Each thread writes a disjoint output region, so no synchronization is needed.
1303/// - A column band maps to a contiguous run of output rows, unique per thread.
1304/// - A row band maps to a unique set of output columns.
1305///
1306/// # Safety
1307///
1308/// Caller must ensure valid pointers for `width * height` elements.
1309#[cfg(all(target_arch = "aarch64", feature = "parallel"))]
1310#[inline]
1311unsafe fn transpose_neon_8b_parallel(
1312    input: *const u64,
1313    output: *mut u64,
1314    width: usize,
1315    height: usize,
1316) {
1317    use rayon::prelude::*;
1318
1319    // Number of available threads in the rayon thread pool.
1320    let num_threads = rayon::current_num_threads();
1321
1322    // We use `AtomicUsize` to pass pointer addresses to threads.
1323    //
1324    // This is safe because:
1325    // 1. We only read the addresses (Relaxed ordering is fine)
1326    // 2. Each thread writes to a disjoint output region
1327    let inp = AtomicUsize::new(input as usize);
1328    let out = AtomicUsize::new(output as usize);
1329
1330    // Split the longer input dimension so each thread's output stays contiguous.
1331    //
1332    // A wide input is split by columns, a tall or square input by rows.
1333    let split_cols = width > height;
1334
1335    // Length of the dimension being split.
1336    let stripe_len = if split_cols { width } else { height };
1337
1338    // Share handed to each thread.
1339    //
1340    // The ceiling keeps the final thread from getting an oversized chunk.
1341    let stripe_per_thread = stripe_len.div_ceil(num_threads);
1342
1343    (0..num_threads).into_par_iter().for_each(|thread_idx| {
1344        // Half-open stripe `[start, end)` of the split dimension owned here.
1345        let start = thread_idx * stripe_per_thread;
1346        let end = (start + stripe_per_thread).min(stripe_len);
1347
1348        // Empty when there are more threads than stripe units.
1349        if start < end {
1350            // Recover the pointers from their atomic addresses.
1351            let input_ptr = inp.load(Ordering::Relaxed) as *const u64;
1352            let output_ptr = out.load(Ordering::Relaxed) as *mut u64;
1353
1354            // Map the stripe to an input region.
1355            //
1356            // A column stripe spans every row.
1357            // A row stripe spans every column.
1358            let (row_start, row_end, col_start, col_end) = if split_cols {
1359                (0, height, start, end)
1360            } else {
1361                (start, end, 0, width)
1362            };
1363
1364            // SAFETY:
1365            // - Pointers are valid for `width * height` elements (from caller).
1366            // - Stripes partition one dimension, so the per-thread output
1367            //   regions are disjoint and never aliased.
1368            unsafe {
1369                transpose_region_tiled_8b(
1370                    input_ptr, output_ptr, row_start, row_end, col_start, col_end, width, height,
1371                );
1372            }
1373        }
1374    });
1375}
1376
1377/// Simple element-by-element transpose for small matrices of 8-byte elements.
1378///
1379/// # Safety
1380///
1381/// Caller must ensure valid pointers for `width * height` elements.
1382#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1383#[inline]
1384unsafe fn transpose_small_8b(input: *const u64, output: *mut u64, width: usize, height: usize) {
1385    for x in 0..width {
1386        for y in 0..height {
1387            let input_index = x + y * width;
1388            let output_index = y + x * height;
1389
1390            unsafe {
1391                *output.add(output_index) = *input.add(input_index);
1392            }
1393        }
1394    }
1395}
1396
1397/// Tiled transpose using 16×16 tiles for 8-byte elements.
1398///
1399/// # Safety
1400///
1401/// Caller must ensure valid pointers for `width * height` elements.
1402#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1403#[inline]
1404unsafe fn transpose_tiled_8b(input: *const u64, output: *mut u64, width: usize, height: usize) {
1405    let x_tile_count = width / TILE_SIZE;
1406    let y_tile_count = height / TILE_SIZE;
1407
1408    let remainder_x = width - x_tile_count * TILE_SIZE;
1409    let remainder_y = height - y_tile_count * TILE_SIZE;
1410
1411    // Process complete tiles
1412    for y_tile in 0..y_tile_count {
1413        for x_tile in 0..x_tile_count {
1414            let x_start = x_tile * TILE_SIZE;
1415            let y_start = y_tile * TILE_SIZE;
1416
1417            unsafe {
1418                transpose_tile_16x16_neon_8b(input, output, width, height, x_start, y_start);
1419            }
1420        }
1421
1422        // Right edge remainder
1423        if remainder_x > 0 {
1424            unsafe {
1425                transpose_block_scalar_8b(
1426                    input,
1427                    output,
1428                    width,
1429                    height,
1430                    x_tile_count * TILE_SIZE,
1431                    y_tile * TILE_SIZE,
1432                    remainder_x,
1433                    TILE_SIZE,
1434                );
1435            }
1436        }
1437    }
1438
1439    // Bottom edge remainder
1440    if remainder_y > 0 {
1441        for x_tile in 0..x_tile_count {
1442            unsafe {
1443                transpose_block_scalar_8b(
1444                    input,
1445                    output,
1446                    width,
1447                    height,
1448                    x_tile * TILE_SIZE,
1449                    y_tile_count * TILE_SIZE,
1450                    TILE_SIZE,
1451                    remainder_y,
1452                );
1453            }
1454        }
1455
1456        // Bottom-right corner
1457        if remainder_x > 0 {
1458            unsafe {
1459                transpose_block_scalar_8b(
1460                    input,
1461                    output,
1462                    width,
1463                    height,
1464                    x_tile_count * TILE_SIZE,
1465                    y_tile_count * TILE_SIZE,
1466                    remainder_x,
1467                    remainder_y,
1468                );
1469            }
1470        }
1471    }
1472}
1473
1474/// Recursive cache-oblivious transpose for large matrices of 8-byte elements.
1475///
1476/// # Safety
1477///
1478/// Caller must ensure valid pointers and that coordinate ranges are within bounds.
1479#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1480#[allow(clippy::too_many_arguments)]
1481unsafe fn transpose_recursive_8b(
1482    input: *const u64,
1483    output: *mut u64,
1484    row_start: usize,
1485    row_end: usize,
1486    col_start: usize,
1487    col_end: usize,
1488    total_cols: usize,
1489    total_rows: usize,
1490) {
1491    let nbr_rows = row_end - row_start;
1492    let nbr_cols = col_end - col_start;
1493
1494    if (nbr_rows <= RECURSIVE_LIMIT && nbr_cols <= RECURSIVE_LIMIT)
1495        || nbr_rows <= 2
1496        || nbr_cols <= 2
1497    {
1498        unsafe {
1499            transpose_region_tiled_8b(
1500                input, output, row_start, row_end, col_start, col_end, total_cols, total_rows,
1501            );
1502        }
1503        return;
1504    }
1505
1506    if nbr_rows >= nbr_cols {
1507        let mid = row_start + (nbr_rows / 2);
1508
1509        unsafe {
1510            transpose_recursive_8b(
1511                input, output, row_start, mid, col_start, col_end, total_cols, total_rows,
1512            );
1513        }
1514
1515        unsafe {
1516            transpose_recursive_8b(
1517                input, output, mid, row_end, col_start, col_end, total_cols, total_rows,
1518            );
1519        }
1520    } else {
1521        let mid = col_start + (nbr_cols / 2);
1522
1523        unsafe {
1524            transpose_recursive_8b(
1525                input, output, row_start, row_end, col_start, mid, total_cols, total_rows,
1526            );
1527        }
1528
1529        unsafe {
1530            transpose_recursive_8b(
1531                input, output, row_start, row_end, mid, col_end, total_cols, total_rows,
1532            );
1533        }
1534    }
1535}
1536
1537/// Tiled transpose for a rectangular region of 8-byte elements.
1538///
1539/// Used as the base case of recursive transpose and for parallel stripe processing.
1540///
1541/// # Safety
1542///
1543/// Caller must ensure:
1544/// - Valid pointers for `total_cols * total_rows` elements
1545/// - `row_start < row_end <= total_rows`
1546/// - `col_start < col_end <= total_cols`
1547#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1548#[inline]
1549#[allow(clippy::too_many_arguments)]
1550unsafe fn transpose_region_tiled_8b(
1551    input: *const u64,
1552    output: *mut u64,
1553    row_start: usize,
1554    row_end: usize,
1555    col_start: usize,
1556    col_end: usize,
1557    total_cols: usize,
1558    total_rows: usize,
1559) {
1560    let nbr_cols = col_end - col_start;
1561    let nbr_rows = row_end - row_start;
1562
1563    let x_tile_count = nbr_cols / TILE_SIZE;
1564    let y_tile_count = nbr_rows / TILE_SIZE;
1565
1566    let remainder_x = nbr_cols - x_tile_count * TILE_SIZE;
1567    let remainder_y = nbr_rows - y_tile_count * TILE_SIZE;
1568
1569    // Process complete tiles
1570    for y_tile in 0..y_tile_count {
1571        for x_tile in 0..x_tile_count {
1572            let col = col_start + x_tile * TILE_SIZE;
1573            let row = row_start + y_tile * TILE_SIZE;
1574
1575            // Uses the buffered tile function: for large matrices the output
1576            // is likely in L3/RAM, so L1 buffering + write prefetching avoids
1577            // RFO stalls on scattered output writes.
1578            unsafe {
1579                transpose_tile_16x16_neon_8b_buffered(
1580                    input, output, total_cols, total_rows, col, row,
1581                );
1582            }
1583        }
1584
1585        // Right edge remainder
1586        if remainder_x > 0 {
1587            unsafe {
1588                transpose_block_scalar_8b(
1589                    input,
1590                    output,
1591                    total_cols,
1592                    total_rows,
1593                    col_start + x_tile_count * TILE_SIZE,
1594                    row_start + y_tile * TILE_SIZE,
1595                    remainder_x,
1596                    TILE_SIZE,
1597                );
1598            }
1599        }
1600    }
1601
1602    // Bottom edge remainder
1603    if remainder_y > 0 {
1604        for x_tile in 0..x_tile_count {
1605            unsafe {
1606                transpose_block_scalar_8b(
1607                    input,
1608                    output,
1609                    total_cols,
1610                    total_rows,
1611                    col_start + x_tile * TILE_SIZE,
1612                    row_start + y_tile_count * TILE_SIZE,
1613                    TILE_SIZE,
1614                    remainder_y,
1615                );
1616            }
1617        }
1618
1619        // Bottom-right corner
1620        if remainder_x > 0 {
1621            unsafe {
1622                transpose_block_scalar_8b(
1623                    input,
1624                    output,
1625                    total_cols,
1626                    total_rows,
1627                    col_start + x_tile_count * TILE_SIZE,
1628                    row_start + y_tile_count * TILE_SIZE,
1629                    remainder_x,
1630                    remainder_y,
1631                );
1632            }
1633        }
1634    }
1635}
1636
1637/// Transpose a complete 16×16 tile of 8-byte elements using NEON SIMD (direct-to-output).
1638///
1639/// Used by the **medium tiled path** where the output likely fits in L2 cache.
1640///
1641/// # Safety
1642///
1643/// Caller must ensure:
1644/// - Valid pointers for the full matrix
1645/// - `x_start + 16 <= width`
1646/// - `y_start + 16 <= height`
1647#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1648#[inline]
1649unsafe fn transpose_tile_16x16_neon_8b(
1650    input: *const u64,
1651    output: *mut u64,
1652    width: usize,
1653    height: usize,
1654    x_start: usize,
1655    y_start: usize,
1656) {
1657    unsafe {
1658        // Block Row 0 (input rows y_start..y_start+4)
1659        let inp = input.add(y_start * width + x_start);
1660        let out = output.add(x_start * height + y_start);
1661        transpose_4x4_neon_8b(inp, out, width, height);
1662        transpose_4x4_neon_8b(inp.add(4), out.add(4 * height), width, height);
1663        transpose_4x4_neon_8b(inp.add(8), out.add(8 * height), width, height);
1664        transpose_4x4_neon_8b(inp.add(12), out.add(12 * height), width, height);
1665
1666        // Block Row 1 (input rows y_start+4..y_start+8)
1667        let inp = input.add((y_start + 4) * width + x_start);
1668        let out = output.add(x_start * height + y_start + 4);
1669        transpose_4x4_neon_8b(inp, out, width, height);
1670        transpose_4x4_neon_8b(inp.add(4), out.add(4 * height), width, height);
1671        transpose_4x4_neon_8b(inp.add(8), out.add(8 * height), width, height);
1672        transpose_4x4_neon_8b(inp.add(12), out.add(12 * height), width, height);
1673
1674        // Block Row 2 (input rows y_start+8..y_start+12)
1675        let inp = input.add((y_start + 8) * width + x_start);
1676        let out = output.add(x_start * height + y_start + 8);
1677        transpose_4x4_neon_8b(inp, out, width, height);
1678        transpose_4x4_neon_8b(inp.add(4), out.add(4 * height), width, height);
1679        transpose_4x4_neon_8b(inp.add(8), out.add(8 * height), width, height);
1680        transpose_4x4_neon_8b(inp.add(12), out.add(12 * height), width, height);
1681
1682        // Block Row 3 (input rows y_start+12..y_start+16)
1683        let inp = input.add((y_start + 12) * width + x_start);
1684        let out = output.add(x_start * height + y_start + 12);
1685        transpose_4x4_neon_8b(inp, out, width, height);
1686        transpose_4x4_neon_8b(inp.add(4), out.add(4 * height), width, height);
1687        transpose_4x4_neon_8b(inp.add(8), out.add(8 * height), width, height);
1688        transpose_4x4_neon_8b(inp.add(12), out.add(12 * height), width, height);
1689    }
1690}
1691
1692/// Transpose a complete 16×16 tile of 8-byte elements with L1 buffering.
1693///
1694/// Used by the **recursive/parallel path** for large matrices where the output
1695/// is in L3/RAM. L1 buffering + write prefetching avoids RFO stalls.
1696///
1697/// # Safety
1698///
1699/// Caller must ensure:
1700/// - Valid pointers for the full matrix
1701/// - `x_start + 16 <= width`
1702/// - `y_start + 16 <= height`
1703#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1704#[inline]
1705unsafe fn transpose_tile_16x16_neon_8b_buffered(
1706    input: *const u64,
1707    output: *mut u64,
1708    width: usize,
1709    height: usize,
1710    x_start: usize,
1711    y_start: usize,
1712) {
1713    // Stack buffer for L1-hot transpose (2 KB for u64).
1714    let mut buffer = MaybeUninit::<[u64; TILE_SIZE * TILE_SIZE]>::uninit();
1715    let buf = buffer.as_mut_ptr().cast::<u64>();
1716
1717    unsafe {
1718        // Transpose 4×4 grid of NEON blocks into the buffer.
1719
1720        // Block Row 0 (input rows y_start..y_start+4)
1721        let inp = input.add(y_start * width + x_start);
1722        transpose_4x4_neon_8b(inp, buf, width, TILE_SIZE);
1723        transpose_4x4_neon_8b(inp.add(4), buf.add(4 * TILE_SIZE), width, TILE_SIZE);
1724        transpose_4x4_neon_8b(inp.add(8), buf.add(8 * TILE_SIZE), width, TILE_SIZE);
1725        transpose_4x4_neon_8b(inp.add(12), buf.add(12 * TILE_SIZE), width, TILE_SIZE);
1726
1727        // Block Row 1 (input rows y_start+4..y_start+8)
1728        let inp = input.add((y_start + 4) * width + x_start);
1729        transpose_4x4_neon_8b(inp, buf.add(4), width, TILE_SIZE);
1730        transpose_4x4_neon_8b(inp.add(4), buf.add(4 * TILE_SIZE + 4), width, TILE_SIZE);
1731        transpose_4x4_neon_8b(inp.add(8), buf.add(8 * TILE_SIZE + 4), width, TILE_SIZE);
1732        transpose_4x4_neon_8b(inp.add(12), buf.add(12 * TILE_SIZE + 4), width, TILE_SIZE);
1733
1734        // Block Row 2 (input rows y_start+8..y_start+12)
1735        let inp = input.add((y_start + 8) * width + x_start);
1736        transpose_4x4_neon_8b(inp, buf.add(8), width, TILE_SIZE);
1737        transpose_4x4_neon_8b(inp.add(4), buf.add(4 * TILE_SIZE + 8), width, TILE_SIZE);
1738        transpose_4x4_neon_8b(inp.add(8), buf.add(8 * TILE_SIZE + 8), width, TILE_SIZE);
1739        transpose_4x4_neon_8b(inp.add(12), buf.add(12 * TILE_SIZE + 8), width, TILE_SIZE);
1740
1741        // Block Row 3 (input rows y_start+12..y_start+16)
1742        let inp = input.add((y_start + 12) * width + x_start);
1743        transpose_4x4_neon_8b(inp, buf.add(12), width, TILE_SIZE);
1744        transpose_4x4_neon_8b(inp.add(4), buf.add(4 * TILE_SIZE + 12), width, TILE_SIZE);
1745        transpose_4x4_neon_8b(inp.add(8), buf.add(8 * TILE_SIZE + 12), width, TILE_SIZE);
1746        transpose_4x4_neon_8b(inp.add(12), buf.add(12 * TILE_SIZE + 12), width, TILE_SIZE);
1747
1748        // Flush buffer to output with write prefetching.
1749        prefetch_write(output.add(x_start * height + y_start) as *const u8);
1750        for c in 0..TILE_SIZE {
1751            if c + 1 < TILE_SIZE {
1752                prefetch_write(output.add((x_start + c + 1) * height + y_start) as *const u8);
1753            }
1754            core::ptr::copy_nonoverlapping(
1755                buf.add(c * TILE_SIZE),
1756                output.add((x_start + c) * height + y_start),
1757                TILE_SIZE,
1758            );
1759        }
1760    }
1761}
1762
1763/// Scalar transpose for an arbitrary rectangular block of 8-byte elements.
1764///
1765/// Used for handling edge cases where dimensions don't align to tile boundaries.
1766///
1767/// # Safety
1768///
1769/// Caller must ensure:
1770/// - Valid pointers for the full matrix
1771/// - `x_start + block_width <= width`
1772/// - `y_start + block_height <= height`
1773#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1774#[inline]
1775#[allow(clippy::too_many_arguments)]
1776unsafe fn transpose_block_scalar_8b(
1777    input: *const u64,
1778    output: *mut u64,
1779    width: usize,
1780    height: usize,
1781    x_start: usize,
1782    y_start: usize,
1783    block_width: usize,
1784    block_height: usize,
1785) {
1786    for inner_x in 0..block_width {
1787        for inner_y in 0..block_height {
1788            let x = x_start + inner_x;
1789            let y = y_start + inner_y;
1790
1791            let input_index = x + y * width;
1792            let output_index = y + x * height;
1793
1794            unsafe {
1795                *output.add(output_index) = *input.add(input_index);
1796            }
1797        }
1798    }
1799}
1800
1801/// Transpose a 4×4 block of 64-bit elements using NEON SIMD.
1802///
1803/// This is the fundamental building block for 8-byte element transpose.
1804///
1805/// Since a 128-bit NEON register holds only 2 u64 elements, each row of 4
1806/// elements requires 2 registers. A 4×4 block uses 8 registers for input
1807/// and 8 for output (16 total, well within NEON's 32 registers).
1808///
1809/// # Algorithm
1810///
1811/// The transpose uses a single-stage butterfly on four independent 2×2
1812/// sub-blocks:
1813///
1814/// ```text
1815///     Load:  q0_lo=[a00,a01] q0_hi=[a02,a03]  (row 0)
1816///            q1_lo=[a10,a11] q1_hi=[a12,a13]  (row 1)
1817///            q2_lo=[a20,a21] q2_hi=[a22,a23]  (row 2)
1818///            q3_lo=[a30,a31] q3_hi=[a32,a33]  (row 3)
1819///
1820///     Transpose 2×2 sub-blocks:
1821///       Top-left:     trn1(q0_lo,q1_lo)=[a00,a10]  trn2(q0_lo,q1_lo)=[a01,a11]
1822///       Top-right:    trn1(q0_hi,q1_hi)=[a02,a12]  trn2(q0_hi,q1_hi)=[a03,a13]
1823///       Bottom-left:  trn1(q2_lo,q3_lo)=[a20,a30]  trn2(q2_lo,q3_lo)=[a21,a31]
1824///       Bottom-right: trn1(q2_hi,q3_hi)=[a22,a32]  trn2(q2_hi,q3_hi)=[a23,a33]
1825///
1826///     Store: row0=[a00,a10,a20,a30]  row1=[a01,a11,a21,a31]
1827///            row2=[a02,a12,a22,a32]  row3=[a03,a13,a23,a33]
1828/// ```
1829///
1830/// # Safety
1831///
1832/// Caller must ensure:
1833/// - `src` is valid for reading 4 rows of `src_stride` elements each
1834/// - `dst` is valid for writing 4 rows of `dst_stride` elements each
1835/// - The first 4 elements of each row are accessible
1836#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1837#[inline(always)]
1838unsafe fn transpose_4x4_neon_8b(
1839    src: *const u64,
1840    dst: *mut u64,
1841    src_stride: usize,
1842    dst_stride: usize,
1843) {
1844    unsafe {
1845        // Load 4 rows, 2 registers per row (4 u64 = 2 × 128-bit)
1846
1847        // Row 0: [a00, a01] [a02, a03]
1848        let q0_lo = vld1q_u64(src);
1849        let q0_hi = vld1q_u64(src.add(2));
1850        // Row 1: [a10, a11] [a12, a13]
1851        let q1_lo = vld1q_u64(src.add(src_stride));
1852        let q1_hi = vld1q_u64(src.add(src_stride + 2));
1853        // Row 2: [a20, a21] [a22, a23]
1854        let q2_lo = vld1q_u64(src.add(2 * src_stride));
1855        let q2_hi = vld1q_u64(src.add(2 * src_stride + 2));
1856        // Row 3: [a30, a31] [a32, a33]
1857        let q3_lo = vld1q_u64(src.add(3 * src_stride));
1858        let q3_hi = vld1q_u64(src.add(3 * src_stride + 2));
1859
1860        // Transpose four 2×2 sub-blocks using vtrn1q_u64/vtrn2q_u64
1861
1862        // Top-left: rows 0,1 × columns 0,1
1863        let r0_lo = vtrn1q_u64(q0_lo, q1_lo); // [a00, a10]
1864        let r1_lo = vtrn2q_u64(q0_lo, q1_lo); // [a01, a11]
1865        // Top-right: rows 0,1 × columns 2,3
1866        let r2_lo = vtrn1q_u64(q0_hi, q1_hi); // [a02, a12]
1867        let r3_lo = vtrn2q_u64(q0_hi, q1_hi); // [a03, a13]
1868        // Bottom-left: rows 2,3 × columns 0,1
1869        let r0_hi = vtrn1q_u64(q2_lo, q3_lo); // [a20, a30]
1870        let r1_hi = vtrn2q_u64(q2_lo, q3_lo); // [a21, a31]
1871        // Bottom-right: rows 2,3 × columns 2,3
1872        let r2_hi = vtrn1q_u64(q2_hi, q3_hi); // [a22, a32]
1873        let r3_hi = vtrn2q_u64(q2_hi, q3_hi); // [a23, a33]
1874
1875        // Store 4 transposed rows, 2 registers per row
1876
1877        // Row 0: [a00, a10, a20, a30]
1878        vst1q_u64(dst, r0_lo);
1879        vst1q_u64(dst.add(2), r0_hi);
1880        // Row 1: [a01, a11, a21, a31]
1881        vst1q_u64(dst.add(dst_stride), r1_lo);
1882        vst1q_u64(dst.add(dst_stride + 2), r1_hi);
1883        // Row 2: [a02, a12, a22, a32]
1884        vst1q_u64(dst.add(2 * dst_stride), r2_lo);
1885        vst1q_u64(dst.add(2 * dst_stride + 2), r2_hi);
1886        // Row 3: [a03, a13, a23, a33]
1887        vst1q_u64(dst.add(3 * dst_stride), r3_lo);
1888        vst1q_u64(dst.add(3 * dst_stride + 2), r3_hi);
1889    }
1890}
1891
1892#[cfg(test)]
1893mod tests {
1894    use alloc::vec;
1895    use alloc::vec::Vec;
1896
1897    use p3_baby_bear::BabyBear;
1898    use p3_field::PrimeCharacteristicRing;
1899    use p3_goldilocks::Goldilocks;
1900    use proptest::prelude::*;
1901
1902    use super::*;
1903
1904    /// A type with the same size/alignment mismatch as `Complex<Mersenne31>`:
1905    /// 8 bytes, but only 4-byte aligned. Must not be routed through the 8-byte
1906    /// NEON path, which assumes `u64` alignment.
1907    #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1908    #[repr(C, align(4))]
1909    struct Size8Align4([u8; 8]);
1910
1911    /// Naive reference implementation for correctness testing.
1912    fn transpose_reference<T: Copy + Default>(input: &[T], width: usize, height: usize) -> Vec<T> {
1913        // Allocate output buffer with same size as input.
1914        let mut output = vec![T::default(); width * height];
1915
1916        // For each position (x, y) in the input matrix:
1917        // - Input index: y * width + x (row-major)
1918        // - Output index: x * height + y (transposed row-major)
1919        for y in 0..height {
1920            for x in 0..width {
1921                output[x * height + y] = input[y * width + x];
1922            }
1923        }
1924
1925        output
1926    }
1927
1928    /// Strategy for generating matrix dimensions.
1929    fn dimension_strategy() -> impl Strategy<Value = (usize, usize)> {
1930        // Compute boundary dimensions from constants.
1931        // `small_side` is the largest square that stays in the small (scalar) path.
1932        let small_side = (SMALL_LEN as f64).sqrt() as usize;
1933        // `medium_side` is the largest square that stays in the medium (tiled) path.
1934        let medium_side = (MEDIUM_LEN as f64).sqrt() as usize;
1935        // `large_side` is the side length that triggers the large (recursive) path.
1936        let large_side = medium_side + 1;
1937
1938        prop_oneof![
1939            // Edge cases: empty and degenerate matrices
1940            //
1941            // Empty matrix (0×0)
1942            Just((0, 0)),
1943            // Single row (1×n) - tests degenerate case
1944            (1..=100_usize).prop_map(|w| (w, 1)),
1945            // Single column (n×1) - tests degenerate case
1946            (1..=100_usize).prop_map(|h| (1, h)),
1947            // Small path: len < SMALL_LEN (scalar transpose)
1948            //
1949            // These dimensions exercise the scalar transpose path.
1950
1951            // Tiny matrices (various shapes within small threshold)
1952            (1..=small_side, 1..=small_side),
1953            // Medium path: SMALL_LEN ≤ len < MEDIUM_LEN (tiled TILE_SIZE×TILE_SIZE)
1954            //
1955            // These dimensions exercise the tiled TILE_SIZE×TILE_SIZE path.
1956
1957            // Exactly 4×4 (single NEON block)
1958            Just((4, 4)),
1959            // Exactly TILE_SIZE×TILE_SIZE (single tile)
1960            Just((TILE_SIZE, TILE_SIZE)),
1961            // Multiple complete tiles (2× and 4× TILE_SIZE)
1962            Just((TILE_SIZE * 2, TILE_SIZE * 2)),
1963            Just((TILE_SIZE * 4, TILE_SIZE * 4)),
1964            // Non-aligned: has remainders in both dimensions
1965            // Range from just above TILE_SIZE to below 4×TILE_SIZE.
1966            // These test the scalar fallback for tile edges.
1967            (
1968                (TILE_SIZE + 1)..=(TILE_SIZE * 4 - 1),
1969                (TILE_SIZE + 1)..=(TILE_SIZE * 4 - 1)
1970            ),
1971            // Wide rectangle with remainders (medium path)
1972            (50..=200_usize, 10..=50_usize),
1973            // Tall rectangle with remainders (medium path)
1974            (10..=50_usize, 50..=200_usize),
1975            // Large path: MEDIUM_LEN ≤ len < PARALLEL_THRESHOLD (recursive)
1976            //
1977            // These exercise the cache-oblivious recursive subdivision.
1978
1979            // Square matrices triggering recursion (just above medium threshold)
1980            Just((large_side, large_side)),
1981            // Slightly larger square
1982            Just((large_side + 100, large_side + 100)),
1983            // Wide rectangle triggering recursion
1984            Just((large_side * 2, large_side / 2)),
1985            // Tall rectangle triggering recursion
1986            Just((large_side / 2, large_side * 2)),
1987            // Non-power-of-2 dimensions in large range
1988            Just((large_side + 50, large_side + 75)),
1989        ]
1990    }
1991
1992    proptest! {
1993        #[test]
1994        fn proptest_transpose_babybear((width, height) in dimension_strategy()) {
1995            // Skip empty matrices (they're trivially correct).
1996            if width == 0 || height == 0 {
1997                // Just verify it doesn't panic.
1998                let input: [BabyBear; 0] = [];
1999                let mut output: [BabyBear; 0] = [];
2000                transpose(&input, &mut output, width, height);
2001                return Ok(());
2002            }
2003
2004            // Create input matrix with unique values at each position.
2005            let input: Vec<BabyBear> = (0..width * height)
2006                .map(|i| BabyBear::from_u64(i as u64))
2007                .collect();
2008
2009            // Allocate output buffer.
2010            let mut output = BabyBear::zero_vec(width * height);
2011
2012            // Run optimized transpose.
2013            transpose(&input, &mut output, width, height);
2014
2015            // Run reference transpose.
2016            let expected = transpose_reference(&input, width, height);
2017
2018            // Verify results match.
2019            prop_assert_eq!(
2020                output,
2021                expected,
2022                "Transpose mismatch for {}×{} matrix",
2023                width,
2024                height
2025            );
2026        }
2027
2028        #[test]
2029        fn proptest_transpose_u64((width, height) in dimension_strategy()) {
2030            // Skip empty and very large matrices for u64 (memory intensive).
2031            if width == 0 || height == 0 || width * height > 100_000 {
2032                return Ok(());
2033            }
2034
2035            // Create input with unique values.
2036            let input: Vec<u64> = (0..width * height).map(|i| i as u64).collect();
2037
2038            // Allocate output.
2039            let mut output = vec![0u64; width * height];
2040
2041            // Run transpose.
2042            transpose(&input, &mut output, width, height);
2043
2044            // Verify against reference.
2045            let expected = transpose_reference(&input, width, height);
2046            prop_assert_eq!(output, expected);
2047        }
2048
2049        #[test]
2050        fn proptest_transpose_u8((width, height) in dimension_strategy()) {
2051            // Skip empty and very large matrices.
2052            if width == 0 || height == 0 || width * height > 100_000 {
2053                return Ok(());
2054            }
2055
2056            // Create input with unique values (wrapping for u8).
2057            let input: Vec<u8> = (0..width * height).map(|i| i as u8).collect();
2058
2059            // Allocate output.
2060            let mut output = vec![0u8; width * height];
2061
2062            // Run transpose.
2063            transpose(&input, &mut output, width, height);
2064
2065            // Verify against reference.
2066            let expected = transpose_reference(&input, width, height);
2067            prop_assert_eq!(output, expected);
2068        }
2069
2070        #[test]
2071        fn proptest_transpose_size8_align4((width, height) in dimension_strategy()) {
2072            // Skip empty and very large matrices.
2073            if width == 0 || height == 0 || width * height > 100_000 {
2074                return Ok(());
2075            }
2076
2077            // Create input with unique values.
2078            let input: Vec<Size8Align4> = (0..width * height)
2079                .map(|i| Size8Align4((i as u64).to_le_bytes()))
2080                .collect();
2081
2082            // Allocate output.
2083            let mut output = vec![Size8Align4::default(); width * height];
2084
2085            // Run transpose.
2086            transpose(&input, &mut output, width, height);
2087
2088            // Verify against reference.
2089            let expected = transpose_reference(&input, width, height);
2090            prop_assert_eq!(output, expected);
2091        }
2092
2093        #[test]
2094        fn proptest_transpose_goldilocks((width, height) in dimension_strategy()) {
2095            // Skip empty matrices.
2096            if width == 0 || height == 0 {
2097                let input: [Goldilocks; 0] = [];
2098                let mut output: [Goldilocks; 0] = [];
2099                transpose(&input, &mut output, width, height);
2100                return Ok(());
2101            }
2102
2103            // Create input matrix with unique values at each position.
2104            let input: Vec<Goldilocks> = (0..width * height)
2105                .map(|i| Goldilocks::from_u64(i as u64))
2106                .collect();
2107
2108            // Allocate output buffer.
2109            let mut output = Goldilocks::zero_vec(width * height);
2110
2111            // Run optimized transpose.
2112            transpose(&input, &mut output, width, height);
2113
2114            // Run reference transpose.
2115            let expected = transpose_reference(&input, width, height);
2116
2117            // Verify results match.
2118            prop_assert_eq!(
2119                output,
2120                expected,
2121                "Transpose mismatch for {}×{} matrix",
2122                width,
2123                height
2124            );
2125        }
2126    }
2127
2128    #[test]
2129    fn transpose_parallel_paths_match_reference() {
2130        // The longer-dimension striping runs only past the parallel threshold,
2131        // and only on aarch64 with the `parallel` feature.
2132        //
2133        // The proptest dimensions stay below that threshold, so these shapes
2134        // cross it on purpose to cover both stripings.
2135        let shapes = [
2136            (1 << 13, 640), // wide, tile-aligned
2137            (640, 1 << 13), // tall, tile-aligned
2138            (8191, 641),    // wide, off both tile and thread boundaries
2139            (641, 8191),    // tall, off both tile and thread boundaries
2140            (1 << 22, 2),   // degenerate wide: a two-row input
2141        ];
2142        for (width, height) in shapes {
2143            // Distinct values 0..size, so a misplaced element is caught.
2144            let size = width * height;
2145
2146            // 4-byte path.
2147            let input: Vec<u32> = (0..size as u32).collect();
2148            let mut output = vec![0u32; size];
2149            transpose(&input, &mut output, width, height);
2150            assert_eq!(
2151                output,
2152                transpose_reference(&input, width, height),
2153                "4-byte parallel transpose mismatch for {width}×{height}"
2154            );
2155
2156            // 8-byte path.
2157            let input: Vec<u64> = (0..size as u64).collect();
2158            let mut output = vec![0u64; size];
2159            transpose(&input, &mut output, width, height);
2160            assert_eq!(
2161                output,
2162                transpose_reference(&input, width, height),
2163                "8-byte parallel transpose mismatch for {width}×{height}"
2164            );
2165        }
2166    }
2167}