Skip to content

Commit

Permalink
Improve and test parachutes
Browse files Browse the repository at this point in the history
  • Loading branch information
Taaitaaiger committed Apr 21, 2023
1 parent 5378804 commit e89eb37
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ tarpaulin-report.html
**/coverage_report.dat
**/coverage_summary.dat

_tests
matplotlibrc
releasenotes.md
8 changes: 4 additions & 4 deletions coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

set -euxo pipefail

JULIA_DIR=$HOME/julia-1.8.3
LD_LIBRARY_PATH=$JULIA_DIR/lib:$JULIA_DIR/lib/julia
export JULIA_DIR=$HOME/julia-1.8.3
export LD_LIBRARY_PATH=$JULIA_DIR/lib:$JULIA_DIR/lib/julia
echo "backend: Gtk3Agg" > matplotlibrc

cargo llvm-cov clean --workspace;
cargo llvm-cov --features full --workspace --no-report -- --test-threads=1
cargo llvm-cov --features full,julia-1-8 --workspace --no-report -- --test-threads=1
cargo llvm-cov --example ccall --no-report -- --test-threads=1
cargo llvm-cov --example ccall_with_threads --no-report -- --test-threads=1
cargo llvm-cov run --example async_tasks --no-report
Expand All @@ -19,4 +19,4 @@ cargo llvm-cov run --example persistent_tasks --no-report
cargo llvm-cov run --example plot --no-report
rm matplotlibrc

cargo llvm-cov --no-run --open --hide-instantiations --ignore-filename-regex "(ptr/internal|jl_sys)"
cargo llvm-cov --no-run --open --hide-instantiations --ignore-filename-regex "(managed/internal|jl_sys)"
2 changes: 2 additions & 0 deletions jlrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ jlrs-macros = { version = "0.1", path = "../jlrs_macros" }
smallvec = "1"
thiserror = "1"
atomic_refcell = "0.1"
atomic = "0.5"
hashers = "1"

async-std = { version = "1.12", features = ["unstable"], optional = true }
async-trait = { version = "0.1", optional = true }
Expand Down
69 changes: 68 additions & 1 deletion jlrs/src/data/layout/foreign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ unsafe impl Sync for ForeignTypes {}

/// A trait that allows arbitrary Rust data to be converted to Julia.
pub unsafe trait ForeignType: Sized + 'static {
#[doc(hidden)]
const TYPE_FN: Option<unsafe fn() -> DataType<'static>> = None;

/// Mark all references to Julia data.
///
/// If a foreign type contains references to Julia data, this method must be overridden.
Expand Down Expand Up @@ -146,6 +149,43 @@ where
target.data_from_ptr(NonNull::new_unchecked(ty), Private)
}

pub(crate) unsafe fn create_foreign_type_internal<'target, U, T>(
target: T,
name: Symbol,
module: Module,
super_type: Option<DataType>,
has_pointers: bool,
large: bool,
) -> DataTypeData<'target, T>
where
U: ForeignType,
T: Target<'target>,
{
let large = large as _;
let has_pointers = has_pointers as _;

unsafe extern "C" fn mark<T: ForeignType>(ptls: PTls, value: *mut jl_value_t) -> usize {
T::mark(ptls, NonNull::new_unchecked(value.cast()).as_ref())
}

unsafe extern "C" fn sweep<T: ForeignType>(value: *mut jl_value_t) {
do_sweep::<T>(NonNull::new_unchecked(value.cast()).as_mut())
}

let ty = jl_new_foreign_type(
name.unwrap(Private),
module.unwrap(Private),
super_type.map_or(null_mut(), |s| s.unwrap(Private)),
Some(mark::<U>),
Some(sweep::<U>),
has_pointers,
large,
);

target.data_from_ptr(NonNull::new_unchecked(ty), Private)
}

// TODO: docs
#[julia_version(since = "1.10")]
pub unsafe fn reinit_foreign_type<U>(datatype: DataType) -> bool
where
Expand Down Expand Up @@ -204,7 +244,34 @@ unsafe impl<F: ForeignType> IntoJulia for F {
unsafe {
let ptls = get_tls();
let sz = std::mem::size_of::<Self>();
let ty = FOREIGN_TYPES.find::<F>().expect("Doesn't exist");
let maybe_ty = FOREIGN_TYPES.find::<F>();

let ty = match maybe_ty {
None => {
if let Some(func) = Self::TYPE_FN {
let mut guard = FOREIGN_TYPES
.data
.write()
.expect("Foreign type lock was poisoned");

// Check again
let tid = TypeId::of::<Self>();
if let Some(ty) = guard.iter().find_map(|s| match s {
&(type_id, ty) if type_id == tid => Some(ty),
_ => None,
}) {
ty
} else {
let ty = func();
guard.push((TypeId::of::<Self>(), ty));
ty
}
} else {
maybe_ty.expect("Doesn't exist")
}
}
Some(t) => t,
};

let ptr: *mut Self = jl_gc_alloc_typed(ptls, sz, ty.unwrap(Private).cast()).cast();
ptr.write(self);
Expand Down
80 changes: 30 additions & 50 deletions jlrs/src/data/managed/parachute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ use std::{
ptr::NonNull,
};

use super::{private::ManagedPriv, Managed};
use super::private::ManagedPriv;
use crate::{
data::{
layout::foreign::{create_foreign_type, ForeignType},
layout::foreign::{create_foreign_type_internal, ForeignType},
managed::{module::Module, symbol::Symbol, value::Value},
},
memory::target::{output::Output, RootingTarget},
memory::target::{unrooted::Unrooted, RootingTarget},
prelude::{DataType, StackFrame},
private::Private,
};

Expand All @@ -43,11 +44,11 @@ use crate::{
///
/// [`Managed`]: crate::data::managed::Managed
/// [module-level docs]: self
pub struct WithParachute<'scope, T: Sync + 'static> {
pub struct WithParachute<'scope, T> {
data: &'scope mut Option<T>,
}

impl<'scope, T: 'static + Sync> WithParachute<'scope, T> {
impl<'scope, T> WithParachute<'scope, T> {
/// Remove the parachute.
///
/// Returns ownership of the data from Julia to Rust.
Expand All @@ -56,14 +57,14 @@ impl<'scope, T: 'static + Sync> WithParachute<'scope, T> {
}
}

impl<'scope, T: 'static + Sync> Deref for WithParachute<'scope, T> {
impl<'scope, T> Deref for WithParachute<'scope, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data.as_ref().expect("Data is None")
}
}

impl<'scope, T: 'static + Sync> DerefMut for WithParachute<'scope, T> {
impl<'scope, T> DerefMut for WithParachute<'scope, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.data.as_mut().expect("Data is None")
}
Expand All @@ -79,9 +80,7 @@ pub trait AttachParachute: 'static + Sized + Sync {
self,
target: T,
) -> WithParachute<'scope, Self> {
// Parachute::<Self>::register(frame)
let mut output = target.into_output();
Parachute::<Self>::register(&mut output);
let output = target.into_output();
let parachute = Parachute { _data: Some(self) };
let data = Value::new(output, parachute);
unsafe {
Expand All @@ -98,52 +97,33 @@ pub(crate) struct Parachute<T: Sync + 'static> {
_data: Option<T>,
}

// Safety: `T` contains no references to Julia data to the default implementation of `mark` is
// Safety: `T` contains no references to Julia data so the default implementation of `mark` is
// correct.
unsafe impl<T: Sync + 'static> ForeignType for Parachute<T> {}

struct UnitHasher(u64);

#[inline(always)]
fn to_8_u8s(data: &[u8]) -> [u8; 8] {
let mut out = [0u8; 8];
for i in 0..data.len().min(8) {
out[i] = data[i];
}
out
unsafe impl<T: Sync + 'static> ForeignType for Parachute<T> {
const TYPE_FN: Option<unsafe fn() -> DataType<'static>> = Some(init_foreign::<Self>);
}

impl Hasher for UnitHasher {
#[inline(always)]
fn finish(&self) -> u64 {
self.0
}

#[inline(always)]
fn write(&mut self, bytes: &[u8]) {
self.0 = u64::from_ne_bytes(to_8_u8s(bytes));
}
}
#[doc(hidden)]
unsafe fn init_foreign<T: ForeignType>() -> DataType<'static> {
let mut hasher = hashers::fnv::FNV1aHasher64::default();
let type_id = TypeId::of::<T>();
type_id.hash(&mut hasher);
let type_id_hash = hasher.finish();

impl<T: Sync + 'static> Parachute<T> {
fn register<'scope>(output: &mut Output<'scope>) {
let type_id = TypeId::of::<T>();
debug_assert_eq!(std::mem::size_of_val(&type_id), 8);
let name = format!("__Parachute_{:x}__", type_id_hash);

let mut hasher = UnitHasher(0);
type_id.hash(&mut hasher);
let hashed = hasher.finish();
let name = format!("__Parachute_{:x}__", hashed);
let sym = Symbol::new(&output, name.as_str());
let module = Module::main(&output);
unsafe {
let unrooted = Unrooted::new();
let sym = Symbol::new(&unrooted, name.as_str());
let module = Module::main(&unrooted);

if module.global(&output, sym).is_ok() {
return;
}
let mut frame = StackFrame::new();
let pinned = frame.pin();
let dt = create_foreign_type_internal::<T, _>(unrooted, sym, module, None, false, false);
pinned.set_sync_root(dt.ptr().as_ptr().cast());
module.set_const_unchecked(sym, dt.as_value());
std::mem::drop(pinned);

unsafe {
let dt = create_foreign_type::<Self, _>(output, sym, module, None, false, false);
module.set_const_unchecked(sym, dt.as_value());
}
DataType::wrap_non_null(dt.ptr(), Private)
}
}
90 changes: 90 additions & 0 deletions jlrs/tests/parachute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
mod util;
#[cfg(feature = "sync-rt")]
mod tests {
use std::sync::{atomic::AtomicU8, Arc};

use atomic::Ordering;
use jlrs::{data::managed::parachute::AttachParachute, memory::gc::Gc, prelude::*};

use super::util::JULIA;

struct SignalsDrop(Arc<AtomicU8>);
impl Drop for SignalsDrop {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}

fn create_parachute() {
JULIA.with(|j| {
let mut frame = StackFrame::new();
let mut jlrs = j.borrow_mut();

jlrs.instance(&mut frame)
.scope(|mut frame| {
let data = vec![1usize, 2usize];
let mut parachute = data.attach_parachute(&mut frame);
assert_eq!(parachute.len(), 2);
parachute.push(3);
assert_eq!(parachute.len(), 3);
Ok(())
})
.unwrap();
});
}

fn remove_parachute() {
JULIA.with(|j| {
let mut frame = StackFrame::new();
let mut jlrs = j.borrow_mut();

jlrs.instance(&mut frame)
.scope(|mut frame| {
let arc = frame.scope(|mut frame| {
let arc = Arc::new(AtomicU8::new(0));
let data = SignalsDrop(arc);
let parachute = data.attach_parachute(&mut frame);
Ok(parachute.remove_parachute())
})?;

assert_eq!(arc.0.fetch_add(1, Ordering::Relaxed), 0);
frame.gc_collect(jlrs::memory::gc::GcCollection::Full);
assert_eq!(arc.0.fetch_add(1, Ordering::Relaxed), 1);

Ok(())
})
.unwrap();
});
}

fn is_dropped() {
JULIA.with(|j| {
let mut frame = StackFrame::new();
let mut jlrs = j.borrow_mut();

jlrs.instance(&mut frame)
.scope(|mut frame| {
let arc = frame.scope(|mut frame| {
let arc = Arc::new(AtomicU8::new(0));
let data = SignalsDrop(arc.clone());
data.attach_parachute(&mut frame);
Ok(arc)
})?;

assert_eq!(arc.fetch_add(1, Ordering::Relaxed), 0);
frame.gc_collect(jlrs::memory::gc::GcCollection::Full);
assert_eq!(arc.fetch_add(1, Ordering::Relaxed), 2);

Ok(())
})
.unwrap();
});
}

#[test]
fn parachute_tests() {
create_parachute();
remove_parachute();
is_dropped();
}
}

0 comments on commit e89eb37

Please sign in to comment.