Skip to content

Commit

Permalink
Moves towards a reference-based implementation.
Browse files Browse the repository at this point in the history
Provides a new `core` module that lays out a new internal design,
and create aliases of the original `ndarray` types using that module.
The module can eventually be moved into its own crate.
  • Loading branch information
akern40 committed Sep 13, 2024
1 parent 5dc62e6 commit 5ae9920
Show file tree
Hide file tree
Showing 5 changed files with 422 additions and 0 deletions.
133 changes: 133 additions & 0 deletions src/core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//! The core types of `ndarray`, redefined in terms of the new infrastructure.
//!
//! The idea is partially that the `core` module / folder could be extracted
//! into its own crate, but these types and implementations would remain here.

mod backend;
mod array;
mod arrayref;

use core::{mem, ptr::NonNull};
use std::sync::Arc;

pub use array::*;
pub use arrayref::*;
pub use backend::*;

use crate::Dimension;

/// Type alias for a dynamically-allocated, unique backend.
pub type VecBackend<T> = (VecOwner<T>, NonNull<T>);

/// This type should act almost entirely equivalently to the existing `Array`
pub type Array<A, D> = OwnedBase<D, VecBackend<A>>;

/// Completely equivalent to [`crate::OwnedRepr`]!
pub struct VecOwner<T>
{
ptr: NonNull<T>,
len: usize,
cap: usize,
}

/// The glue implementation for this backend.
unsafe impl<E> Backend for VecBackend<E>
{
type Elem = E;

type Ref = NonNull<E>;

type Owned = VecOwner<E>;

fn ref_from_owner_offset(owner: &Self::Owned, offset: isize) -> Self::Ref
{
todo!("unsafe {{ owner.ptr.offset(offset) }}")
}
}

/// A very simple Uniqueable, since this type is always unique.
unsafe impl<A, D> Uniqueable for Array<A, D>
{
fn try_ensure_unique(&mut self) {}

fn try_is_unique(&self) -> Option<bool>
{
Some(true)
}
}

/// A blanket NdArray implementation for any backend whose reference type is `NonNull<A>`.
///
/// As described in the `arrayref` module of `core`, this kind of reference-focused `impl`
/// is very ergonomic and makes sense, since most behavior relies on the reference type,
/// not the owned type.
unsafe impl<L, B, A> NdArray<B> for OwnedBase<L, B>
where B: Backend<Ref = NonNull<A>, Elem = A>
{
fn as_ptr(&self) -> *mut <B as Backend>::Elem
{
self.aref.storage.as_ptr()
}
}

/// And now our Arc-based backend
pub type ArcBackend<T> = (Arc<VecOwner<T>>, NonNull<T>);

/// Again, should be almost entirely equivalent to the existing `ArcArray`
pub type ArcArray<A, D> = OwnedBase<D, ArcBackend<A>>;

/// A simple backend implementation
unsafe impl<E> Backend for ArcBackend<E>
{
type Elem = E;

type Ref = NonNull<E>;

type Owned = Arc<VecOwner<E>>;

fn ref_from_owner_offset(owner: &Self::Owned, offset: isize) -> Self::Ref
{
todo!("unsafe {{ owner.as_ref().ptr.offset(isize) }}")
}
}

/// Uniqueable implementation, with the uniqueness logic stolen from what already exists
unsafe impl<A, D: Dimension> Uniqueable for ArcArray<A, D>
{
fn try_ensure_unique(&mut self)
{
if Arc::get_mut(&mut self.own).is_some() {
return;
}
if self.layout.size() <= self.own.len / 2 {
todo!(".to_owned().to_shared()");
}
let ptr = self.as_ptr();
let rcvec = &mut self.own;
let a_size = mem::size_of::<A>() as isize;
let our_off = if a_size != 0 {
(ptr as isize - Arc::as_ptr(&rcvec) as isize) / a_size
} else {
0
};
self.storage = ArcBackend::<A>::ref_from_owner_offset(&mut self.own, our_off);
}

/// We're using strong count here so that we don't need &mut self.
/// This is not as strong as the original `try_is_unique`, but it doesn't matter.
/// The original does not hold on to the mutable reference provided by [`Arc::get_mut`],
/// so the moment the call is over the uniqueness guarantee does not hold.
/// This is a weaker guarantee of instantaneous uniqueness, but neither this nor the
/// original implementation are good enough to safely do anything with that uniqueness anyway.
fn try_is_unique(&self) -> Option<bool>
{
Some(Arc::strong_count(&self.own) == 1)
}
}

/// These aliases work for a view into any array whose reference type is `NonNull`:

pub type ArrayView<'a, A, D> = ViewBase<'a, D, NonNull<A>, A>;
pub type ArrayViewMut<'a, A, D> = ViewBaseMut<'a, D, NonNull<A>, A>;
pub type RawArrayView<A, D> = RawViewBase<D, NonNull<A>, A>;
pub type RawArrayViewMut<A, D> = RawViewBaseMut<D, NonNull<A>, A>;
208 changes: 208 additions & 0 deletions src/core/array.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//! Owning array types.

use core::{fmt::Debug, marker::PhantomData};

use super::{
arrayref::{RawRefBase, RefBase},
backend::Backend,
};

/// Base type for arrays with owning semantics.
pub struct OwnedBase<L, B: Backend>
{
pub(super) aref: RefBase<L, B::Ref>,
pub(super) own: B::Owned,
}

/// Base type for array views.
///
/// Views are like references; in fact, they're just wrappers for references.
/// The difference is that a reference's layout must be identical to the array
/// from which is has been derived; a view's layout may be different, representing
/// a segment, strided, or otherwise incomplete look at its parent array.
#[derive(Debug)]
pub struct ViewBase<'a, L, R, E>
{
aref: RefBase<L, R>,
life: PhantomData<&'a E>,
}

/// Base type for array views with mutable data.
///
/// All kinds of views can have their layout mutated. However, data mutation
/// is tracked separately via two different types.
#[derive(Debug)]
pub struct ViewBaseMut<'a, L, R, E>
{
aref: RefBase<L, R>,
life: PhantomData<&'a mut E>,
}

/// Base type for array views without lifetimes.
#[derive(Debug)]
pub struct RawViewBase<L, R, E>
{
rref: RawRefBase<L, R>,
life: PhantomData<*const E>,
}

/// Base type for array views without lifetimes, but with mutable data.
#[derive(Debug)]
pub struct RawViewBaseMut<L, R, E>
{
rref: RawRefBase<L, R>,
life: PhantomData<*const E>,
}

/// A trait for arrays with mutable data that can be made unique.
///
/// Essentially all monomorphizations of [`OwnedBase`], [`ViewBaseMut`],
/// and [`RawViewBaseMut`] should implement Uniqueable; this applies even
/// when the array type does not have any data sharing capabilities.
///
/// There are already blanket implementations for `ViewBaseMut` and
/// `RawViewBaseMut`; as a result, any creation of these types
/// _must_ ensure that the underlying data is unique (a.k.a, unshared)
/// before creating these mutable views.
pub unsafe trait Uniqueable
{
fn try_ensure_unique(&mut self);

fn try_is_unique(&self) -> Option<bool>;
}

/// Trait implemented by all the non-reference array types.
///
/// We'll probably want to split trait into multiple trait, for three reasons:
/// 1. Adding a "Raw" and "Mut" variants will likely be necessary
/// 2. We probably don't want a single trait specified by backend, but instead traits
/// that are specified by reference type, owned type, and backend separately.
/// This allows for blanket implementations that would otherwise be annoying.
/// 3. We may want to add subtraits that break the behavior into logical pieces,
/// instead of having a monolith.
pub unsafe trait NdArray<B: Backend>
{
fn as_ptr(&self) -> *mut B::Elem;
}

mod array_impls
{
use core::fmt::Debug;
use core::ops::{Deref, DerefMut};

use crate::core::{Backend, RawRefBase, RefBase};

use super::{OwnedBase, RawViewBase, RawViewBaseMut, Uniqueable, ViewBase, ViewBaseMut};

impl<L, B: Backend> Deref for OwnedBase<L, B>
{
type Target = RefBase<L, B::Ref>;

fn deref(&self) -> &Self::Target
{
&self.aref
}
}

impl<L, B: Backend> DerefMut for OwnedBase<L, B>
where Self: Uniqueable
{
fn deref_mut(&mut self) -> &mut Self::Target
{
self.try_ensure_unique();
&mut self.aref
}
}

impl<'a, L, R, E> Deref for ViewBase<'a, L, R, E>
{
type Target = RefBase<L, R>;

fn deref(&self) -> &Self::Target
{
&self.aref
}
}

impl<'a, L, R, E> Deref for ViewBaseMut<'a, L, R, E>
{
type Target = RefBase<L, R>;

fn deref(&self) -> &Self::Target
{
&self.aref
}
}

impl<'a, L, R, E> DerefMut for ViewBaseMut<'a, L, R, E>
{
fn deref_mut(&mut self) -> &mut Self::Target
{
&mut self.aref
}
}

impl<L, R, E> Deref for RawViewBase<L, R, E>
{
type Target = RawRefBase<L, R>;

fn deref(&self) -> &Self::Target
{
&self.rref
}
}

impl<L, R, E> Deref for RawViewBaseMut<L, R, E>
{
type Target = RawRefBase<L, R>;

fn deref(&self) -> &Self::Target
{
&self.rref
}
}

impl<L, R, E> DerefMut for RawViewBaseMut<L, R, E>
{
fn deref_mut(&mut self) -> &mut Self::Target
{
&mut self.rref
}
}

unsafe impl<'a, L, R, E> Uniqueable for ViewBaseMut<'a, L, R, E>
{
fn try_ensure_unique(&mut self) {}

fn try_is_unique(&self) -> Option<bool>
{
Some(true)
}
}

unsafe impl<L, R, E> Uniqueable for RawViewBaseMut<L, R, E>
{
fn try_ensure_unique(&mut self) {}

fn try_is_unique(&self) -> Option<bool>
{
None
}
}

impl<L, B> Debug for OwnedBase<L, B>
where
B: Backend + Debug,
B::Ref: Debug,
B::Owned: Debug,
L: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
{
f.debug_struct("OwnedBase")
.field("aref", &self.aref)
.field("own", &self.own)
.finish()
}
}
}
Loading

0 comments on commit 5ae9920

Please sign in to comment.