1pub fn is_injective_layout(dims: &[usize], strides: &[isize]) -> bool {
44 is_injective_layout_without_alloc(dims, strides) || is_injective_layout_exact(dims, strides)
45}
46
47#[doc(hidden)]
57pub fn is_injective_layout_without_alloc(dims: &[usize], strides: &[isize]) -> bool {
58 let Some(total) = validate_injective_layout_inputs(dims, strides) else {
59 return false;
60 };
61 if total <= 1 {
62 return true;
63 }
64 match interleaved_block(dims, strides) {
65 None => false,
66 Some(None) => true,
67 Some(Some(block)) => {
68 if !block.may_be_injective() {
69 return false;
71 }
72 if let Some(injective) = two_axis_block_is_injective(dims, strides, &block) {
73 return injective;
74 }
75 if let Some(injective) = block_differences_avoid_zero(dims, strides, &block) {
76 return injective;
77 }
78 block.total <= PAIRWISE_BLOCK_BUDGET as u128
79 && block_offsets_unique_pairwise(dims, strides, &block)
80 }
81 }
82}
83
84fn is_injective_layout_exact(dims: &[usize], strides: &[isize]) -> bool {
86 let Some(total) = validate_injective_layout_inputs(dims, strides) else {
87 return false;
88 };
89 if total <= 1 {
90 return true;
91 }
92 match interleaved_block(dims, strides) {
93 None => false,
94 Some(None) => true,
95 Some(Some(block)) => {
96 if !block.may_be_injective() {
97 return false;
99 }
100 if let Some(injective) = two_axis_block_is_injective(dims, strides, &block) {
101 return injective;
102 }
103 block.total <= EXACT_BLOCK_BUDGET as u128 && block_offsets_unique(dims, strides, &block)
106 }
107 }
108}
109
110const EXACT_BLOCK_BUDGET: usize = 1 << 24;
118
119const PAIRWISE_BLOCK_BUDGET: usize = 4096;
124
125const DIFFERENCE_SEARCH_BUDGET: u128 = 1 << 22;
133
134struct InterleavedBlock {
136 last_key: (u128, usize),
137 total: u128,
139 span: u128,
142}
143
144impl InterleavedBlock {
145 fn contains(&self, axis: usize, dim: usize, stride: isize) -> bool {
146 dim > 1 && (stride.unsigned_abs() as u128, axis) <= self.last_key
147 }
148
149 fn may_be_injective(&self) -> bool {
151 self.total <= self.span + 1
152 }
153}
154
155fn two_axis_block_is_injective(
166 dims: &[usize],
167 strides: &[isize],
168 block: &InterleavedBlock,
169) -> Option<bool> {
170 let mut axes = [(0usize, 0usize); 2]; let mut count = 0;
172 for (axis, (&dim, &stride)) in dims.iter().zip(strides.iter()).enumerate() {
173 if !block.contains(axis, dim, stride) {
174 continue;
175 }
176 if count == 2 {
177 return None;
178 }
179 axes[count] = (dim, stride.unsigned_abs());
180 count += 1;
181 }
182 if count != 2 {
183 return None;
184 }
185 let (extent_lo, stride_lo, extent_hi, stride_hi) = if axes[0].1 <= axes[1].1 {
187 (
188 axes[0].0 as u128,
189 axes[0].1 as u128,
190 axes[1].0 as u128,
191 axes[1].1 as u128,
192 )
193 } else {
194 (
195 axes[1].0 as u128,
196 axes[1].1 as u128,
197 axes[0].0 as u128,
198 axes[0].1 as u128,
199 )
200 };
201 let g = gcd(stride_lo, stride_hi);
202 debug_assert!(g > 0, "stride zero on a non-singleton axis was rejected");
203 Some(stride_hi / g >= extent_lo || stride_lo / g >= extent_hi)
204}
205
206fn block_differences_avoid_zero(
221 dims: &[usize],
222 strides: &[isize],
223 block: &InterleavedBlock,
224) -> Option<bool> {
225 let in_block = |axis: usize| block.contains(axis, dims[axis], strides[axis]);
226 let (mut first, mut second) = (None::<usize>, None::<usize>);
228 let mut count = 0usize;
229 for axis in (0..dims.len()).filter(|&axis| in_block(axis)) {
230 count += 1;
231 if first.is_none_or(|f| dims[axis] > dims[f]) {
232 second = first;
233 first = Some(axis);
234 } else if second.is_none_or(|s| dims[axis] > dims[s]) {
235 second = Some(axis);
236 }
237 }
238 if count < 3 {
239 return None;
240 }
241 let (first, second) = (first?, second?);
242 let enumerated = |axis: usize| axis != first && axis != second && in_block(axis);
243
244 let mut boxed = 1u128;
247 for axis in (0..dims.len()).filter(|&axis| enumerated(axis)) {
248 boxed = boxed.checked_mul(2 * dims[axis] as u128 - 1)?;
249 if boxed / 2 > DIFFERENCE_SEARCH_BUDGET {
250 return None;
251 }
252 }
253
254 let a = strides[first].unsigned_abs() as i128;
257 let b = strides[second].unsigned_abs() as i128;
258 let (x_bound, y_bound) = ((dims[first] - 1) as i128, (dims[second] - 1) as i128);
259 let (g, inverse) = gcd_and_inverse(a, b);
260 let (a, b) = (a / g, b / g);
261 let solvable = |c: i128| -> bool {
263 let x0 = (c.rem_euclid(b) * inverse).rem_euclid(b);
265 let lo = (c - y_bound * b).div_euclid(a) + i128::from((c - y_bound * b).rem_euclid(a) != 0);
267 let hi = (c + y_bound * b).div_euclid(a);
268 let (lo, hi) = (lo.max(-x_bound), hi.min(x_bound));
269 lo <= hi && lo + (x0 - lo).rem_euclid(b) <= hi
270 };
271
272 if b <= x_bound && a <= y_bound {
274 return Some(false);
275 }
276
277 let mut axes: tinyvec::TinyVec<[(i128, i128, i128); crate::INLINE_RANK]> = (0..dims.len())
282 .filter(|&axis| enumerated(axis))
283 .map(|axis| {
284 (
285 0,
286 (dims[axis] - 1) as i128,
287 strides[axis].unsigned_abs() as i128,
288 )
289 })
290 .collect();
291 let mut r = 0i128;
292 for _ in 0..boxed / 2 {
293 for (delta, bound, stride) in axes.iter_mut() {
294 if *delta < *bound {
295 *delta += 1;
296 r += *stride;
297 break;
298 }
299 r -= *stride * (*delta + *bound);
300 *delta = -*bound;
301 }
302 if r % g == 0 && solvable(-r / g) {
303 return Some(false);
304 }
305 }
306 Some(true)
307}
308
309fn gcd_and_inverse(a: i128, b: i128) -> (i128, i128) {
312 let (mut old_r, mut r) = (a, b);
314 let (mut old_s, mut s) = (1i128, 0i128);
315 while r != 0 {
316 let q = old_r / r;
317 (old_r, r) = (r, old_r - q * r);
318 (old_s, s) = (s, old_s - q * s);
319 }
320 let modulus = b / old_r;
321 (old_r, old_s.rem_euclid(modulus))
322}
323
324fn gcd(mut a: u128, mut b: u128) -> u128 {
326 while b != 0 {
327 let remainder = a % b;
328 a = b;
329 b = remainder;
330 }
331 a
332}
333
334fn interleaved_block(dims: &[usize], strides: &[isize]) -> Option<Option<InterleavedBlock>> {
341 let mut covered_span = 0u128;
342 let mut covered_total = 1u128;
343 let mut previous_key = None;
344 let mut block = None;
345 let active_axes = dims.iter().filter(|&&dim| dim > 1).count();
346 for _ in 0..active_axes {
347 let mut next = None;
348 for (axis, (&dim, &stride)) in dims.iter().zip(strides.iter()).enumerate() {
349 if dim <= 1 {
350 continue;
351 }
352 let key = (stride.checked_abs()? as u128, axis);
353 if previous_key.is_some_and(|previous| key <= previous) {
354 continue;
355 }
356 if next.is_none_or(|(best, _)| key < best) {
357 next = Some((key, dim as u128));
358 }
359 }
360 let ((stride, axis), dim) = next?;
361 let separated = stride > covered_span;
362 covered_span = stride
363 .checked_mul(dim - 1)
364 .and_then(|span| covered_span.checked_add(span))?;
365 covered_total = covered_total.checked_mul(dim)?;
366 if !separated {
367 block = Some(InterleavedBlock {
368 last_key: (stride, axis),
369 total: covered_total,
370 span: covered_span,
371 });
372 }
373 previous_key = Some((stride, axis));
374 }
375 Some(block)
376}
377
378fn block_offsets_unique(dims: &[usize], strides: &[isize], block: &InterleavedBlock) -> bool {
380 let axes: Vec<(usize, usize)> = dims
381 .iter()
382 .zip(strides.iter())
383 .enumerate()
384 .filter(|&(axis, (&dim, &stride))| block.contains(axis, dim, stride))
385 .map(|(_, (&dim, &stride))| (dim, stride.unsigned_abs()))
386 .collect();
387 let (Ok(total), Ok(span)) = (usize::try_from(block.total), usize::try_from(block.span)) else {
388 return false;
389 };
390
391 let mut indices = vec![0usize; axes.len()];
395 let mut offset = 0usize;
396 let mut advance = |offset: &mut usize| {
397 for (index, &(dim, stride)) in indices.iter_mut().zip(axes.iter()) {
398 if *index + 1 < dim {
399 *index += 1;
400 *offset += stride;
401 return;
402 }
403 *offset -= stride * (dim - 1);
404 *index = 0;
405 }
406 };
407
408 if block.span / 64 <= block.total {
409 let mut seen = vec![0u64; span / 64 + 1];
410 for _ in 0..total {
411 let (word, bit) = (offset / 64, 1u64 << (offset % 64));
412 if seen[word] & bit != 0 {
413 return false;
414 }
415 seen[word] |= bit;
416 advance(&mut offset);
417 }
418 true
419 } else {
420 let mut offsets = Vec::with_capacity(total);
421 for _ in 0..total {
422 offsets.push(offset);
423 advance(&mut offset);
424 }
425 offsets.sort_unstable();
426 offsets.windows(2).all(|pair| pair[0] != pair[1])
427 }
428}
429
430fn block_offset_for_linear_index(
431 dims: &[usize],
432 strides: &[isize],
433 block: &InterleavedBlock,
434 mut linear: usize,
435) -> u128 {
436 let mut offset = 0u128;
437 for (axis, (&dim, &stride)) in dims.iter().zip(strides.iter()).enumerate() {
438 if !block.contains(axis, dim, stride) {
439 continue;
440 }
441 offset += stride.unsigned_abs() as u128 * (linear % dim) as u128;
442 linear /= dim;
443 }
444 offset
445}
446
447fn block_offsets_unique_pairwise(
448 dims: &[usize],
449 strides: &[isize],
450 block: &InterleavedBlock,
451) -> bool {
452 let Ok(total) = usize::try_from(block.total) else {
453 return false;
454 };
455 for lhs in 0..total {
456 let lhs_offset = block_offset_for_linear_index(dims, strides, block, lhs);
457 for rhs in (lhs + 1)..total {
458 if block_offset_for_linear_index(dims, strides, block, rhs) == lhs_offset {
459 return false;
460 }
461 }
462 }
463 true
464}
465
466fn validate_injective_layout_inputs(dims: &[usize], strides: &[isize]) -> Option<usize> {
467 if dims.len() != strides.len() {
468 return None;
469 }
470
471 let total = total_len(dims)?;
474 if total <= 1 {
475 return Some(total);
476 }
477 if dims
478 .iter()
479 .zip(strides.iter())
480 .any(|(&dim, &stride)| dim > 1 && stride == 0)
481 {
482 return None;
483 }
484
485 let mut min_offset = 0isize;
486 let mut max_offset = 0isize;
487 for (&dim, &stride) in dims.iter().zip(strides.iter()) {
488 if dim <= 1 {
489 continue;
490 }
491 let extent = isize::try_from(dim - 1).ok()?;
492 let span = stride.checked_mul(extent)?;
493 if span >= 0 {
494 max_offset = max_offset.checked_add(span)?;
495 } else {
496 min_offset = min_offset.checked_add(span)?;
497 }
498 }
499 Some(total)
500}
501
502fn total_len(dims: &[usize]) -> Option<usize> {
504 if dims.contains(&0) {
508 return Some(0);
509 }
510 dims.iter()
511 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517
518 #[test]
519 fn disjoint_axes_are_injective() {
520 assert!(is_injective_layout(&[2, 3], &[1, 2]));
521 assert!(is_injective_layout(&[2, 3], &[3, 1]));
522 assert!(is_injective_layout(&[1, 5], &[0, 1]));
523 assert!(is_injective_layout(&[], &[]));
524 }
525
526 #[test]
527 fn overlapping_axes_are_rejected() {
528 assert!(!is_injective_layout(&[2, 3], &[0, 1]));
529 assert!(!is_injective_layout(&[2, 2], &[1, 1]));
530 assert!(!is_injective_layout(&[256, 256], &[256, 256]));
531 assert!(!is_injective_layout(&[3, 3], &[1, 0]));
533 }
534
535 #[test]
536 fn three_axis_interleaved_injective_layout_is_decided_without_alloc() {
537 assert!(is_injective_layout_without_alloc(&[2, 2, 2000], &[4, 5, 6]));
541 assert!(is_injective_layout(&[2, 2, 2000], &[4, 5, 6]));
542 assert!(is_injective_layout_without_alloc(&[3, 1500], &[2000, 3]));
545 assert!(is_injective_layout(&[1500, 3], &[3, 2000]));
546 assert!(!is_injective_layout(&[3, 1500], &[2000, 2]));
547 }
548
549 #[test]
550 fn large_two_axis_injective_layout_is_decided_without_enumeration() {
551 assert!(is_injective_layout(&[3, 6_000_000], &[6_000_001, 3]));
556 assert!(is_injective_layout_without_alloc(
557 &[3, 6_000_000],
558 &[6_000_001, 3]
559 ));
560 assert!(!is_injective_layout(&[3, 6_000_000], &[6_000_003, 3]));
564 assert!(!is_injective_layout_without_alloc(
565 &[3, 6_000_000],
566 &[6_000_003, 3]
567 ));
568 }
569
570 #[test]
571 fn large_three_axis_injective_layout_is_decided_without_enumeration() {
572 assert!(is_injective_layout(&[2, 2, 5_000_000], &[4, 5, 6]));
576 assert!(is_injective_layout_without_alloc(
577 &[2, 2, 5_000_000],
578 &[4, 5, 6]
579 ));
580 assert!(!is_injective_layout(&[2, 2, 5_000_000], &[4, 6, 5]));
582 }
583
584 #[test]
585 fn difference_search_matches_enumeration_on_small_layouts() {
586 let mut checked = 0;
590 for rank in 3..=4usize {
591 let cases = 3usize.pow(rank as u32) * 5usize.pow(rank as u32);
592 for case in 0..cases {
593 let mut code = case;
594 let mut dims = [0usize; 4];
595 let mut strides = [0isize; 4];
596 for axis in 0..rank {
597 dims[axis] = 2 + code % 3;
598 code /= 3;
599 let stride = 1 + (code % 5) as isize;
600 code /= 5;
601 strides[axis] = if axis == 1 { -stride } else { stride };
602 }
603 let (dims, strides) = (&dims[..rank], &strides[..rank]);
604 let Some(Some(block)) = interleaved_block(dims, strides) else {
605 continue;
606 };
607 let Some(fast) = block_differences_avoid_zero(dims, strides, &block) else {
608 continue;
609 };
610 assert_eq!(
611 fast,
612 block_offsets_unique(dims, strides, &block),
613 "{dims:?} {strides:?}"
614 );
615 checked += 1;
616 }
617 }
618 assert!(checked > 1000);
619 }
620
621 #[test]
622 fn checked_total_len_detects_overflow() {
623 assert_eq!(total_len(&[usize::MAX, 2]), None);
624 assert_eq!(total_len(&[3, 4]), Some(12));
625 assert_eq!(total_len(&[usize::MAX, 2, 0]), Some(0));
627 }
628}