1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
//! Interact with Julia when calling Rust from Julia.
//!
//! This module is only available if the `ccall` feature is enabled.
use jl_sys::jl_throw;
#[cfg(feature = "uv")]
use jl_sys::uv_async_send;
use crate::{
error::JlrsResult,
memory::{
context::stack::Stack,
stack_frame::{PinnedFrame, StackFrame},
target::{frame::GcFrame, unrooted::Unrooted},
},
private::Private,
wrappers::ptr::{
private::WrapperPriv,
value::{Value, ValueRef},
},
};
/// Use Julia from a Rust function called through `ccall`.
///
/// When you call Rust from Julia through `ccall`, Julia has already been initialized and trying to
/// initialize it again causes a crash. In order to still be able to call Julia from Rust
/// you must create a scope first. You can use this struct to do so. It must never be used outside
/// functions called through `ccall`, and only once for each `ccall`ed function.
///
/// Exceptions must only be thrown by calling [`CCall::throw_exception`].
pub struct CCall<'context> {
frame: PinnedFrame<'context, 0>,
}
impl<'context> CCall<'context> {
/// Create a new `CCall`
///
/// Safety: This function must never be called outside a function called through `ccall` from
/// Julia and must only be called once during that call.
pub unsafe fn new(frame: &'context mut StackFrame<0>) -> Self {
CCall { frame: frame.pin() }
}
/// Wake the task associated with `handle`.
///
/// The handle must be the `handle` field of a `Base.AsyncCondition` in Julia. This can be
/// used to call a long-running Rust function from Julia with `ccall` in another thread and
/// wait for it to complete in Julia without blocking, an example is available in the
/// repository: `ccall_with_threads`.
///
/// This method is only available if the `uv` feature is enabled.
///
/// Safety: the handle must be acquired from an `AsyncCondition`.
#[cfg(feature = "uv")]
pub unsafe fn uv_async_send(handle: *mut std::ffi::c_void) -> bool {
uv_async_send(handle.cast()) == 0
}
/// Create a [`GcFrame`], call the given closure, and return its result.
pub fn scope<T, F>(&mut self, func: F) -> JlrsResult<T>
where
for<'scope> F: FnOnce(GcFrame<'scope>) -> JlrsResult<T>,
{
unsafe {
let stack = self.frame.stack_frame().sync_stack();
let (owner, frame) = GcFrame::base(stack);
let ret = func(frame);
std::mem::drop(owner);
ret
}
}
/// Create and throw an exception.
///
/// This method calls `func` and throws the result as a Julia exception.
///
/// Safety:
///
/// Julia exceptions are implemented with `setjmp` / `longjmp`. This means that when an
/// exception is thrown, control flow is returned to a `catch` block by jumping over
/// intermediate stack frames. It's undefined behaviour to jump over frames that have pending
/// drops, so you must take care to structure your code such that none of the intermediate
/// frames have any pending drops.
#[inline(never)]
pub unsafe fn throw_exception<F>(mut self, func: F)
where
F: for<'scope> FnOnce(&mut GcFrame<'scope>) -> Value<'scope, 'static>,
{
let exception = construct_exception(self.frame.stack_frame().sync_stack(), func);
// catch unwinds the GC stack, so it's okay to forget self.
std::mem::forget(self);
jl_throw(exception.ptr().as_ptr());
unreachable!()
}
/// Create an [`Unrooted`], call the given closure, and return its result.
///
/// Unlike [`CCall::scope`] this method doesn't allocate a stack.
///
/// Safety: must only be called from a `ccall`ed function that doesn't need to root any data.
pub unsafe fn stackless_scope<T, F>(func: F) -> JlrsResult<T>
where
for<'scope> F: FnOnce(Unrooted<'scope>) -> JlrsResult<T>,
{
func(Unrooted::new())
}
}
#[inline(never)]
unsafe fn construct_exception<'stack, F>(stack: &'stack Stack, func: F) -> ValueRef<'stack, 'static>
where
for<'scope> F: FnOnce(&mut GcFrame<'scope>) -> Value<'scope, 'static>,
{
let (owner, mut frame) = GcFrame::base(stack);
let ret = func(&mut frame);
let rewrapped = ValueRef::wrap(ret.unwrap_non_null(Private));
std::mem::drop(owner);
rewrapped
}