Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate most of CCall in favor of weak_handle #167

Merged
merged 1 commit into from
Mar 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

- `Target::with_local_scope` has been moved to a new trait, `LocalScopeExt`, and like `LocalScope::local_scope` only takes its size as a constant generic. This trait also provides `LocalScopeExt::with_unsized_local_scope`.

- Many functions from `CCall` have been removed or turned into free-standing functions, the remainder has been deprecated. Use the `weak_handle` macro instead.

## v0.21

- Support generating bindings for Julia enums with integer base types in combination with JlrsCore.Reflect and the `Enum` derive macro.
Expand Down
45 changes: 25 additions & 20 deletions examples/ccall_throw_exception.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
//! This example shows how to throw a Julia exception from a `ccall`ed function.

use jlrs::prelude::*;
use jlrs::{prelude::*, runtime::handle::ccall::throw_exception, weak_handle};

// This function returns `nothing` if a < b, throws an `AssertionError` otherwise.
#[no_mangle]
pub unsafe extern "C" fn assert_less_than(a: i32, b: i32) {
let res = CCall::local_scope::<_, _, 2>(|mut frame| {
if a >= b {
let msg = JuliaString::new(&mut frame, "a is larger than b").as_value();
match weak_handle!() {
Ok(handle) => {
let res = handle
.local_scope::<2>(|mut frame| {
if a >= b {
let msg = JuliaString::new(&mut frame, "a is larger than b").as_value();

let leaked = Module::core(&frame)
.global(&frame, "AssertionError")
.expect("AssertionError does not exist in Core")
.as_value()
.cast::<DataType>()
.expect("AssertionError is not a DataType")
.instantiate_unchecked(&mut frame, [msg])
.leak();
let leaked = Module::core(&frame)
.global(&frame, "AssertionError")
.expect("AssertionError does not exist in Core")
.as_value()
.cast::<DataType>()
.expect("AssertionError is not a DataType")
.instantiate_unchecked(&mut frame, [msg])
.leak();

return Ok(Err(leaked));
}
return Err(leaked);
}

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

// Safe: there are no pendings drops.
if let Err(exc) = res {
CCall::throw_exception(exc)
// Safe: there are no pendings drops.
if let Err(exc) = res {
throw_exception(exc)
}
}
Err(_) => panic!("Not called from Julia"),
}
}

Expand Down
39 changes: 33 additions & 6 deletions jlrs/src/catch/impl_stable.rs → jlrs/src/catch.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
//! Try-catch blocks.
//!
//! Many functions in Julia can throw exceptions, jlrs provides checked and unchecked variants of
//! such functions. The checked variant calls the function in a try-catch block and returns a
//! `Result` to indicate whether or not the operation succeeded, while the unchecked variant
//! simply calls the function. If an exception is thrown and it isn't caught the application is
//! aborted. The main disadvantage of the checked variants is that a new try-catch block is
//! created every time the function is called and creating such a block is relatively expensive.
//!
//! Instead of using the checked variants you can create a try-catch block from Rust with
//! [`catch_exceptions`]. This function takes two closures, think of them as the content of the
//! try and catch blocks respectively.
//!
//! Because exceptions work by jumping to the nearest enclosing catch block, you must guarantee
//! that there are no pending drops when an exception is thrown. See this [blog post] for more
//! information.
//!
//! Only local scopes may be created in the try-block, Julia's unwinding mechanism ensures that
//! any scope we jump out of is removed from the GC stack. Dynamic scopes (i.e. scopes that
//! provide a `GcFrame`) depend on `Drop` so jumping out of them is not sound.
//!
//! [blog post]: https://blog.rust-lang.org/inside-rust/2021/01/26/ffi-unwind-longjmp.html#pofs-and-stack-deallocating-functions

use std::{
any::Any,
ffi::c_void,
Expand All @@ -6,7 +29,7 @@ use std::{
ptr::{null_mut, NonNull},
};

use jl_sys::{jlrs_catch_t, jlrs_catch_tag_t, jlrs_try_catch};
use jl_sys::{jl_value_t, jlrs_catch_t, jlrs_catch_tag_t, jlrs_try_catch};

use crate::{
data::managed::{private::ManagedPriv, Managed},
Expand All @@ -22,11 +45,10 @@ use crate::{
///
/// If an exception is thrown, there must be no pending drops. Only local scopes may be created in
/// `func`.
pub unsafe fn catch_exceptions<F, H, T, E>(func: F, exception_handler: H) -> Result<T, E>
where
F: FnOnce() -> T,
H: for<'exc> FnOnce(Value<'exc, 'static>) -> E,
{
pub unsafe fn catch_exceptions<T, E>(
func: impl FnOnce() -> T,
exception_handler: impl for<'exc> FnOnce(Value<'exc, 'static>) -> E,
) -> Result<T, E> {
let mut func = Some(func);
let func = &mut func;
let trampoline = trampoline_for(func).unwrap_unchecked();
Expand Down Expand Up @@ -92,3 +114,8 @@ fn trampoline_for<'frame, F: FnOnce() -> T, T>(
>(Some(trampoline::<F, T>))
}
}

#[inline]
pub(crate) fn unwrap_exc(exc: Value) -> NonNull<jl_value_t> {
exc.unwrap_non_null(Private)
}
37 changes: 0 additions & 37 deletions jlrs/src/catch/mod.rs

This file was deleted.

14 changes: 7 additions & 7 deletions jlrs/src/convert/ccall_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
};
use crate::{
data::{managed::value::ValueRet, types::construct_type::ConstructType},
prelude::{JlrsResult, Nothing},
prelude::{JlrsResult, LocalScope as _, Nothing}, weak_handle_unchecked,
};

/// Trait implemented by types that can be used as argument types of Rust functions exposed by the
Expand Down Expand Up @@ -111,7 +111,7 @@ unsafe impl<T: CCallReturn> CCallReturn for Result<T, ValueRet> {
{
match self {
Ok(t) => t,
Err(e) => crate::runtime::handle::ccall::CCall::throw_exception(e),
Err(e) => crate::runtime::handle::ccall::throw_exception(e),
}
}

Expand Down Expand Up @@ -145,16 +145,16 @@ unsafe impl<T: CCallReturn> CCallReturn for JlrsResult<T> {
match self {
Ok(t) => t,
Err(e) => {
let e = crate::runtime::handle::ccall::CCall::local_scope::<_, _, 1>(
let handle = weak_handle_unchecked!();
let e = handle.local_scope::<1>(
|mut frame| {
let msg = JuliaString::new(&mut frame, format!("{}", e)).as_value();
let err =
JlrsCore::jlrs_error(&frame).instantiate_unchecked(&frame, [msg]);
Ok(err.leak())
err.leak()
},
)
.unwrap();
crate::runtime::handle::ccall::CCall::throw_exception(e)
);
crate::runtime::handle::ccall::throw_exception(e)
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions jlrs/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ pub use crate::memory::stack_frame::StackFrame;
pub use crate::runtime::builder::Builder;
#[cfg(feature = "tokio-rt")]
pub use crate::runtime::executor::tokio_exec::*;
#[cfg(feature = "ccall")]
pub use crate::runtime::handle::ccall::CCall;
#[cfg(feature = "local-rt")]
pub use crate::runtime::sync_rt::{Julia, PendingJulia};
#[cfg(feature = "async")]
Expand Down
42 changes: 15 additions & 27 deletions jlrs/src/runtime/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,20 @@ pub trait Executor<const N: usize>: Send + Sync + 'static {
///
/// Implementations of this method should start a new local runtime. `loop_fn` may block for
/// extended periods of time.
fn block_on<T, F>(&self, loop_fn: F) -> T
where
F: Future<Output = T>;
fn block_on<T>(&self, loop_fn: impl Future<Output = T>) -> T;

/// Spawn `future` as a task on the current executor.
fn spawn_local<F>(future: F) -> Self::JoinHandle
where
F: Future<Output = ()> + 'static;
fn spawn_local(future: impl Future<Output = ()> + 'static) -> Self::JoinHandle;

/// Yield the current task.
fn yield_now() -> impl Future<Output = ()>;

/// Wait on `future` until it resolves or `duration` has elapsed. If the future times out it
/// must return `None`.
fn timeout<F>(
fn timeout(
duration: Duration,
future: F,
) -> impl Future<Output = Option<Result<Message, RecvError>>>
where
F: Future<Output = Result<Message, RecvError>>;
future: impl Future<Output = Result<Message, RecvError>>,
) -> impl Future<Output = Option<Result<Message, RecvError>>>;
}

#[cfg(feature = "tokio-rt")]
Expand Down Expand Up @@ -96,18 +90,18 @@ pub mod tokio_exec {
}

/// See [`tokio::runtime::Builder::on_thread_park`]
pub fn on_thread_park<F: Fn() + Send + Sync + 'static>(
pub fn on_thread_park(
&mut self,
on_thread_park: F,
on_thread_park: impl Fn() + Send + Sync + 'static,
) -> &mut Self {
self.on_thread_park = Some(Arc::new(on_thread_park));
self
}

/// See [`tokio::runtime::Builder::on_thread_unpark`]
pub fn on_thread_unpark<F: Fn() + Send + Sync + 'static>(
pub fn on_thread_unpark(
&mut self,
on_thread_unpark: F,
on_thread_unpark: impl Fn() + Send + Sync + 'static,
) -> &mut Self {
self.on_thread_unpark = Some(Arc::new(on_thread_unpark));
self
Expand All @@ -125,10 +119,7 @@ pub mod tokio_exec {
type JoinHandle = JoinHandle<()>;

#[inline]
fn block_on<T, F>(&self, loop_fn: F) -> T
where
F: Future<Output = T>,
{
fn block_on<T>(&self, loop_fn: impl Future<Output = T>) -> T {
let mut builder = Builder::new_current_thread();
builder.enable_time();

Expand All @@ -154,10 +145,7 @@ pub mod tokio_exec {
}

#[inline]
fn spawn_local<F>(future: F) -> Self::JoinHandle
where
F: Future<Output = ()> + 'static,
{
fn spawn_local(future: impl Future<Output = ()> + 'static) -> Self::JoinHandle {
tokio::task::spawn_local(future)
}

Expand All @@ -166,10 +154,10 @@ pub mod tokio_exec {
tokio::task::yield_now()
}

async fn timeout<F>(duration: Duration, future: F) -> Option<Result<Message, RecvError>>
where
F: Future<Output = Result<Message, RecvError>>,
{
async fn timeout(
duration: Duration,
future: impl Future<Output = Result<Message, RecvError>>,
) -> Option<Result<Message, RecvError>> {
timeout(duration, future).await.ok()
}
}
Expand Down
Loading