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 crate::rt::Cleanup;
use std::alloc::Layout;
use std::future::Future;
use std::marker;
use std::num::NonZeroU32;
use std::ptr;
pub unsafe trait Subtask {
const ABI_LAYOUT: Layout;
const RESULTS_OFFSET: usize;
type Params;
type ParamsLower: Copy;
type Results;
unsafe fn call_import(params: Self::ParamsLower, results: *mut u8) -> u32;
unsafe fn params_lower(params: Self::Params, dst: *mut u8) -> Self::ParamsLower;
unsafe fn params_dealloc_lists(lower: Self::ParamsLower);
unsafe fn params_dealloc_lists_and_own(lower: Self::ParamsLower);
unsafe fn results_lift(src: *mut u8) -> Self::Results;
fn call(params: Self::Params) -> impl Future<Output = Self::Results>
where
Self: Sized,
{
async {
match WaitableOperation::<SubtaskOps<Self>>::new(Start { params }).await {
Ok(results) => results,
Err(_) => unreachable!(
"cancellation is not exposed API-wise, \
should not be possible"
),
}
}
}
}
struct SubtaskOps<T>(marker::PhantomData<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(state: Self::Start) -> (u32, Self::InProgress) {
unsafe {
let (ptr_params, cleanup) = Cleanup::new(T::ABI_LAYOUT);
let ptr_results = ptr_params.add(T::RESULTS_OFFSET);
let params_lower = T::params_lower(state.params, ptr_params);
let packed = T::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(_state: Self::Start) -> Self::Cancel {
Err(())
}
fn in_progress_update(
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();
Err(state)
}
STATUS_RETURNED => {
if !state.started {
state.flag_started();
}
unsafe { Ok(Ok(T::results_lift(state.ptr_results()))) }
}
STATUS_STARTED_CANCELLED => {
assert!(!state.started);
unsafe {
T::params_dealloc_lists_and_own(state.params_lower);
}
Ok(Err(()))
}
STATUS_RETURNED_CANCELLED => {
if !state.started {
state.flag_started();
}
Ok(Err(()))
}
other => panic!("unknown code {other:#x}"),
}
}
fn in_progress_waitable(state: &Self::InProgress) -> u32 {
state.subtask.as_ref().unwrap().handle.get()
}
fn in_progress_cancel(state: &Self::InProgress) -> u32 {
unsafe { cancel(Self::in_progress_waitable(state)) }
}
fn result_into_cancel(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) {
assert!(!self.started);
self.started = true;
unsafe {
T::params_dealloc_lists(self.params_lower);
}
}
fn ptr_results(&self) -> *mut u8 {
unsafe {
self.params_and_results
.as_ref()
.map(|c| c.ptr.as_ptr())
.unwrap_or(ptr::null_mut())
.add(T::RESULTS_OFFSET)
}
}
}
#[cfg(not(target_arch = "wasm32"))]
unsafe fn drop(_: u32) {
unreachable!()
}
#[cfg(not(target_arch = "wasm32"))]
unsafe fn cancel(_: u32) -> u32 {
unreachable!()
}
#[cfg(target_arch = "wasm32")]
#[link(wasm_import_module = "$root")]
extern "C" {
#[link_name = "[subtask-cancel]"]
fn cancel(handle: u32) -> u32;
#[link_name = "[subtask-drop]"]
fn drop(handle: u32);
}