use crate::fuse::FusionPlanError;
use crate::hptt::{build_permute_plan, execute_permute_blocked};
use strided_view::{Result, StridedError, StridedView, StridedViewMut};
use threadteam::Exec;
fn plan_error(err: FusionPlanError) -> StridedError {
match err {
FusionPlanError::LengthMismatch { .. } => StridedError::StrideLengthMismatch,
FusionPlanError::DimensionOverflow => StridedError::OffsetOverflow,
}
}
fn total_len(dims: &[usize]) -> usize {
dims.iter().product()
}
fn is_both_contiguous(dims: &[usize], dst_strides: &[isize], src_strides: &[isize]) -> bool {
if dims.is_empty() {
return true;
}
let mut expected = 1isize;
let mut col_ok = true;
for (&d, (&ds, &ss)) in dims.iter().zip(dst_strides.iter().zip(src_strides.iter())) {
if d <= 1 {
continue;
}
if ds != expected || ss != expected {
col_ok = false;
break;
}
expected = expected.saturating_mul(d as isize);
}
if col_ok {
return true;
}
let mut expected = 1isize;
let mut row_ok = true;
for (&d, (&ds, &ss)) in dims
.iter()
.rev()
.zip(dst_strides.iter().rev().zip(src_strides.iter().rev()))
{
if d <= 1 {
continue;
}
if ds != expected || ss != expected {
row_ok = false;
break;
}
expected = expected.saturating_mul(d as isize);
}
row_ok
}
struct RawCopy<'a, T, U> {
dims: &'a [usize],
dst_strides: &'a [isize],
src_strides: &'a [isize],
dst: *mut U,
src: *const T,
}
fn prepare<'a, T, U>(
dest: &'a mut StridedViewMut<U>,
src: &'a StridedView<T>,
) -> Result<Option<RawCopy<'a, T, U>>> {
let dst_dims = dest.dims();
let src_dims = src.dims();
if dst_dims.len() != src_dims.len() {
return Err(StridedError::RankMismatch(dst_dims.len(), src_dims.len()));
}
if dst_dims != src_dims {
return Err(StridedError::ShapeMismatch(
dst_dims.to_vec(),
src_dims.to_vec(),
));
}
if dst_dims.contains(&0) {
return Ok(None);
}
Ok(Some(RawCopy {
dims: dst_dims,
dst_strides: dest.strides(),
src_strides: src.strides(),
dst: dest.as_mut_ptr(),
src: src.ptr(),
}))
}
unsafe fn copy_raw<T: Copy + Send + Sync + 'static, U>(
raw: RawCopy<'_, T, U>,
exec: Exec<'_>,
) -> Result<()> {
let dst_ptr = raw.dst.cast::<T>();
let exec = match raw.dims.iter().try_fold(1usize, |n, &d| n.checked_mul(d)) {
Some(n) => {
let estimate =
std::time::Duration::from_secs_f64(n as f64 * crate::copy_ns_per_element() * 1e-9);
let width = exec.width_for(estimate, 1);
exec.limit(width)
}
None => exec.limit(std::num::NonZeroUsize::MIN),
};
if is_both_contiguous(raw.dims, raw.dst_strides, raw.src_strides) {
let len = total_len(raw.dims);
if exec.is_serial() {
unsafe { std::ptr::copy_nonoverlapping(raw.src, dst_ptr, len) };
} else {
let src_addr = raw.src as usize;
let dst_addr = dst_ptr as usize;
exec.for_ranges(len, std::num::NonZeroUsize::MIN, |range| {
unsafe {
std::ptr::copy_nonoverlapping(
(src_addr as *const T).add(range.start),
(dst_addr as *mut T).add(range.start),
range.len(),
);
}
});
}
return Ok(());
}
let elem_size = std::mem::size_of::<T>();
let plan = build_permute_plan(raw.dims, raw.src_strides, raw.dst_strides, elem_size)
.map_err(plan_error)?;
if !strided_view::is_injective_layout(raw.dims, raw.dst_strides) {
return Err(StridedError::NonInjectiveOutputLayout);
}
execute_permute_blocked(raw.src, dst_ptr, &plan, exec);
Ok(())
}
pub fn copy_into<T: Copy + Send + Sync + 'static>(
dest: &mut StridedViewMut<T>,
src: &StridedView<T>,
exec: Exec<'_>,
) -> Result<()> {
match prepare(dest, src)? {
Some(raw) => unsafe { copy_raw(raw, exec) },
None => Ok(()),
}
}
pub fn copy_into_uninit<T: Copy + Send + Sync + 'static>(
dest: &mut StridedViewMut<core::mem::MaybeUninit<T>>,
src: &StridedView<T>,
exec: Exec<'_>,
) -> Result<()> {
match prepare(dest, src)? {
Some(raw) => unsafe { copy_raw(raw, exec) },
None => Ok(()),
}
}
pub fn copy_into_col_major<T: Copy + Send + Sync + 'static>(
dst: &mut StridedViewMut<T>,
src: &StridedView<T>,
exec: Exec<'_>,
) -> Result<()> {
copy_into(dst, src, exec)
}
#[cfg(test)]
fn try_fuse_group(dims: &[usize], strides: &[isize]) -> Option<(usize, isize)> {
match dims.len() {
0 => Some((1, 0)),
1 => Some((dims[0], strides[0])),
_ => {
if dims.len() != strides.len() {
return None;
}
for (&d, &s) in dims.iter().zip(strides.iter()) {
if d > 1 && s == 0 {
return None;
}
}
let mut base_idx: Option<usize> = None;
let mut base_abs = usize::MAX;
for (i, (&d, &s)) in dims.iter().zip(strides.iter()).enumerate() {
if d <= 1 {
continue;
}
let abs = s.unsigned_abs();
if abs < base_abs {
base_abs = abs;
base_idx = Some(i);
}
}
let Some(base) = base_idx else {
let stride = *strides
.iter()
.min_by_key(|s| s.unsigned_abs())
.unwrap_or(&0);
return Some((dims.iter().product(), stride));
};
let mut used = vec![false; dims.len()];
used[base] = true;
let mut expected_abs = base_abs.checked_mul(dims[base])?;
let non_singleton = dims.iter().filter(|&&d| d > 1).count();
for _ in 1..non_singleton {
let mut next = None;
for i in 0..dims.len() {
if used[i] || dims[i] <= 1 {
continue;
}
if strides[i].unsigned_abs() == expected_abs {
next = Some(i);
break;
}
}
let i = next?;
used[i] = true;
expected_abs = expected_abs.checked_mul(dims[i])?;
}
Some((dims.iter().product(), strides[base]))
}
}
}
#[cfg(test)]
mod tests;