use std::ffi::c_void;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use crate::{
ABI_VERSION, ERR_EXPORT, ERR_INVALID_ARGUMENT, ERR_PANIC, Error, OK, POLL_PENDING, POLL_READY,
Result, XabiCallError, XabiOwnedBytes, XabiOwnedBytesOwner, XabiResult, XabiType,
catch_unwind_code, validate_abi_version, validate_size,
};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XabiWaker {
pub size: usize,
pub abi_version: u32,
pub instance: *mut c_void,
pub clone: unsafe extern "C" fn(*mut c_void) -> XabiWaker,
pub wake: unsafe extern "C" fn(*mut c_void),
pub wake_by_ref: unsafe extern "C" fn(*mut c_void),
pub release: unsafe extern "C" fn(*mut c_void),
}
unsafe impl Send for XabiWaker {}
unsafe impl Sync for XabiWaker {}
impl XabiWaker {
pub const ABI_VERSION: u32 = ABI_VERSION;
pub const MIN_SIZE: usize = std::mem::offset_of!(XabiWaker, release)
+ std::mem::size_of::<unsafe extern "C" fn(*mut c_void)>();
pub const FULL_SIZE: usize = std::mem::size_of::<Self>();
pub fn validate(&self) -> Result<()> {
validate_size(self.size, Self::MIN_SIZE, "XabiWaker")?;
validate_abi_version(self.abi_version, Self::ABI_VERSION, "XabiWaker")?;
if self.instance.is_null() {
return Err(Error::NullPointer("XabiWaker::instance"));
}
Ok(())
}
pub fn from_waker_ref(waker: &Waker) -> Self {
Self {
size: std::mem::size_of::<Self>(),
abi_version: ABI_VERSION,
instance: waker as *const Waker as *mut c_void,
clone: clone_borrowed_waker,
wake: wake_borrowed_waker,
wake_by_ref: wake_borrowed_waker,
release: release_borrowed_waker,
}
}
pub unsafe fn to_waker(&self) -> Result<Waker> {
self.validate()?;
let owned = unsafe { (self.clone)(self.instance) };
owned.validate()?;
let boxed = Box::new(owned);
let raw = RawWaker::new(Box::into_raw(boxed) as *const (), &XABI_WAKER_VTABLE);
Ok(unsafe { Waker::from_raw(raw) })
}
}
unsafe extern "C" fn clone_borrowed_waker(instance: *mut c_void) -> XabiWaker {
let waker = unsafe { &*(instance as *const Waker) };
let owned = Box::new(waker.clone());
XabiWaker {
size: std::mem::size_of::<XabiWaker>(),
abi_version: ABI_VERSION,
instance: Box::into_raw(owned) as *mut c_void,
clone: clone_owned_waker,
wake: wake_owned_waker,
wake_by_ref: wake_by_ref_owned_waker,
release: release_owned_waker,
}
}
unsafe extern "C" fn wake_borrowed_waker(instance: *mut c_void) {
let waker = unsafe { &*(instance as *const Waker) };
waker.wake_by_ref();
}
unsafe extern "C" fn release_borrowed_waker(_instance: *mut c_void) {}
unsafe extern "C" fn clone_owned_waker(instance: *mut c_void) -> XabiWaker {
let waker = unsafe { &*(instance as *const Waker) };
let owned = Box::new(waker.clone());
XabiWaker {
size: std::mem::size_of::<XabiWaker>(),
abi_version: ABI_VERSION,
instance: Box::into_raw(owned) as *mut c_void,
clone: clone_owned_waker,
wake: wake_owned_waker,
wake_by_ref: wake_by_ref_owned_waker,
release: release_owned_waker,
}
}
unsafe extern "C" fn wake_owned_waker(instance: *mut c_void) {
let waker = unsafe { &*(instance as *const Waker) };
waker.wake_by_ref();
}
unsafe extern "C" fn wake_by_ref_owned_waker(instance: *mut c_void) {
let waker = unsafe { &*(instance as *const Waker) };
waker.wake_by_ref();
}
unsafe extern "C" fn release_owned_waker(instance: *mut c_void) {
if !instance.is_null() {
drop(unsafe { Box::from_raw(instance as *mut Waker) });
}
}
static XABI_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
raw_waker_clone,
raw_waker_wake,
raw_waker_wake_by_ref,
raw_waker_drop,
);
unsafe fn raw_waker_clone(data: *const ()) -> RawWaker {
let waker = unsafe { &*(data as *const XabiWaker) };
let cloned = unsafe { (waker.clone)(waker.instance) };
RawWaker::new(
Box::into_raw(Box::new(cloned)) as *const (),
&XABI_WAKER_VTABLE,
)
}
unsafe fn raw_waker_wake(data: *const ()) {
let waker = unsafe { Box::from_raw(data as *mut XabiWaker) };
unsafe {
(waker.wake)(waker.instance);
(waker.release)(waker.instance);
}
}
unsafe fn raw_waker_wake_by_ref(data: *const ()) {
let waker = unsafe { &*(data as *const XabiWaker) };
unsafe { (waker.wake_by_ref)(waker.instance) };
}
unsafe fn raw_waker_drop(data: *const ()) {
let waker = unsafe { Box::from_raw(data as *mut XabiWaker) };
unsafe { (waker.release)(waker.instance) };
}
#[repr(C)]
pub struct XabiFuture {
pub size: usize,
pub abi_version: u32,
pub instance: *mut c_void,
pub poll: unsafe extern "C" fn(*mut c_void, *const XabiWaker, *mut XabiResult) -> i32,
pub release: unsafe extern "C" fn(*mut c_void),
}
unsafe impl Send for XabiFuture {}
impl XabiFuture {
pub const ABI_VERSION: u32 = ABI_VERSION;
pub const MIN_SIZE: usize = std::mem::offset_of!(XabiFuture, release)
+ std::mem::size_of::<unsafe extern "C" fn(*mut c_void)>();
pub const FULL_SIZE: usize = std::mem::size_of::<Self>();
pub fn empty() -> Self {
Self {
size: std::mem::size_of::<Self>(),
abi_version: ABI_VERSION,
instance: std::ptr::null_mut(),
poll: poll_missing_future,
release: release_missing_future,
}
}
pub fn validate(&self) -> Result<()> {
validate_size(self.size, Self::MIN_SIZE, "XabiFuture")?;
validate_abi_version(self.abi_version, Self::ABI_VERSION, "XabiFuture")?;
if self.instance.is_null() {
return Err(Error::NullPointer("XabiFuture::instance"));
}
Ok(())
}
pub fn from_result_bytes<F, E>(future: F) -> Self
where
F: Future<Output = std::result::Result<Vec<u8>, E>> + Send + 'static,
E: XabiType + 'static,
{
let state = Box::new(XabiFutureState {
future: Some(Box::pin(future)),
});
Self {
size: std::mem::size_of::<Self>(),
abi_version: ABI_VERSION,
instance: Box::into_raw(state) as *mut c_void,
poll: poll_result_bytes_future::<F, E>,
release: release_result_bytes_future::<F, E>,
}
}
pub fn from_result_value<F, T, E>(future: F) -> Self
where
F: Future<Output = std::result::Result<T, E>> + Send + 'static,
T: XabiType + 'static,
E: XabiType + 'static,
{
let state = Box::new(XabiFutureState {
future: Some(Box::pin(future)),
});
Self {
size: std::mem::size_of::<Self>(),
abi_version: ABI_VERSION,
instance: Box::into_raw(state) as *mut c_void,
poll: poll_result_value_future::<F, T, E>,
release: release_result_value_future::<F, T, E>,
}
}
}
unsafe extern "C" fn poll_missing_future(
_instance: *mut c_void,
_waker: *const XabiWaker,
_out: *mut XabiResult,
) -> i32 {
ERR_INVALID_ARGUMENT
}
unsafe extern "C" fn release_missing_future(_instance: *mut c_void) {}
struct XabiFutureState<F> {
future: Option<Pin<Box<F>>>,
}
unsafe extern "C" fn poll_result_bytes_future<F, E>(
instance: *mut c_void,
waker: *const XabiWaker,
out: *mut XabiResult,
) -> i32
where
F: Future<Output = std::result::Result<Vec<u8>, E>> + Send + 'static,
E: XabiType + 'static,
{
catch_unwind_code(|| {
let Some(state) = (unsafe { (instance as *mut XabiFutureState<F>).as_mut() }) else {
return ERR_INVALID_ARGUMENT;
};
let Some(out) = (unsafe { out.as_mut() }) else {
return ERR_INVALID_ARGUMENT;
};
let Some(waker) = (unsafe { waker.as_ref() }) else {
return ERR_INVALID_ARGUMENT;
};
let rust_waker = match unsafe { waker.to_waker() } {
Ok(waker) => waker,
Err(_) => return ERR_INVALID_ARGUMENT,
};
let mut cx = Context::from_waker(&rust_waker);
let Some(future) = state.future.as_mut() else {
return ERR_INVALID_ARGUMENT;
};
match future.as_mut().poll(&mut cx) {
Poll::Pending => POLL_PENDING,
Poll::Ready(Ok(bytes)) => {
state.future = None;
*out = XabiResult::ok(XabiOwnedBytes::from_vec(bytes));
POLL_READY
}
Poll::Ready(Err(err)) => {
state.future = None;
*out = XabiResult {
code: ERR_EXPORT,
payload: err.into_payload(),
};
POLL_READY
}
}
})
}
unsafe extern "C" fn release_result_bytes_future<F, E>(instance: *mut c_void)
where
F: Future<Output = std::result::Result<Vec<u8>, E>> + Send + 'static,
E: XabiType + 'static,
{
if !instance.is_null() {
drop(unsafe { Box::from_raw(instance as *mut XabiFutureState<F>) });
}
}
unsafe extern "C" fn poll_result_value_future<F, T, E>(
instance: *mut c_void,
waker: *const XabiWaker,
out: *mut XabiResult,
) -> i32
where
F: Future<Output = std::result::Result<T, E>> + Send + 'static,
T: XabiType + 'static,
E: XabiType + 'static,
{
catch_unwind_code(|| {
let Some(state) = (unsafe { (instance as *mut XabiFutureState<F>).as_mut() }) else {
return ERR_INVALID_ARGUMENT;
};
let Some(out) = (unsafe { out.as_mut() }) else {
return ERR_INVALID_ARGUMENT;
};
let Some(waker) = (unsafe { waker.as_ref() }) else {
return ERR_INVALID_ARGUMENT;
};
let rust_waker = match unsafe { waker.to_waker() } {
Ok(waker) => waker,
Err(_) => return ERR_INVALID_ARGUMENT,
};
let mut cx = Context::from_waker(&rust_waker);
let Some(future) = state.future.as_mut() else {
return ERR_INVALID_ARGUMENT;
};
match future.as_mut().poll(&mut cx) {
Poll::Pending => POLL_PENDING,
Poll::Ready(Ok(value)) => {
state.future = None;
*out = XabiResult::ok(value.into_payload());
POLL_READY
}
Poll::Ready(Err(err)) => {
state.future = None;
*out = XabiResult {
code: ERR_EXPORT,
payload: err.into_payload(),
};
POLL_READY
}
}
})
}
unsafe extern "C" fn release_result_value_future<F, T, E>(instance: *mut c_void)
where
F: Future<Output = std::result::Result<T, E>> + Send + 'static,
T: XabiType + 'static,
E: XabiType + 'static,
{
if !instance.is_null() {
drop(unsafe { Box::from_raw(instance as *mut XabiFutureState<F>) });
}
}
pub struct XabiFutureHandle {
future: XabiFuture,
}
pub struct XabiTypedFuture<E, T = Vec<u8>> {
future: XabiFuture,
module: Option<std::sync::Arc<crate::ModuleHandle>>,
_marker: std::marker::PhantomData<(E, T)>,
}
impl<E, T> Unpin for XabiTypedFuture<E, T> {}
impl<E, T> XabiTypedFuture<E, T> {
pub fn new(future: XabiFuture) -> Result<Self> {
future.validate()?;
Ok(Self {
future,
module: None,
_marker: std::marker::PhantomData,
})
}
#[doc(hidden)]
pub fn new_with_module(
future: XabiFuture,
module: std::sync::Arc<crate::ModuleHandle>,
) -> Result<Self> {
future.validate()?;
Ok(Self {
future,
module: Some(module),
_marker: std::marker::PhantomData,
})
}
}
impl<E, T> Future for XabiTypedFuture<E, T>
where
E: XabiType,
T: XabiType,
{
type Output = std::result::Result<T, XabiCallError<E>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let waker = XabiWaker::from_waker_ref(cx.waker());
let mut out = XabiResult::empty();
let code = unsafe { (this.future.poll)(this.future.instance, &waker, &mut out) };
match code {
POLL_PENDING => {
discard_owned_payload(out.payload);
Poll::Pending
}
POLL_READY => {
if out.code == OK {
Poll::Ready(match unsafe { T::from_payload(out.payload) } {
Ok(mut value) => {
if let Some(module) = &this.module {
T::retain_module(&mut value, module);
}
Ok(value)
}
Err(err) => Err(XabiCallError::Runtime(err)),
})
} else if out.code == ERR_EXPORT {
Poll::Ready(match unsafe { E::from_payload(out.payload) } {
Ok(mut err) => {
if let Some(module) = &this.module {
E::retain_module(&mut err, module);
}
Err(XabiCallError::Export(err))
}
Err(err) => Err(XabiCallError::Runtime(err)),
})
} else {
discard_owned_payload(out.payload);
Poll::Ready(Err(XabiCallError::Runtime(Error::Export(format!(
"future completed with xabi code {}",
out.code
)))))
}
}
ERR_PANIC => {
discard_owned_payload(out.payload);
Poll::Ready(Err(XabiCallError::Runtime(Error::Export(
"future poll panicked across xabi boundary".to_string(),
))))
}
other => {
discard_owned_payload(out.payload);
Poll::Ready(Err(XabiCallError::Runtime(Error::Export(format!(
"future poll returned xabi code {other}"
)))))
}
}
}
}
impl<E, T> Drop for XabiTypedFuture<E, T> {
fn drop(&mut self) {
unsafe { (self.future.release)(self.future.instance) };
}
}
impl XabiFutureHandle {
pub fn new(future: XabiFuture) -> Result<Self> {
future.validate()?;
Ok(Self { future })
}
}
impl Future for XabiFutureHandle {
type Output = Result<Vec<u8>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let waker = XabiWaker::from_waker_ref(cx.waker());
let mut out = XabiResult::empty();
let code = unsafe { (this.future.poll)(this.future.instance, &waker, &mut out) };
match code {
POLL_PENDING => {
discard_owned_payload(out.payload);
Poll::Pending
}
POLL_READY => {
if out.code == OK {
Poll::Ready(unsafe { out.payload.to_vec_and_free() })
} else {
Poll::Ready(match unsafe { Error::from_payload(out.payload) } {
Ok(err) => Err(err),
Err(err) => Err(err),
})
}
}
ERR_PANIC => {
discard_owned_payload(out.payload);
Poll::Ready(Err(Error::Export(
"future poll panicked across xabi boundary".to_string(),
)))
}
other => {
discard_owned_payload(out.payload);
Poll::Ready(Err(Error::Export(format!(
"future poll returned xabi code {other}"
))))
}
}
}
}
impl Drop for XabiFutureHandle {
fn drop(&mut self) {
unsafe { (self.future.release)(self.future.instance) };
}
}
fn discard_owned_payload(payload: XabiOwnedBytes) {
drop(unsafe { XabiOwnedBytesOwner::from_raw(payload) });
}
#[cfg(test)]
mod tests {
use super::*;
use std::future::Future;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::task::{Wake, Waker};
struct CountingWaker(Arc<AtomicUsize>);
impl Wake for CountingWaker {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
fn context() -> (Arc<AtomicUsize>, Waker) {
let count = Arc::new(AtomicUsize::new(0));
let waker = Waker::from(Arc::new(CountingWaker(Arc::clone(&count))));
(count, waker)
}
#[test]
fn xabi_waker_roundtrips_to_rust_waker() {
let (count, rust_waker) = context();
let waker = XabiWaker::from_waker_ref(&rust_waker);
let rust_waker = unsafe { waker.to_waker() }.unwrap();
rust_waker.wake_by_ref();
assert_eq!(count.load(Ordering::SeqCst), 1);
}
#[test]
fn xabi_future_handle_returns_ready_bytes() {
let future = XabiFuture::from_result_bytes(async { Ok::<_, Error>(b"ready".to_vec()) });
let mut future = Box::pin(XabiFutureHandle::new(future).unwrap());
let (_count, waker) = context();
let mut cx = Context::from_waker(&waker);
match Future::poll(future.as_mut(), &mut cx) {
Poll::Ready(Ok(bytes)) => assert_eq!(bytes, b"ready"),
other => panic!("unexpected poll result: {other:?}"),
}
}
#[test]
fn xabi_future_handle_returns_export_error_payload() {
let future = XabiFuture::from_result_bytes(async {
Err::<Vec<u8>, _>(Error::Export("failed".to_string()))
});
let mut future = Box::pin(XabiFutureHandle::new(future).unwrap());
let (_count, waker) = context();
let mut cx = Context::from_waker(&waker);
match Future::poll(future.as_mut(), &mut cx) {
Poll::Ready(Err(err)) => assert_eq!(err.to_string(), "failed"),
other => panic!("unexpected poll result: {other:?}"),
}
}
#[test]
fn raw_future_poll_rejects_null_arguments() {
let future = XabiFuture::from_result_bytes(async { Ok::<_, Error>(Vec::new()) });
let code = unsafe {
(future.poll)(
future.instance,
std::ptr::null(),
std::ptr::null_mut::<XabiResult>(),
)
};
unsafe { (future.release)(future.instance) };
assert_eq!(code, ERR_INVALID_ARGUMENT);
}
}