//! Giterated ABI //! # ABI //! //! ## Value ABI //! //! At its core, the Giterated Runtime uses the `extern "C"` ABI. What that means is likely platform specific, and doesn't matter. //! You are intended to compile the Giterated Runtime and Plugins for your local machine, all with a similar idea of what //! your "local machine" is. //! //! Values are passed using the `FFI` type. There are four categories of value that the `FFI` type enables you to pass: //! //! | `FFI` Type Category | Placed Backing? | Owned? | //! |---------------------|-----------------|--------| //! | Slice | Heap/Stack | No | //! | Referenced Slice | Stack | No | //! | Referenced Value | No | No | //! | Owned Value | Heap | Yes | //! //! For an FFI type to have a "placed backing" is for it to have some data structure beyond the data it represents, placed //! somewhere in memory. Some types only require stack placement while some offer both stack and heap placement. //! //! Stack-placed values can be shared by `PinnedRef` and `PinnedMut`, and thus can only be owned by the caller. //! //! Heap-placed values can be shared by `Owned`, `PinnedRef`, and `PinnedMut`. They can be owned by any one consumer, //! When the handle with ownership is `Drop`'d by the sole consumer, it will free the object using the associated `Drop` callback. //! //! ### Safety Intents //! //! This API is designed to simplify interaction with FFI values, and provide a static ABI for those values to be passed. It //! is key to enabling ownership across FFI while ensuring associated dropping and allocation freeing logic is ran. //! //! The contract the developer has to follow is made simpler by this system, and it allows for generic code to be written to //! interact with FFI-given values and pass values using FFI. //! //! ### Stability Guarantees //! //! There are no plans to guarantee stability until 1.0.0. At that point you can expect the ABI to remain stable until the major version //! is incremented again. There will be an appropriate deprecation process and changeover period. //! //! ### Memory Representation //! //! Please check out the source code, sorry if you needed that from the docs! //! //! ## Object, Operation, Setting, Value, Plugin, and Runtime ABIs //! //! The Giterated Runtime uses vtables to accomplish the goal of ensuring maximum compatibility. For every object that is shared //! between plugins, a vtable is used to allow each plugin to provide their own code for interacting with the object. //! //! When objects switch "runtime domains" (e.g. host -> plugin, plugin -> plugin, plugin -> host), their vtable is swapped out //! for the new runtime domain's own vtables. //! //! ### Untyped "Objects" (see above header for list) //! //! Untyped objects, in memory, are represented by a data pointer and a vtable pointer. Exactly like Rust traits. However, to //! prevent small compilation differences and other random garbage from making the interface not perfectly compatible we use //! the local plugin's idea of the vtable for the object at all times. An object that the plugin does not have a vtable for cannot //! be relevant to the plugin. //! //! It is important that the object's base representation in memory remain unchanged between major versions, but the vtables that provide methods for //! that object may be grown. The methods that operate on that object may be changed in an non-breaking fashion, and bugs can be //! fixed. //! //! ## Futures ABI //! //! The Giterated Runtime has an async runtime that allows for futures to be shared and awaited across FFI boundaries while only //! executing the future within the context of the Plugin who is running the underlying future. //! //! Futures are spawned onto the `RuntimeState` with the `RuntimeFuturesExt` trait. This takes a Rust future, boxes it, and //! provides a `RuntimeFuture` handle that can be used to drive the underlying Rust future locally. The `RuntimeFuture` handle //! is thread-safe and can be shared with the callee and `.await`'d directly like any other future. //! //! ### RuntimeFuture //! //! The `RuntimeFuture` mixes a vtable with data to allow for any caller to drive a spawned future. It contains: //! //! - A `poll_fn` which is used to poll the future for `Ready`-ness //! - A `wake_fn` which is used to wake the callee to poll for (expected) `Ready`-ness, it is populated when the `RuntimeFuture` is `await`'d. //! //! When the `RuntimeFuture` is polled, it causes the inner future to also be polled. We provide the inner future with a waker //! that triggers the `RuntimeFuture`'s waker so it is polled again. Breaking character to point out how freaking cool that is. //! //! `RuntimeFuture`s drop the associated inner future as they drop. mod future; mod heap; pub mod result; pub mod vtable; use abi_backing::{HeapValueBacking, SliceBacking}; pub use future::{FfiFuture, RuntimeFuturePoll}; use heap::HeapPlacable; use std::{ marker::PhantomData, mem::{transmute, MaybeUninit}, ops::{Deref, DerefMut}, }; use abi_types::{Slice, SliceMut, SliceRef, Value, ValueMut, ValueRef}; use guards::{HeapPinnedSlice, HeapPinnedValue, StackPinnedSlice, StackPinnedValue}; #[doc(hidden)] pub mod prelude { pub use crate::Ffi; pub use crate::StackPinned; pub use crate::{FfiSlice, FfiSliceRef, FfiValue, FfiValueRef}; } /// Slice Reference /// Heap or Stack Placed pub type FfiSliceRef = Ffi; /// Mutable Slice Reference /// Heap or Stack Placed pub type FfiSliceMut = Ffi; /// Value Reference /// Heap or Stack Placed pub type FfiValueRef = Ffi; /// Mutable Value Reference /// Heap or Stack Placed pub type FfiValueMut = Ffi; /// Owned Value /// Heap Placed pub type FfiValue = Ffi; /// Owned Slice /// Heap Placed pub type FfiSlice = Ffi; pub mod value_ex { use crate::{abi_types::Value, Ffi}; pub type FfiValueUntyped = Ffi<(), Value>; pub type FfiValueRefUntyped = Ffi<(), Value>; } /// A value passed over FFI, following the Giterated ABI. /// /// The function of the [`Ffi`] type is to take an arbitrary pointer and send it over FFI. /// Both the caller and callee **must** have the same understanding of what the pointer represents. /// The [`Ffi`] type is also used to encode ownership information. /// /// # The Pointer /// The pointer contained within the [`Ffi`] is transmuted based on the provided `ABI` on the /// [`Ffi`] type signature. #[repr(transparent)] pub struct Ffi { inner: *const (), _type_marker: PhantomData, _abi_marker: PhantomData, } impl FfiSlice { #[inline(always)] pub fn pin(&self) -> HeapPinnedSlice<'_, T> { unsafe { HeapPinnedSlice::from_raw(self) } } } impl Deref for FfiSlice { type Target = [T]; #[inline(always)] fn deref(&self) -> &Self::Target { let inner: *const SliceBacking<[T]> = unsafe { transmute(self.inner) }; let backing = unsafe { inner.as_ref().unwrap_unchecked() }; unsafe { core::slice::from_raw_parts( backing.slice as *mut T, usize::try_from(backing.count).unwrap_unchecked(), ) } } } impl DerefMut for FfiSlice { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { let inner: *mut SliceBacking<[T]> = unsafe { transmute(self.inner) }; let backing = unsafe { inner.as_mut().unwrap_unchecked() }; unsafe { core::slice::from_raw_parts_mut( backing.slice as *mut T, usize::try_from(backing.count).unwrap_unchecked(), ) } } } impl FfiSliceRef {} impl Deref for FfiSliceRef<[T]> { type Target = [T]; #[inline(always)] fn deref(&self) -> &Self::Target { let inner: *const SliceBacking<[T]> = unsafe { transmute(self.inner) }; let backing = unsafe { inner.as_ref().unwrap_unchecked() }; unsafe { core::slice::from_raw_parts( backing.slice as *const T, usize::try_from(backing.count).unwrap_unchecked(), ) } } } impl FfiValueRef {} impl Deref for FfiValueRef { type Target = T; #[inline(always)] fn deref(&self) -> &Self::Target { let inner: *const T = unsafe { transmute(self.inner) }; match unsafe { inner.as_ref() } { Some(val) => val, _ => unreachable!(), } } } impl Deref for FfiValueMut { type Target = T; fn deref(&self) -> &Self::Target { let inner: *mut T = unsafe { transmute(self.inner) }; unsafe { inner.as_ref().unwrap_unchecked() } } } impl DerefMut for FfiValueMut { fn deref_mut(&mut self) -> &mut Self::Target { let inner: *mut T = unsafe { transmute(self.inner) }; unsafe { inner.as_mut().unwrap_unchecked() } } } impl std::fmt::Display for FfiValueRef where T: std::fmt::Display, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { unsafe { (self.inner as *const T).as_ref().unwrap() }.fmt(f) } } impl FfiValue { pub fn new(value: T) -> Self { let value = Box::new(HeapValueBacking { value, drop_fn: ::free, }); FfiValue { inner: Box::into_raw(value) as _, _type_marker: PhantomData, _abi_marker: PhantomData, } } pub fn pin(&self) -> HeapPinnedValue<'_, T> { unsafe { HeapPinnedValue::from_raw(self) } } pub fn take(self) -> T { // This all boils down to moving `T` out of the `FfiValue` and dropping the backing // storage for said `FfiValue`. Despite the use of unsafe this is exactly how moving // a value onto the stack works. let inner = self.inner as *mut T; let mut move_target: MaybeUninit = MaybeUninit::zeroed(); unsafe { move_target.as_mut_ptr().copy_from(inner, 1) } let inner_descriptor: *mut HeapValueBacking = unsafe { transmute(self.inner) }; unsafe { (inner_descriptor.as_mut().unwrap_unchecked().drop_fn)(self, true) }; unsafe { move_target.assume_init() } } } impl Deref for FfiValue { type Target = T; #[inline(always)] fn deref(&self) -> &Self::Target { let inner: *const T = unsafe { transmute(self.inner) }; unsafe { inner.as_ref().unwrap_unchecked() } } } impl DerefMut for FfiValue { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { let inner: *mut T = unsafe { transmute(self.inner) }; unsafe { inner.as_mut().unwrap_unchecked() } } } mod abi_backing { use std::{marker::PhantomData, mem::transmute}; use crate::{FfiSlice, FfiValue}; #[repr(C)] pub struct HeapValueBacking { pub(super) value: T, pub(super) drop_fn: unsafe extern "C" fn(value: FfiValue, taken: bool), } pub struct SliceBacking { pub(crate) count: u64, pub(crate) slice: *const (), _marker: PhantomData, } impl SliceBacking { /// Creates a new slice backing from a raw slice pointer and a count. /// /// # SAFETY /// /// `slice` **must** refer to a valid slice, with a length greater than or equal to the /// value provided as `count`. #[inline(always)] pub(crate) unsafe fn from_raw(count: u64, slice: *const ()) -> Self { Self { count, slice, _marker: PhantomData, } } /// Creates a new slice backing from an [`FfiSlice`]. /// /// # SAFETY /// /// The resultant [`SliceBacking`] **must not** outlive the backing [`FfiSlice`]. #[inline(always)] pub(crate) unsafe fn from_heap(slice: &FfiSlice) -> Self { let heap_backing: *const SliceBacking = unsafe { transmute(slice.inner) }; let heap_backing = unsafe { heap_backing.as_ref().unwrap_unchecked() }; Self { count: heap_backing.count, slice: heap_backing.slice, _marker: PhantomData, } } } } mod guards { use std::marker::PhantomData; use crate::{ abi_backing::SliceBacking, Ffi, FfiSlice, FfiSliceMut, FfiSliceRef, FfiValue, FfiValueMut, FfiValueRef, }; #[repr(transparent)] pub struct StackPinnedSlice<'v, T: ?Sized> { _lifetime: PhantomData<&'v T>, slice: SliceBacking, } impl<'v, T> StackPinnedSlice<'v, T> { #[inline(always)] pub fn as_ref(&self) -> FfiSliceRef { FfiSliceRef { inner: &self.slice as *const _ as *const (), _type_marker: PhantomData, _abi_marker: PhantomData, } } #[inline(always)] pub fn as_mut(&mut self) -> FfiSliceMut { FfiSliceMut { inner: &mut self.slice as *mut _ as *mut (), _type_marker: PhantomData, _abi_marker: PhantomData, } } } impl<'v, T> StackPinnedSlice<'v, T> { /// Creates a stack pinned slice guard from a borrowed slice. /// /// # SAFETY /// This function itself isn't "unsafe" but other code will become unsafe if the `slice` /// becomes invalid or moves. You'd have to violate safety rules somewhere else to do that, /// though. #[inline(always)] pub(crate) unsafe fn from_raw(slice: &'v [T]) -> StackPinnedSlice<'v, T> { Self { _lifetime: PhantomData, slice: SliceBacking::from_raw( u64::try_from(slice.len()).unwrap(), slice.as_ptr() as *const (), ), } } } pub struct StackPinnedValue<'v, T> { value_ref: &'v T, } impl<'v, T> StackPinnedValue<'v, T> { /// Grants a reference to the pinned value. /// /// # SAFETY /// - The granted reference **must not** outlive the lifetime of `&self`. /// - There **must not** be a mutable reference created or mutable dereference performed during the lifetime of the [`FfiValueRef`]. #[inline(always)] pub unsafe fn grant_ref(&self) -> FfiValueRef { Ffi { inner: self.value_ref as *const _ as *const (), _type_marker: PhantomData, _abi_marker: PhantomData, } } } impl<'v, T> StackPinnedValue<'v, T> { #[inline(always)] pub(crate) fn from_raw(value: &'v T) -> Self { Self { value_ref: value } } } pub struct HeapPinnedSlice<'v, T> { _lifetime: PhantomData<&'v T>, slice: SliceBacking, } impl<'v, T> HeapPinnedSlice<'v, T> { /// Creates a pin guard from a heap placed slice. /// /// # SAFETY /// The `slice` **must not** be moved and **must not** have a mutable reference given during the lifetime /// of the returned [`HeapPinnedSlice`] guard. #[inline(always)] pub(crate) unsafe fn from_raw(slice: &'v FfiSlice) -> HeapPinnedSlice<'v, T> { Self { _lifetime: PhantomData, slice: SliceBacking::from_heap(slice), } } pub unsafe fn grant_ref(&self) -> FfiSliceRef { FfiSliceRef { inner: &self.slice as *const _ as *const (), _type_marker: PhantomData, _abi_marker: PhantomData, } } pub unsafe fn grant_mut(&mut self) -> FfiSliceMut { FfiSliceMut { inner: &mut self.slice as *mut _ as *mut (), _type_marker: PhantomData, _abi_marker: PhantomData, } } } #[repr(transparent)] pub struct HeapPinnedValue<'v, T> { value: &'v FfiValue, } impl<'v, T> HeapPinnedValue<'v, T> { #[inline(always)] pub(crate) unsafe fn from_raw(value: &'v FfiValue) -> HeapPinnedValue<'v, T> { Self { value } } #[inline(always)] pub unsafe fn grant_ref(&self) -> FfiValueRef { FfiValueRef { inner: self.value.inner, _type_marker: PhantomData, _abi_marker: PhantomData, } } #[inline(always)] pub unsafe fn grant_mut(&mut self) -> FfiValueMut { FfiValueMut { inner: self.value.inner, _type_marker: PhantomData, _abi_marker: PhantomData, } } } } mod abi_types { pub struct Slice; pub struct SliceRef; pub struct SliceMut; pub struct ValueRef; pub struct ValueMut; pub struct Value; } pub trait StackPinned<'p> { type Pinned: ?Sized + 'p; fn pin(&'p self) -> Self::Pinned; } impl<'p, T: 'p> StackPinned<'p> for [T] { type Pinned = StackPinnedSlice<'p, T>; #[inline(always)] fn pin(&'p self) -> StackPinnedSlice<'p, T> { unsafe { StackPinnedSlice::from_raw(self) } } } impl<'p, T: 'p> StackPinned<'p> for T { type Pinned = StackPinnedValue<'p, T>; #[inline(always)] fn pin(&'p self) -> Self::Pinned { StackPinnedValue::from_raw(self) } }