Skip to content
This repository was archived by the owner on Oct 3, 2025. It is now read-only.

Commit 982925e

Browse files
committed
feat: implement basic async
add ability for host functions to suspend vm and yield values to host, and then to resume, potentially receiving value from host
1 parent 76a65cc commit 982925e

File tree

9 files changed

+501
-67
lines changed

9 files changed

+501
-67
lines changed

crates/tinywasm/src/coro.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use core::fmt::Debug;
2+
3+
use crate::Result;
4+
// use alloc::boxed::Box;
5+
pub(crate) use tinywasm_types::{ResumeArgument, YieldedValue};
6+
7+
#[derive(Debug)]
8+
pub enum SuspendReason {
9+
/// host function yielded
10+
/// potentially some host functions might expect resume argument when calling resume
11+
Yield(YieldedValue),
12+
// /// timer ran out (not implemented),
13+
// /// host shouldn't provide resume argument when calling resume
14+
// SuspendedEpoch,
15+
16+
// /// async should_suspend flag was set (not implemented)
17+
// /// host shouldn't provide resume argument when calling resume
18+
// SuspendedFlag,
19+
}
20+
21+
/// result of a function that might pause in the middle and yield
22+
/// to be resumed later
23+
#[derive(Debug)]
24+
pub enum PotentialCoroCallResult<R, State>
25+
//where for<Ctx>
26+
// State: CoroState<R, Ctx>, // can't in stable rust
27+
{
28+
/// function returns normally
29+
Return(R),
30+
/// interpreter will be suspended and execution will return to host along with SuspendReason
31+
Suspended(SuspendReason, State),
32+
}
33+
34+
/// result of resuming coroutine state. Unlike [`PotentialCoroCallResult`]
35+
/// doesn't need to have state, since it's contained in self
36+
#[derive(Debug)]
37+
pub enum CoroStateResumeResult<R> {
38+
/// after this CoroState::resume can't be called again on that CoroState
39+
Return(R),
40+
41+
/// host function yielded
42+
/// execution returns to host along with yielded value
43+
Suspended(SuspendReason),
44+
}
45+
46+
impl<R, State> PotentialCoroCallResult<R, State> {
47+
/// true if coro is finished
48+
pub fn finished(&self) -> bool {
49+
if let Self::Return(_) = self {
50+
true
51+
} else {
52+
false
53+
}
54+
}
55+
/// separates state from PotentialCoroCallResult, leaving CoroStateResumeResult (one without state)
56+
pub fn split_state(self) -> (CoroStateResumeResult<R>, Option<State>) {
57+
match self {
58+
Self::Return(val) => (CoroStateResumeResult::Return(val), None),
59+
Self::Suspended(suspend, state) => (CoroStateResumeResult::Suspended(suspend), Some(state)),
60+
}
61+
}
62+
/// separates result from PotentialCoroCallResult, leaving unit type in it's place
63+
pub fn split_result(self) -> (PotentialCoroCallResult<(), State>, Option<R>) {
64+
match self {
65+
Self::Return(result) => (PotentialCoroCallResult::Return(()), Some(result)),
66+
Self::Suspended(suspend, state) => (PotentialCoroCallResult::Suspended(suspend, state), None),
67+
}
68+
}
69+
70+
/// transforms state
71+
pub fn map_state<OutS>(self, mapper: impl FnOnce(State) -> OutS) -> PotentialCoroCallResult<R, OutS> {
72+
match self {
73+
Self::Return(val) => PotentialCoroCallResult::Return(val),
74+
Self::Suspended(suspend, state) => PotentialCoroCallResult::Suspended(suspend, mapper(state)),
75+
}
76+
}
77+
/// transform result with mapper if there is none - calls "otherwise" arg. user_val
78+
pub fn map_result_or_else<OutR, Usr>(
79+
self,
80+
user_val: Usr,
81+
mapper: impl FnOnce(R, Usr) -> OutR,
82+
otherwise: impl FnOnce(Usr) -> (),
83+
) -> PotentialCoroCallResult<OutR, State> {
84+
match self {
85+
Self::Return(res) => PotentialCoroCallResult::Return(mapper(res, user_val)),
86+
Self::Suspended(suspend, state) => {
87+
otherwise(user_val);
88+
PotentialCoroCallResult::Suspended(suspend, state)
89+
}
90+
}
91+
}
92+
/// transforms result
93+
pub fn map_result<OutR>(self, mapper: impl FnOnce(R) -> OutR) -> PotentialCoroCallResult<OutR, State> {
94+
self.map_result_or_else((), |val, _| mapper(val), |_| {})
95+
}
96+
}
97+
98+
impl<R> CoroStateResumeResult<R> {
99+
/// true if coro is finished
100+
pub fn finished(&self) -> bool {
101+
if let Self::Return(_) = self {
102+
true
103+
} else {
104+
false
105+
}
106+
}
107+
/// separates result from CoroStateResumeResult, leaving unit type in it's place
108+
pub fn split_result(self) -> (CoroStateResumeResult<()>, Option<R>) {
109+
let (a, r) = PotentialCoroCallResult::<R, ()>::from(self).split_result();
110+
(a.into(), r)
111+
}
112+
/// transforms result
113+
pub fn map_result<OutR>(self, mapper: impl FnOnce(R) -> OutR) -> CoroStateResumeResult<OutR> {
114+
PotentialCoroCallResult::<R, ()>::from(self).map_result(mapper).into()
115+
}
116+
/// transform result with mapper if there is none - calls "otherwise" arg. user_val called
117+
pub fn map_result_or_else<OutR, Usr>(
118+
self,
119+
user_val: Usr,
120+
mapper: impl FnOnce(R, Usr) -> OutR,
121+
otherwise: impl FnOnce(Usr) -> (),
122+
) -> CoroStateResumeResult<OutR> {
123+
PotentialCoroCallResult::<R, ()>::from(self).map_result_or_else(user_val, mapper, otherwise).into()
124+
}
125+
}
126+
127+
impl<DstR, SrcR> From<PotentialCoroCallResult<SrcR, ()>> for CoroStateResumeResult<DstR>
128+
where
129+
DstR: From<SrcR>,
130+
{
131+
fn from(value: PotentialCoroCallResult<SrcR, ()>) -> Self {
132+
match value {
133+
PotentialCoroCallResult::Return(val) => Self::Return(val.into()),
134+
PotentialCoroCallResult::Suspended(suspend, ()) => Self::Suspended(suspend),
135+
}
136+
}
137+
}
138+
impl<SrcR> From<CoroStateResumeResult<SrcR>> for PotentialCoroCallResult<SrcR, ()> {
139+
fn from(value: CoroStateResumeResult<SrcR>) -> Self {
140+
match value {
141+
CoroStateResumeResult::Return(val) => PotentialCoroCallResult::Return(val),
142+
CoroStateResumeResult::Suspended(suspend) => PotentialCoroCallResult::Suspended(suspend, ()),
143+
}
144+
}
145+
}
146+
147+
///"coroutine statse", "coroutine instance", "resumable". Stores info to continue a function that was paused
148+
pub trait CoroState<Ret, ResumeContext>: Debug {
149+
/// resumes the execution of the coroutine
150+
fn resume(&mut self, ctx: ResumeContext, arg: ResumeArgument) -> Result<CoroStateResumeResult<Ret>>;
151+
}

crates/tinywasm/src/error.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use tinywasm_types::FuncType;
55
#[cfg(feature = "parser")]
66
pub use tinywasm_parser::ParseError;
77

8+
use crate::{coro::SuspendReason, interpreter};
9+
810
/// Errors that can occur for `TinyWasm` operations
911
#[derive(Debug)]
1012
pub enum Error {
@@ -23,12 +25,25 @@ pub enum Error {
2325
/// A function did not return a value
2426
FuncDidNotReturn,
2527

28+
/// A host function returned results that don't match it's signature
29+
HostFuncInvalidReturn,
30+
2631
/// An invalid label type was encountered
2732
InvalidLabelType,
2833

2934
/// The store is not the one that the module instance was instantiated in
3035
InvalidStore,
3136

37+
/// ResumeArgument of wrong type was provided
38+
InvalidResumeArgument,
39+
40+
/// Tried to resume on runtime when it's not suspended
41+
InvalidResume,
42+
43+
/// Function unexpectedly yielded instead of returning
44+
/// (for backwards compatibility with old api)
45+
UnexpectedSuspend(SuspendReason),
46+
3247
#[cfg(feature = "std")]
3348
/// An I/O error occurred
3449
Io(crate::std::io::Error),
@@ -184,7 +199,13 @@ impl Display for Error {
184199
Self::Other(message) => write!(f, "unknown error: {message}"),
185200
Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {feature}"),
186201
Self::FuncDidNotReturn => write!(f, "function did not return"),
202+
Self::HostFuncInvalidReturn => write!(f, "host function returned invalid types"),
203+
187204
Self::InvalidStore => write!(f, "invalid store"),
205+
206+
Self::UnexpectedSuspend(_) => write!(f, "funtion yielded instead of returning"),
207+
Self::InvalidResumeArgument => write!(f, "invalid resume argument supplied to suspended function"),
208+
Self::InvalidResume => write!(f, "attempt to resume coroutine that has already finished"),
188209
}
189210
}
190211
}
@@ -238,14 +259,14 @@ impl From<tinywasm_parser::ParseError> for Error {
238259
pub type Result<T, E = Error> = crate::std::result::Result<T, E>;
239260

240261
pub(crate) trait Controlify<T> {
241-
fn to_cf(self) -> ControlFlow<Option<Error>, T>;
262+
fn to_cf(self) -> ControlFlow<interpreter::executor::ReasonToBreak, T>;
242263
}
243264

244265
impl<T> Controlify<T> for Result<T, Error> {
245-
fn to_cf(self) -> ControlFlow<Option<Error>, T> {
266+
fn to_cf(self) -> ControlFlow<interpreter::executor::ReasonToBreak, T> {
246267
match self {
247268
Ok(value) => ControlFlow::Continue(value),
248-
Err(err) => ControlFlow::Break(Some(err)),
269+
Err(err) => ControlFlow::Break(interpreter::executor::ReasonToBreak::Errored(err)),
249270
}
250271
}
251272
}

crates/tinywasm/src/func.rs

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
use crate::coro::CoroState;
2+
use crate::interpreter;
3+
use crate::interpreter::executor::SuspendedHostCoroState;
14
use crate::interpreter::stack::{CallFrame, Stack};
25
use crate::{log, unlikely, Function};
36
use crate::{Error, FuncContext, Result, Store};
47
use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec};
5-
use tinywasm_types::{FuncType, ModuleInstanceAddr, ValType, WasmValue};
8+
use tinywasm_types::{FuncType, ModuleInstanceAddr, ResumeArgument, ValType, WasmValue};
69

710
#[derive(Debug)]
811
/// A function handle
@@ -15,12 +18,60 @@ pub struct FuncHandle {
1518
pub name: Option<String>,
1619
}
1720

21+
pub(crate) type FuncHandleResumeOutcome = crate::coro::CoroStateResumeResult<Vec<WasmValue>>;
22+
23+
#[derive(Debug)]
24+
struct SuspendedWasmFunc {
25+
runtime: interpreter::SuspendedRuntime,
26+
result_types: Box<[ValType]>,
27+
}
28+
impl SuspendedWasmFunc {
29+
fn resume(&mut self, ctx: FuncContext<'_>, arg: ResumeArgument) -> Result<FuncHandleResumeOutcome> {
30+
Ok(self.runtime.resume(ctx, arg)?.map_result(|mut stack| stack.values.pop_results(&self.result_types)))
31+
}
32+
}
33+
34+
#[derive(Debug)]
35+
pub(self) enum SuspendFuncInner {
36+
Wasm(SuspendedWasmFunc),
37+
Host(SuspendedHostCoroState),
38+
}
39+
40+
#[derive(Debug)]
41+
pub struct SuspendFunc {
42+
pub(self) func: SuspendFuncInner,
43+
pub(crate) module_addr: ModuleInstanceAddr,
44+
}
45+
46+
impl<'a> crate::coro::CoroState<Vec<WasmValue>, &mut Store> for SuspendFunc {
47+
fn resume(&mut self, store: &mut Store, arg: ResumeArgument) -> Result<FuncHandleResumeOutcome> {
48+
let ctx = FuncContext { store, module_addr: self.module_addr };
49+
match &mut self.func {
50+
SuspendFuncInner::Wasm(wasm) => wasm.resume(ctx, arg),
51+
SuspendFuncInner::Host(host) => Ok(host.coro_state.resume(ctx, arg)?),
52+
}
53+
}
54+
}
55+
56+
type FuncHandleCallOutcome = crate::coro::PotentialCoroCallResult<Vec<WasmValue>, SuspendFunc>;
57+
1858
impl FuncHandle {
1959
/// Call a function (Invocation)
2060
///
2161
/// See <https://webassembly.github.io/spec/core/exec/modules.html#invocation>
62+
///
63+
2264
#[inline]
2365
pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result<Vec<WasmValue>> {
66+
match self.call_coro(store, params)? {
67+
crate::coro::PotentialCoroCallResult::Return(res) => Ok(res),
68+
crate::coro::PotentialCoroCallResult::Suspended(suspend, _state) => Err(Error::UnexpectedSuspend(suspend)),
69+
}
70+
}
71+
72+
/// Call a function (Invocation) and anticipate possible yield instead as well as return
73+
#[inline]
74+
pub fn call_coro(&self, store: &mut Store, params: &[WasmValue]) -> Result<FuncHandleCallOutcome> {
2475
// Comments are ordered by the steps in the spec
2576
// In this implementation, some steps are combined and ordered differently for performance reasons
2677

@@ -53,7 +104,13 @@ impl FuncHandle {
53104
Function::Host(host_func) => {
54105
let func = &host_func.clone().func;
55106
let ctx = FuncContext { store, module_addr: self.module_addr };
56-
return (func)(ctx, params);
107+
return Ok((func)(ctx, params)?.map_state(|state| SuspendFunc {
108+
func: SuspendFuncInner::Host(SuspendedHostCoroState {
109+
coro_state: state,
110+
coro_orig_function: self.addr,
111+
}),
112+
module_addr: self.module_addr,
113+
}));
57114
}
58115
Function::Wasm(wasm_func) => wasm_func,
59116
};
@@ -63,23 +120,33 @@ impl FuncHandle {
63120

64121
// 7. Push the frame f to the call stack
65122
// & 8. Push the values to the stack (Not needed since the call frame owns the values)
66-
let mut stack = Stack::new(call_frame);
123+
let stack = Stack::new(call_frame);
67124

68125
// 9. Invoke the function instance
69126
let runtime = store.runtime();
70-
runtime.exec(store, &mut stack)?;
71-
72-
// Once the function returns:
73-
// let result_m = func_ty.results.len();
74-
75-
// 1. Assert: m values are on the top of the stack (Ensured by validation)
76-
// assert!(stack.values.len() >= result_m);
77-
78-
// 2. Pop m values from the stack
79-
let res = stack.values.pop_results(&func_ty.results);
80-
81-
// The values are returned as the results of the invocation.
82-
Ok(res)
127+
let exec_outcome = runtime.exec(store, stack)?;
128+
Ok(exec_outcome
129+
.map_result(|mut stack| {
130+
// Once the function returns:
131+
// let result_m = func_ty.results.len();
132+
133+
// 1. Assert: m values are on the top of the stack (Ensured by validation)
134+
// assert!(stack.values.len() >= result_m);
135+
136+
// 2. Pop m values from the stack
137+
let res = stack.values.pop_results(&func_ty.results);
138+
// The values are returned as the results of the invocation.
139+
return res;
140+
})
141+
.map_state(|coro_state| -> SuspendFunc {
142+
SuspendFunc {
143+
func: SuspendFuncInner::Wasm(SuspendedWasmFunc {
144+
runtime: coro_state,
145+
result_types: func_ty.results.clone(),
146+
}),
147+
module_addr: self.module_addr,
148+
}
149+
}))
83150
}
84151
}
85152

0 commit comments

Comments
 (0)