1pub fn is_injective_layout(dims: &[usize], strides: &[isize]) -> bool {
35 is_injective_layout_without_alloc(dims, strides) || is_injective_layout_exact(dims, strides)
36}
37
38#[doc(hidden)]
47pub fn is_injective_layout_without_alloc(dims: &[usize], strides: &[isize]) -> bool {
48 let Some(total) = validate_injective_layout_inputs(dims, strides) else {
49 return false;
50 };
51 if total <= 1 {
52 return true;
53 }
54 match interleaved_block(dims, strides) {
55 None => false,
56 Some(None) => true,
57 Some(Some(block)) => {
58 if !block.may_be_injective() {
59 return false;
61 }
62 if let Some(injective) = two_axis_block_is_injective(dims, strides, &block) {
63 return injective;
64 }
65 block.total <= PAIRWISE_BLOCK_BUDGET as u128
66 && block_offsets_unique_pairwise(dims, strides, &block)
67 }
68 }
69}
70
71fn is_injective_layout_exact(dims: &[usize], strides: &[isize]) -> bool {
73 let Some(total) = validate_injective_layout_inputs(dims, strides) else {
74 return false;
75 };
76 if total <= 1 {
77 return true;
78 }
79 match interleaved_block(dims, strides) {
80 None => false,
81 Some(None) => true,
82 Some(Some(block)) => {
83 if !block.may_be_injective() {
84 return false;
86 }
87 if let Some(injective) = two_axis_block_is_injective(dims, strides, &block) {
88 return injective;
89 }
90 block.total <= EXACT_BLOCK_BUDGET as u128 && block_offsets_unique(dims, strides, &block)
91 }
92 }
93}
94
95const EXACT_BLOCK_BUDGET: usize = 1 << 24;
103
104const PAIRWISE_BLOCK_BUDGET: usize = 4096;
109
110struct InterleavedBlock {
112 last_key: (u128, usize),
113 total: u128,
115 span: u128,
118}
119
120impl InterleavedBlock {
121 fn contains(&self, axis: usize, dim: usize, stride: isize) -> bool {
122 dim > 1 && (stride.unsigned_abs() as u128, axis) <= self.last_key
123 }
124
125 fn may_be_injective(&self) -> bool {
127 self.total <= self.span + 1
128 }
129}
130
131fn two_axis_block_is_injective(
142 dims: &[usize],
143 strides: &[isize],
144 block: &InterleavedBlock,
145) -> Option<bool> {
146 let mut axes = [(0usize, 0usize); 2]; let mut count = 0;
148 for (axis, (&dim, &stride)) in dims.iter().zip(strides.iter()).enumerate() {
149 if !block.contains(axis, dim, stride) {
150 continue;
151 }
152 if count == 2 {
153 return None;
154 }
155 axes[count] = (dim, stride.unsigned_abs());
156 count += 1;
157 }
158 if count != 2 {
159 return None;
160 }
161 let (extent_lo, stride_lo, extent_hi, stride_hi) = if axes[0].1 <= axes[1].1 {
163 (
164 axes[0].0 as u128,
165 axes[0].1 as u128,
166 axes[1].0 as u128,
167 axes[1].1 as u128,
168 )
169 } else {
170 (
171 axes[1].0 as u128,
172 axes[1].1 as u128,
173 axes[0].0 as u128,
174 axes[0].1 as u128,
175 )
176 };
177 let g = gcd(stride_lo, stride_hi);
178 debug_assert!(g > 0, "stride zero on a non-singleton axis was rejected");
179 Some(stride_hi / g >= extent_lo || stride_lo / g >= extent_hi)
180}
181
182fn gcd(mut a: u128, mut b: u128) -> u128 {
184 while b != 0 {
185 let remainder = a % b;
186 a = b;
187 b = remainder;
188 }
189 a
190}
191
192fn interleaved_block(dims: &[usize], strides: &[isize]) -> Option<Option<InterleavedBlock>> {
199 let mut covered_span = 0u128;
200 let mut covered_total = 1u128;
201 let mut previous_key = None;
202 let mut block = None;
203 let active_axes = dims.iter().filter(|&&dim| dim > 1).count();
204 for _ in 0..active_axes {
205 let mut next = None;
206 for (axis, (&dim, &stride)) in dims.iter().zip(strides.iter()).enumerate() {
207 if dim <= 1 {
208 continue;
209 }
210 let key = (stride.checked_abs()? as u128, axis);
211 if previous_key.is_some_and(|previous| key <= previous) {
212 continue;
213 }
214 if next.is_none_or(|(best, _)| key < best) {
215 next = Some((key, dim as u128));
216 }
217 }
218 let ((stride, axis), dim) = next?;
219 let separated = stride > covered_span;
220 covered_span = stride
221 .checked_mul(dim - 1)
222 .and_then(|span| covered_span.checked_add(span))?;
223 covered_total = covered_total.checked_mul(dim)?;
224 if !separated {
225 block = Some(InterleavedBlock {
226 last_key: (stride, axis),
227 total: covered_total,
228 span: covered_span,
229 });
230 }
231 previous_key = Some((stride, axis));
232 }
233 Some(block)
234}
235
236fn block_offsets_unique(dims: &[usize], strides: &[isize], block: &InterleavedBlock) -> bool {
238 let axes: Vec<(usize, usize)> = dims
239 .iter()
240 .zip(strides.iter())
241 .enumerate()
242 .filter(|&(axis, (&dim, &stride))| block.contains(axis, dim, stride))
243 .map(|(_, (&dim, &stride))| (dim, stride.unsigned_abs()))
244 .collect();
245 let (Ok(total), Ok(span)) = (usize::try_from(block.total), usize::try_from(block.span)) else {
246 return false;
247 };
248
249 let mut indices = vec![0usize; axes.len()];
253 let mut offset = 0usize;
254 let mut advance = |offset: &mut usize| {
255 for (index, &(dim, stride)) in indices.iter_mut().zip(axes.iter()) {
256 if *index + 1 < dim {
257 *index += 1;
258 *offset += stride;
259 return;
260 }
261 *offset -= stride * (dim - 1);
262 *index = 0;
263 }
264 };
265
266 if block.span / 64 <= block.total {
267 let mut seen = vec![0u64; span / 64 + 1];
268 for _ in 0..total {
269 let (word, bit) = (offset / 64, 1u64 << (offset % 64));
270 if seen[word] & bit != 0 {
271 return false;
272 }
273 seen[word] |= bit;
274 advance(&mut offset);
275 }
276 true
277 } else {
278 let mut offsets = Vec::with_capacity(total);
279 for _ in 0..total {
280 offsets.push(offset);
281 advance(&mut offset);
282 }
283 offsets.sort_unstable();
284 offsets.windows(2).all(|pair| pair[0] != pair[1])
285 }
286}
287
288fn block_offset_for_linear_index(
289 dims: &[usize],
290 strides: &[isize],
291 block: &InterleavedBlock,
292 mut linear: usize,
293) -> u128 {
294 let mut offset = 0u128;
295 for (axis, (&dim, &stride)) in dims.iter().zip(strides.iter()).enumerate() {
296 if !block.contains(axis, dim, stride) {
297 continue;
298 }
299 offset += stride.unsigned_abs() as u128 * (linear % dim) as u128;
300 linear /= dim;
301 }
302 offset
303}
304
305fn block_offsets_unique_pairwise(
306 dims: &[usize],
307 strides: &[isize],
308 block: &InterleavedBlock,
309) -> bool {
310 let Ok(total) = usize::try_from(block.total) else {
311 return false;
312 };
313 for lhs in 0..total {
314 let lhs_offset = block_offset_for_linear_index(dims, strides, block, lhs);
315 for rhs in (lhs + 1)..total {
316 if block_offset_for_linear_index(dims, strides, block, rhs) == lhs_offset {
317 return false;
318 }
319 }
320 }
321 true
322}
323
324fn validate_injective_layout_inputs(dims: &[usize], strides: &[isize]) -> Option<usize> {
325 if dims.len() != strides.len() {
326 return None;
327 }
328
329 let total = total_len(dims)?;
332 if total <= 1 {
333 return Some(total);
334 }
335 if dims
336 .iter()
337 .zip(strides.iter())
338 .any(|(&dim, &stride)| dim > 1 && stride == 0)
339 {
340 return None;
341 }
342
343 let mut min_offset = 0isize;
344 let mut max_offset = 0isize;
345 for (&dim, &stride) in dims.iter().zip(strides.iter()) {
346 if dim <= 1 {
347 continue;
348 }
349 let extent = isize::try_from(dim - 1).ok()?;
350 let span = stride.checked_mul(extent)?;
351 if span >= 0 {
352 max_offset = max_offset.checked_add(span)?;
353 } else {
354 min_offset = min_offset.checked_add(span)?;
355 }
356 }
357 Some(total)
358}
359
360fn total_len(dims: &[usize]) -> Option<usize> {
362 if dims.contains(&0) {
366 return Some(0);
367 }
368 dims.iter()
369 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn disjoint_axes_are_injective() {
378 assert!(is_injective_layout(&[2, 3], &[1, 2]));
379 assert!(is_injective_layout(&[2, 3], &[3, 1]));
380 assert!(is_injective_layout(&[1, 5], &[0, 1]));
381 assert!(is_injective_layout(&[], &[]));
382 }
383
384 #[test]
385 fn overlapping_axes_are_rejected() {
386 assert!(!is_injective_layout(&[2, 3], &[0, 1]));
387 assert!(!is_injective_layout(&[2, 2], &[1, 1]));
388 assert!(!is_injective_layout(&[256, 256], &[256, 256]));
389 assert!(!is_injective_layout(&[3, 3], &[1, 0]));
391 }
392
393 #[test]
394 fn three_axis_interleaved_injective_layout_needs_the_exact_spill_path() {
395 assert!(is_injective_layout_without_alloc(&[2, 2, 2000], &[4, 5, 6]) == false);
398 assert!(is_injective_layout(&[2, 2, 2000], &[4, 5, 6]));
399 assert!(is_injective_layout_without_alloc(&[3, 1500], &[2000, 3]));
402 assert!(is_injective_layout(&[1500, 3], &[3, 2000]));
403 assert!(!is_injective_layout(&[3, 1500], &[2000, 2]));
404 }
405
406 #[test]
407 fn large_two_axis_injective_layout_is_decided_without_enumeration() {
408 assert!(is_injective_layout(&[3, 6_000_000], &[6_000_001, 3]));
413 assert!(is_injective_layout_without_alloc(
414 &[3, 6_000_000],
415 &[6_000_001, 3]
416 ));
417 assert!(!is_injective_layout(&[3, 6_000_000], &[6_000_003, 3]));
421 assert!(!is_injective_layout_without_alloc(
422 &[3, 6_000_000],
423 &[6_000_003, 3]
424 ));
425 }
426
427 #[test]
428 fn checked_total_len_detects_overflow() {
429 assert_eq!(total_len(&[usize::MAX, 2]), None);
430 assert_eq!(total_len(&[3, 4]), Some(12));
431 assert_eq!(total_len(&[usize::MAX, 2, 0]), Some(0));
433 }
434}