use crate::rt::Cleanup;
use crate::rt::async_support::waitable::{WaitableOp, WaitableOperation};
use crate::rt::async_support::{
STATUS_RETURNED, STATUS_RETURNED_CANCELLED, STATUS_STARTED, STATUS_STARTED_CANCELLED,
STATUS_STARTING,
};
use std::alloc::Layout;
use std::future::Future;
use std::marker;
use std::num::NonZeroU32;
use std::ptr;
pub unsafe trait Subtask {
type Params;
type ParamsLower: Copy;
type Results;
fn abi_layout(&mut self) -> Layout;
fn results_offset(&mut self) -> usize;
unsafe fn call_import(&mut self, params: Self::ParamsLower, results: *mut u8) -> u32;
unsafe fn params_lower(&mut self, params: Self::Params, dst: *mut u8) -> Self::ParamsLower;
unsafe fn params_dealloc_lists(&mut self, lower: Self::ParamsLower);
unsafe fn params_dealloc_lists_and_own(&mut self, lower: Self::ParamsLower);
unsafe fn results_lift(&mut self, src: *mut u8) -> Self::Results;
fn call(&mut self, params: Self::Params) -> impl Future<Output = Self::Results>
where
Self: Sized,
{
async {
match WaitableOperation::<SubtaskOps<Self>>::new(SubtaskOps(self), Start { params })
.await
{
Ok(results) => results,
Err(_) => unreachable!(
"cancellation is not exposed API-wise, \
should not be possible"
),
}
}
}
}
struct SubtaskOps<'a, T>(&'a mut T);
struct Start<T: Subtask> {
params: T::Params,
}
unsafe impl<T: Subtask> WaitableOp for SubtaskOps<'_, T> {
type Start = Start<T>;
type InProgress = InProgress<T>;
type Result = Result<T::Results, ()>;
type Cancel = Result<T::Results, ()>;
fn start(&mut self, state: Self::Start) -> (u32, Self::InProgress) {
unsafe {
let (ptr_params, cleanup) = Cleanup::new(self.0.abi_layout());
let ptr_results = ptr_params.add(self.0.results_offset());
let params_lower = self.0.params_lower(state.params, ptr_params);
let packed = self.0.call_import(params_lower, ptr_results);
let code = packed & 0xf;
let subtask = NonZeroU32::new(packed >> 4).map(|handle| SubtaskHandle { handle });
rtdebug!("<import>({ptr_params:?}, {ptr_results:?}) = ({code:#x}, {subtask:#x?})");
(
code,
InProgress {
params_lower,
params_and_results: cleanup,
subtask,
started: false,
_marker: marker::PhantomData,
},
)
}
}
fn start_cancelled(&mut self, _state: Self::Start) -> Self::Cancel {
Err(())
}
fn in_progress_update(
&mut self,
mut state: Self::InProgress,
code: u32,
) -> Result<Self::Result, Self::InProgress> {
match code {
STATUS_STARTING => {
assert!(!state.started);
Err(state)
}
STATUS_STARTED => {
state.flag_started(self.0);
Err(state)
}
STATUS_RETURNED => {
if !state.started {
state.flag_started(self.0);
}
let ptr = state.ptr_results(self.0);
unsafe { Ok(Ok(self.0.results_lift(ptr))) }
}
STATUS_STARTED_CANCELLED => {
assert!(!state.started);
unsafe {
self.0.params_dealloc_lists_and_own(state.params_lower);
}
Ok(Err(()))
}
STATUS_RETURNED_CANCELLED => {
if !state.started {
state.flag_started(self.0);
}
Ok(Err(()))
}
other => panic!("unknown code {other:#x}"),
}
}
fn in_progress_waitable(&mut self, state: &Self::InProgress) -> u32 {
state.subtask.as_ref().unwrap().handle.get()
}
fn in_progress_cancel(&mut self, state: &mut Self::InProgress) -> u32 {
unsafe { cancel(self.in_progress_waitable(state)) }
}
fn result_into_cancel(&mut self, result: Self::Result) -> Self::Cancel {
result
}
}
#[derive(Debug)]
struct SubtaskHandle {
handle: NonZeroU32,
}
impl Drop for SubtaskHandle {
fn drop(&mut self) {
unsafe {
drop(self.handle.get());
}
}
}
struct InProgress<T: Subtask> {
params_and_results: Option<Cleanup>,
params_lower: T::ParamsLower,
started: bool,
subtask: Option<SubtaskHandle>,
_marker: marker::PhantomData<T>,
}
impl<T: Subtask> InProgress<T> {
fn flag_started(&mut self, op: &mut T) {
assert!(!self.started);
self.started = true;
unsafe {
op.params_dealloc_lists(self.params_lower);
}
}
fn ptr_results(&mut self, op: &mut T) -> *mut u8 {
unsafe {
self.params_and_results
.as_ref()
.map(|c| c.ptr.as_ptr())
.unwrap_or(ptr::null_mut())
.add(op.results_offset())
}
}
}
extern_wasm! {
#[link(wasm_import_module = "$root")]
unsafe extern "C" {
#[link_name = "[subtask-cancel]"]
fn cancel(handle: u32) -> u32;
#[link_name = "[subtask-drop]"]
fn drop(handle: u32);
}
}