@@ -898,6 +898,68 @@ mod tests {
898898 ) ;
899899 }
900900
901+ /// `exchange::<G>` composes a complete 16x16 transpose over four stages.
902+ ///
903+ /// This is the control the codegen oracle's driver applies to
904+ /// `transpose_16x16_composed`, brought into the test suite so the library
905+ /// method is checked and not merely its probe-local twin. A packed-but-
906+ /// wrong shuffle network would pass a codegen histogram and fail here.
907+ ///
908+ /// Note the semantics differ from `interleave_*` / `concat_*` above: those
909+ /// mirror x86's per-128-bit-lane `unpack`, whereas `exchange` pairs lane
910+ /// `c` with lane `c ^ G` across the whole vector. They are different
911+ /// permutations and neither substitutes for the other.
912+ #[ test]
913+ fn u32x16_exchange_stages_compose_a_transpose ( ) {
914+ fn stage < const G : usize > ( m : & mut [ U32x16 ; 16 ] ) {
915+ for r in 0 ..16 {
916+ if r & G == 0 {
917+ let ( lo, hi) = m[ r] . exchange :: < G > ( m[ r | G ] ) ;
918+ m[ r] = lo;
919+ m[ r | G ] = hi;
920+ }
921+ }
922+ }
923+
924+ // Row r, lane c = r*16 + c. A transpose must yield row r, lane c = c*16 + r.
925+ let mut m: [ U32x16 ; 16 ] =
926+ core:: array:: from_fn ( |r| U32x16 :: from_array ( core:: array:: from_fn ( |c| ( r * 16 + c) as u32 ) ) ) ;
927+
928+ stage :: < 1 > ( & mut m) ;
929+ stage :: < 2 > ( & mut m) ;
930+ stage :: < 4 > ( & mut m) ;
931+ stage :: < 8 > ( & mut m) ;
932+
933+ for ( r, row) in m. iter ( ) . enumerate ( ) {
934+ let want: [ u32 ; 16 ] = core:: array:: from_fn ( |c| ( c * 16 + r) as u32 ) ;
935+ assert_eq ! ( row. to_array( ) , want, "row {r} after four exchange stages" ) ;
936+ }
937+ }
938+
939+ /// Each granularity in isolation, pinned lane-by-lane, so a backend that
940+ /// gets one stage wrong is not masked by the composition above.
941+ #[ test]
942+ fn u32x16_exchange_is_lane_exact_per_granularity ( ) {
943+ let a: [ u32 ; 16 ] = core:: array:: from_fn ( |i| 10 + i as u32 ) ;
944+ let b: [ u32 ; 16 ] = core:: array:: from_fn ( |i| 30 + i as u32 ) ;
945+ let ( va, vb) = ( U32x16 :: from_array ( a) , U32x16 :: from_array ( b) ) ;
946+
947+ // lo[c] = a[c] when c & G == 0 else b[c ^ G]
948+ // hi[c] = b[c] when c & G != 0 else a[c ^ G]
949+ for g in [ 1usize , 2 , 4 , 8 ] {
950+ let ( lo, hi) = match g {
951+ 1 => va. exchange :: < 1 > ( vb) ,
952+ 2 => va. exchange :: < 2 > ( vb) ,
953+ 4 => va. exchange :: < 4 > ( vb) ,
954+ _ => va. exchange :: < 8 > ( vb) ,
955+ } ;
956+ let want_lo: [ u32 ; 16 ] = core:: array:: from_fn ( |c| if c & g == 0 { a[ c] } else { b[ c ^ g] } ) ;
957+ let want_hi: [ u32 ; 16 ] = core:: array:: from_fn ( |c| if c & g != 0 { b[ c] } else { a[ c ^ g] } ) ;
958+ assert_eq ! ( lo. to_array( ) , want_lo, "exchange::<{g}> lo" ) ;
959+ assert_eq ! ( hi. to_array( ) , want_hi, "exchange::<{g}> hi" ) ;
960+ }
961+ }
962+
901963 /// `U32x8`'s shuffle + rotate surface, checked against the REAL x86
902964 /// intrinsics it claims to reproduce.
903965 ///
0 commit comments