use std::{
cell::UnsafeCell,
ffi::c_void,
fmt::{Debug, Display, Formatter, Result as FmtResult},
marker::PhantomData,
mem::MaybeUninit,
ptr::NonNull,
};
#[julia_version(since = "1.11")]
use jl_sys::jl_alloc_array_nd;
#[julia_version(until = "1.10")]
use jl_sys::jl_new_array;
use jl_sys::{
jl_alloc_array_1d, jl_alloc_array_2d, jl_alloc_array_3d, jl_apply_array_type, jl_array_t,
jl_ptr_to_array, jl_ptr_to_array_1d, jl_value_t,
};
use jlrs_macros::julia_version;
use jlrs_sys::jlrs_dimtuple_type;
use super::{ArrayData, sized_dim_tuple, unsized_dim_tuple};
use crate::{
data::{
managed::{datatype::DataTypeData, private::ManagedPriv as _},
types::construct_type::{ArrayTypeConstructor, ConstantIsize, ConstantSize, ConstructType},
},
memory::scope::{LocalScope, LocalScopeExt},
prelude::{Array, Managed, Target, Value, ValueData},
private::Private,
};
pub unsafe trait Dims: Sized + Debug {
const RANK: isize;
const UNKNOWN_RANK: bool = Self::RANK == -1;
type SlicingType<'s>: DimSlice
where
Self: 's;
#[inline]
fn to_dimensions(&self) -> Dimensions {
Dimensions::from_dims(self)
}
fn index_of<D: Dims>(&self, dim_index: &D) -> Option<usize> {
let _: () = <(Self, D) as CompatibleIndices<Self, D>>::ASSERT_COMPATIBLE;
if Self::UNKNOWN_RANK || D::UNKNOWN_RANK {
let n_dims = self.rank();
if n_dims != dim_index.rank() {
return None;
}
if n_dims == 0 {
return Some(0);
}
unsafe {
for dim in 0..n_dims {
if self.n_elements_unchecked(dim) <= dim_index.n_elements_unchecked(dim) {
return None;
}
}
let init = dim_index.n_elements_unchecked(n_dims - 1);
let idx = (0..n_dims - 1).rev().fold(init, |idx_acc, dim| {
idx_acc * self.n_elements_unchecked(dim) + dim_index.n_elements_unchecked(dim)
});
Some(idx)
}
} else {
if Self::RANK == 0 {
return Some(0);
}
unsafe {
for dim in 0..Self::RANK as usize {
if self.n_elements_unchecked(dim) <= dim_index.n_elements_unchecked(dim) {
return None;
}
}
let init = dim_index.n_elements_unchecked(Self::RANK as usize - 1);
let idx = (0..Self::RANK as usize - 1)
.rev()
.fold(init, |idx_acc, dim| {
idx_acc * self.n_elements_unchecked(dim)
+ dim_index.n_elements_unchecked(dim)
});
Some(idx)
}
}
}
unsafe fn index_of_unchecked<D: Dims>(&self, dim_index: &D) -> usize {
unsafe {
let _: () = <(Self, D) as CompatibleIndices<Self, D>>::ASSERT_COMPATIBLE;
let rank = self.rank();
if rank == 0 {
return 0;
}
let init = dim_index.n_elements_unchecked(rank - 1);
let idx = (0..rank - 1).rev().fold(init, |idx_acc, dim| {
idx_acc * self.n_elements_unchecked(dim) + dim_index.n_elements_unchecked(dim)
});
idx
}
}
fn rank(&self) -> usize;
#[inline]
fn n_elements(&self, dimension: usize) -> Option<usize> {
if dimension >= self.rank() {
if dimension == 0 {
return Some(0);
}
return None;
}
unsafe { Some(self.n_elements_unchecked(dimension)) }
}
unsafe fn n_elements_unchecked(&self, dimension: usize) -> usize;
fn size(&self) -> usize {
(0..self.rank())
.map(|i| unsafe { self.n_elements_unchecked(i) })
.product()
}
fn to_slicer<'s>(&'s self) -> Self::SlicingType<'s>;
}
unsafe impl Dims for usize {
const RANK: isize = 1;
type SlicingType<'s> = [usize; 1];
fn to_slicer<'s>(&'s self) -> Self::SlicingType<'s> {
[*self]
}
#[inline]
fn rank(&self) -> usize {
1
}
#[inline]
unsafe fn n_elements_unchecked(&self, _dimension: usize) -> usize {
*self
}
}
unsafe impl<const N: usize> Dims for [usize; N] {
const RANK: isize = N as isize;
type SlicingType<'s> = &'s Self;
fn to_slicer<'s>(&'s self) -> Self::SlicingType<'s> {
self
}
#[inline]
fn rank(&self) -> usize {
N
}
#[inline]
unsafe fn n_elements_unchecked(&self, dimension: usize) -> usize {
unsafe { *self.get_unchecked(dimension) }
}
}
unsafe impl<const N: usize> Dims for &[usize; N] {
const RANK: isize = N as isize;
type SlicingType<'s>
= Self
where
Self: 's;
fn to_slicer<'s>(&'s self) -> Self::SlicingType<'s> {
self
}
#[inline]
fn rank(&self) -> usize {
N
}
#[inline]
unsafe fn n_elements_unchecked(&self, dimension: usize) -> usize {
unsafe { *self.get_unchecked(dimension) }
}
}
unsafe impl Dims for &[usize] {
const RANK: isize = -1;
type SlicingType<'s>
= Self
where
Self: 's;
fn to_slicer<'s>(&'s self) -> Self::SlicingType<'s> {
self
}
#[inline]
fn rank(&self) -> usize {
self.len()
}
#[inline]
unsafe fn n_elements_unchecked(&self, dimension: usize) -> usize {
unsafe { *self.get_unchecked(dimension) }
}
}
unsafe impl<const N: isize> Dims for ArrayDimensions<'_, N> {
const RANK: isize = N;
type SlicingType<'s>
= &'s [usize]
where
Self: 's;
fn to_slicer<'s>(&'s self) -> Self::SlicingType<'s> {
let slice = self.as_slice();
let len = slice.len();
let ptr = slice.as_ptr();
unsafe { std::slice::from_raw_parts(ptr.cast(), len) }
}
#[inline]
fn rank(&self) -> usize {
if N >= 0 {
return N as usize;
}
self.as_slice().len()
}
#[inline]
unsafe fn n_elements_unchecked(&self, dimension: usize) -> usize {
unsafe { self.as_slice().get_unchecked(dimension).get().read() }
}
}
unsafe impl Dims for Dimensions {
const RANK: isize = -1;
type SlicingType<'s> = &'s Self;
fn to_slicer<'s>(&'s self) -> Self::SlicingType<'s> {
self
}
#[inline]
fn rank(&self) -> usize {
self.as_slice().len()
}
#[inline]
unsafe fn n_elements_unchecked(&self, dimension: usize) -> usize {
unsafe { *self.as_slice().get_unchecked(dimension) }
}
}
pub trait DimSlice {
fn as_slice(&self) -> &[usize];
}
impl<const N: usize> DimSlice for [usize; N] {
fn as_slice(&self) -> &[usize] {
&self[..]
}
}
impl<const N: usize> DimSlice for &[usize; N] {
fn as_slice(&self) -> &[usize] {
&self[..]
}
}
impl DimSlice for &[usize] {
fn as_slice(&self) -> &[usize] {
self
}
}
impl DimSlice for &Dimensions {
fn as_slice(&self) -> &[usize] {
Dimensions::as_slice(self)
}
}
pub unsafe trait DimsExt: Dims {
fn array_type<'target, T, Tgt>(&self, target: Tgt) -> ValueData<'target, 'static, Tgt>
where
T: ConstructType,
Tgt: Target<'target>,
{
target.with_local_scope::<_, 1>(|target, mut frame| {
let n = self.rank();
let elem_ty = T::construct_type(&mut frame);
unsafe {
let ty = jl_apply_array_type(elem_ty.unwrap(Private), n);
target.data_from_ptr(NonNull::new_unchecked(ty), Private)
}
})
}
fn fill_tuple(&self, tup: &mut [MaybeUninit<usize>], _: Private);
#[doc(hidden)]
#[inline]
unsafe fn alloc_array<'target, Tgt>(
&self,
target: Tgt,
array_type: Value,
) -> ArrayData<'target, 'static, Tgt>
where
Tgt: Target<'target>,
{
unsafe {
let array_type = array_type.unwrap(Private);
let arr = match Self::RANK {
1 => jl_alloc_array_1d(array_type, self.n_elements_unchecked(0)),
2 => jl_alloc_array_2d(
array_type,
self.n_elements_unchecked(0),
self.n_elements_unchecked(1),
),
3 => jl_alloc_array_3d(
array_type,
self.n_elements_unchecked(0),
self.n_elements_unchecked(1),
self.n_elements_unchecked(2),
),
_ => self.alloc_large(array_type, &target),
};
Array::wrap_non_null(NonNull::new_unchecked(arr), Private).root(target)
}
}
#[cold]
#[doc(hidden)]
#[inline(never)]
#[julia_version(until = "1.10")]
unsafe fn alloc_large<'target, Tgt>(
&self,
array_type: *mut jl_value_t,
target: &Tgt,
) -> *mut jl_array_t
where
Tgt: Target<'target>,
{
target.local_scope::<_, 1>(|mut frame| unsafe {
let tuple = unsized_dim_tuple(&frame, self);
tuple.root(&mut frame);
jl_new_array(array_type, tuple.ptr().as_ptr())
})
}
#[cold]
#[doc(hidden)]
#[inline(never)]
#[julia_version(since = "1.11")]
unsafe fn alloc_large<'target, Tgt>(
&self,
array_type: *mut jl_value_t,
_target: &Tgt,
) -> *mut jl_array_t
where
Tgt: Target<'target>,
{
unsafe {
let slicer = self.to_slicer();
let slice = slicer.as_slice();
let len = slice.len();
let ptr = slice.as_ptr();
jl_alloc_array_nd(array_type, ptr as *mut _, len)
}
}
#[doc(hidden)]
#[inline]
unsafe fn alloc_array_with_data<'target, 'data, Tgt: Target<'target>>(
&self,
target: Tgt,
array_type: Value,
data: *mut c_void,
) -> ArrayData<'target, 'data, Tgt> {
unsafe {
let array_type = array_type.unwrap(Private);
let arr = match self.rank() {
1 => jl_ptr_to_array_1d(array_type, data, self.n_elements_unchecked(0), 0),
_ => target.local_scope::<_, 1>(|frame| {
let tuple = unsized_dim_tuple(frame, self);
jl_ptr_to_array(array_type, data, tuple.unwrap(Private), 0)
}),
};
target.data_from_ptr(NonNull::new_unchecked(arr), Private)
}
}
#[doc(hidden)]
#[inline]
fn dimension_object<'target, Tgt: Target<'target>>(
&self,
target: Tgt,
) -> DataTypeData<'target, Tgt> {
let rank = self.rank();
unsafe {
let raw = jlrs_dimtuple_type(rank);
target.data_from_ptr(NonNull::new_unchecked(raw), Private)
}
}
}
unsafe impl<D: RankedDims> DimsExt for D {
#[inline]
fn array_type<'target, T, Tgt>(&self, target: Tgt) -> ValueData<'target, 'static, Tgt>
where
T: ConstructType,
Tgt: Target<'target>,
{
<<Self as RankedDims>::ArrayConstructor<T>>::construct_type(target)
}
#[inline]
fn fill_tuple(&self, tup: &mut [MaybeUninit<usize>], _: Private) {
<Self as RankedDims>::fill_tuple(self, tup, Private)
}
}
unsafe impl DimsExt for &[usize] {
#[inline]
fn fill_tuple(&self, tup: &mut [MaybeUninit<usize>], _: Private) {
let n = self.len();
let ptr = self.as_ptr().cast::<MaybeUninit<usize>>();
unsafe {
let slice = std::slice::from_raw_parts(ptr, n);
tup.copy_from_slice(slice);
}
}
}
unsafe impl DimsExt for Dimensions {
#[inline]
fn fill_tuple(&self, tup: &mut [MaybeUninit<usize>], _: Private) {
let n = self.rank();
let slice = self.as_slice();
let ptr = slice.as_ptr().cast::<MaybeUninit<usize>>();
unsafe {
let slice = std::slice::from_raw_parts(ptr, n);
tup.copy_from_slice(slice);
}
}
}
pub unsafe trait RankedDims: Dims {
const ASSERT_RANKED: () = assert!(Self::RANK != -1);
type ArrayConstructor<T: ConstructType>: ConstructType;
fn fill_tuple(&self, tup: &mut [MaybeUninit<usize>], _: Private);
#[doc(hidden)]
#[inline]
unsafe fn alloc_array<'target, Tgt>(
&self,
target: Tgt,
array_type: Value,
) -> ArrayData<'target, 'static, Tgt>
where
Tgt: Target<'target>,
{
unsafe {
let _: () = Self::ASSERT_RANKED;
let array_type = array_type.unwrap(Private);
let arr = match Self::RANK {
1 => jl_alloc_array_1d(array_type, self.n_elements_unchecked(0)),
2 => jl_alloc_array_2d(
array_type,
self.n_elements_unchecked(0),
self.n_elements_unchecked(1),
),
3 => jl_alloc_array_3d(
array_type,
self.n_elements_unchecked(0),
self.n_elements_unchecked(1),
self.n_elements_unchecked(2),
),
_ => self.alloc_large(array_type, &target),
};
Array::wrap_non_null(NonNull::new_unchecked(arr), Private).root(target)
}
}
#[cold]
#[doc(hidden)]
#[inline(never)]
#[julia_version(until = "1.10")]
unsafe fn alloc_large<'target, Tgt>(
&self,
array_type: *mut jl_value_t,
target: &Tgt,
) -> *mut jl_array_t
where
Tgt: Target<'target>,
{
let _: () = Self::ASSERT_RANKED;
target.local_scope::<_, 1>(|mut frame| unsafe {
let tuple = sized_dim_tuple(&frame, self);
tuple.root(&mut frame);
jl_new_array(array_type, tuple.ptr().as_ptr())
})
}
#[cold]
#[doc(hidden)]
#[inline(never)]
#[julia_version(since = "1.11")]
unsafe fn alloc_large<'target, Tgt>(
&self,
array_type: *mut jl_value_t,
_target: &Tgt,
) -> *mut jl_array_t
where
Tgt: Target<'target>,
{
unsafe {
let _: () = Self::ASSERT_RANKED;
let slicer = self.to_slicer();
let slice = slicer.as_slice();
let len = slice.len();
let ptr = slice.as_ptr();
jl_alloc_array_nd(array_type, ptr as *mut _, len)
}
}
#[doc(hidden)]
#[inline]
unsafe fn alloc_array_with_data<'target, 'data, Tgt: Target<'target>>(
&self,
target: Tgt,
array_type: Value,
data: *mut c_void,
) -> ArrayData<'target, 'data, Tgt> {
unsafe {
let _: () = Self::ASSERT_RANKED;
let array_type = array_type.unwrap(Private);
let arr = match Self::RANK {
1 => jl_ptr_to_array_1d(array_type, data, self.n_elements_unchecked(0), 0),
_ => target.local_scope::<_, 1>(|frame| {
let tuple = sized_dim_tuple(frame, self);
jl_ptr_to_array(array_type, data, tuple.unwrap(Private), 0)
}),
};
target.data_from_ptr(NonNull::new_unchecked(arr), Private)
}
}
#[doc(hidden)]
#[inline]
fn dimension_object<'target, Tgt: Target<'target>>(
&self,
target: Tgt,
) -> DataTypeData<'target, Tgt> {
let _: () = Self::ASSERT_RANKED;
let rank = Self::RANK;
unsafe {
let raw = jlrs_dimtuple_type(rank as _);
target.data_from_ptr(NonNull::new_unchecked(raw), Private)
}
}
}
unsafe impl RankedDims for usize {
type ArrayConstructor<T: ConstructType> = ArrayTypeConstructor<T, ConstantIsize<1>>;
#[inline]
fn fill_tuple(&self, tup: &mut [MaybeUninit<usize>], _: Private) {
tup[0].write(*self);
}
}
unsafe impl<const N: usize> RankedDims for &[usize; N] {
type ArrayConstructor<T: ConstructType> = ArrayTypeConstructor<T, ConstantSize<N>>;
#[inline]
fn fill_tuple(&self, tup: &mut [MaybeUninit<usize>], _: Private) {
let data = unsafe { &*(*self as *const [usize; N] as *const [MaybeUninit<usize>; N]) };
tup.copy_from_slice(data);
}
}
unsafe impl<const N: usize> RankedDims for [usize; N] {
type ArrayConstructor<T: ConstructType> = ArrayTypeConstructor<T, ConstantSize<N>>;
#[inline]
fn fill_tuple(&self, tup: &mut [MaybeUninit<usize>], _: Private) {
let data = unsafe { &*(self as *const [usize; N] as *const [MaybeUninit<usize>; N]) };
tup.copy_from_slice(data);
}
}
#[derive(Debug)]
pub struct ArrayDimensions<'borrow, const N: isize> {
dims: &'borrow [UnsafeCell<usize>],
}
impl<'borrow, const N: isize> ArrayDimensions<'borrow, N> {
#[inline]
pub(crate) fn new(dims: &'borrow [UnsafeCell<usize>]) -> Self {
ArrayDimensions { dims }
}
#[inline]
pub(crate) fn as_slice(&self) -> &'borrow [UnsafeCell<usize>] {
&self.dims
}
}
#[derive(Clone)]
pub enum Dimensions {
#[doc(hidden)]
Few([usize; 4]),
#[doc(hidden)]
Many(Box<[usize]>),
}
impl Dimensions {
pub fn from_dims<D: Dims>(dims: &D) -> Self {
unsafe {
match dims.rank() {
0 => Dimensions::Few([0, 0, 0, 0]),
1 => Dimensions::Few([1, dims.n_elements_unchecked(0), 0, 0]),
2 => Dimensions::Few([
2,
dims.n_elements_unchecked(0),
dims.n_elements_unchecked(1),
0,
]),
3 => Dimensions::Few([
3,
dims.n_elements_unchecked(0),
dims.n_elements_unchecked(1),
dims.n_elements_unchecked(2),
]),
n => {
let mut v = Vec::with_capacity(n + 1);
v.push(n);
let iter = (0..n).map(|dim| dims.n_elements_unchecked(dim));
v.extend(iter);
Dimensions::Many(v.into_boxed_slice())
}
}
}
}
#[inline]
pub fn as_slice(&self) -> &[usize] {
match self {
Dimensions::Few(v) => &v[1..v[0] as usize + 1],
Dimensions::Many(v) => &v[1..],
}
}
}
impl Debug for Dimensions {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let mut f = f.debug_tuple("Dimensions");
for d in self.as_slice() {
f.field(&d);
}
f.finish()
}
}
impl Display for Dimensions {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let mut f = f.debug_tuple("");
for d in self.as_slice() {
f.field(&d);
}
f.finish()
}
}
pub trait CompatibleIndices<A: Dims, B: Dims>: private::CompatibleIndicesPriv {
const ASSERT_COMPATIBLE: () = assert!(
A::RANK == -1 || B::RANK == -1 || A::RANK == B::RANK,
"The rank of the dimensions is incompatible with the rank of the array"
);
}
impl<A: Dims, B: Dims> CompatibleIndices<A, B> for (A, B) {}
pub trait DimsRankCheck<D: Dims, const N: isize> {
const ASSERT_VALID_RANK: () = assert!(
D::RANK == N || N == -1 || D::RANK == -1,
"The rank of the dimensions is incompatible with the rank of the array"
);
const NEEDS_RUNTIME_RANK_CHECK: bool = N != -1 && D::RANK == -1;
}
pub struct DimsRankAssert<D: Dims, const N: isize>(PhantomData<D>);
impl<D: Dims, const N: isize> DimsRankCheck<D, N> for DimsRankAssert<D, N> {}
pub(crate) mod private {
use super::Dims;
pub trait CompatibleIndicesPriv {}
impl<A: Dims, B: Dims> CompatibleIndicesPriv for (A, B) {}
}
#[cfg(test)]
mod tests {
use crate::data::managed::array::dimensions::{Dimensions, Dims};
#[test]
fn convert_usize() {
let d: Dimensions = 4.to_dimensions();
assert_eq!(d.rank(), 1);
assert_eq!(d.n_elements(0), Some(4));
assert_eq!(d.size(), 4);
}
#[test]
fn convert_array_5d() {
let d: Dimensions = (&[4, 3, 2, 1, 2]).to_dimensions();
assert_eq!(d.rank(), 5);
assert_eq!(d.n_elements(0), Some(4));
assert_eq!(d.n_elements(1), Some(3));
assert_eq!(d.n_elements(2), Some(2));
assert_eq!(d.n_elements(3), Some(1));
assert_eq!(d.n_elements(4), Some(2));
assert_eq!(d.size(), 48);
}
#[test]
fn convert_array_nd() {
let v = &[1, 2, 3][..];
let d: Dimensions = v.to_dimensions();
assert_eq!(d.rank(), 3);
assert_eq!(d.n_elements(0), Some(1));
assert_eq!(d.n_elements(1), Some(2));
assert_eq!(d.n_elements(2), Some(3));
assert_eq!(d.size(), 6);
}
}