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

Commit ae83085

Browse files
committed
feat: added call_coro to typed fucn handle, some code reorganization and qol functions
1 parent b73ab18 commit ae83085

File tree

4 files changed

+200
-60
lines changed

4 files changed

+200
-60
lines changed

crates/tinywasm/src/coro.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,39 @@ impl<R, State> PotentialCoroCallResult<R, State> {
116116
}
117117
}
118118

119+
impl<R, State, E> PotentialCoroCallResult<core::result::Result<R, E>, State> {
120+
/// turns Self<Result<R>, S> into Resulf<Self<R>, S>
121+
pub fn propagate_err_result(self) -> core::result::Result<PotentialCoroCallResult<R, State>, E> {
122+
Ok(match self {
123+
PotentialCoroCallResult::Return(res) => PotentialCoroCallResult::<R, State>::Return(res?),
124+
PotentialCoroCallResult::Suspended(why, state) => {
125+
PotentialCoroCallResult::<R, State>::Suspended(why, state)
126+
}
127+
})
128+
}
129+
}
130+
impl<R, State, E> PotentialCoroCallResult<R, core::result::Result<State, E>> {
131+
/// turns Self<R, Result<S>> into Resulf<R, Self<S>>
132+
pub fn propagate_err_state(self) -> core::result::Result<PotentialCoroCallResult<R, State>, E> {
133+
Ok(match self {
134+
PotentialCoroCallResult::Return(res) => PotentialCoroCallResult::<R, State>::Return(res),
135+
PotentialCoroCallResult::Suspended(why, state) => {
136+
PotentialCoroCallResult::<R, State>::Suspended(why, state?)
137+
}
138+
})
139+
}
140+
}
141+
119142
impl<R> CoroStateResumeResult<R> {
143+
/// in case you expect function only to return
144+
/// you can make Suspend into [crate::Error::UnexpectedSuspend] error
145+
pub fn suspend_to_err(self) -> Result<R> {
146+
match self {
147+
Self::Return(r) => Ok(r),
148+
Self::Suspended(r) => Err(crate::Error::UnexpectedSuspend(r.into())),
149+
}
150+
}
151+
120152
/// true if coro is finished
121153
pub fn finished(&self) -> bool {
122154
matches!(self, Self::Return(_))
@@ -142,6 +174,14 @@ impl<R> CoroStateResumeResult<R> {
142174
}
143175
}
144176

177+
impl<R, E> CoroStateResumeResult<core::result::Result<R, E>> {
178+
/// turns Self<Result<R>> into Resulf<Self<R>>
179+
pub fn propagate_err(self) -> core::result::Result<CoroStateResumeResult<R>, E> {
180+
Ok(PotentialCoroCallResult::<core::result::Result<R, E>, ()>::from(self).propagate_err_result()?.into())
181+
}
182+
}
183+
184+
// convert between PotentialCoroCallResult<SrcR, ()> and CoroStateResumeResult<SrcR>
145185
impl<DstR, SrcR> From<PotentialCoroCallResult<SrcR, ()>> for CoroStateResumeResult<DstR>
146186
where
147187
DstR: From<SrcR>,

crates/tinywasm/src/func.rs

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,50 +18,6 @@ pub struct FuncHandle {
1818
pub name: Option<String>,
1919
}
2020

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-
#[allow(clippy::large_enum_variant)] // Wasm is bigger, but also much more common variant
36-
enum SuspendedFuncInner {
37-
Wasm(SuspendedWasmFunc),
38-
Host(SuspendedHostCoroState),
39-
}
40-
41-
/// handle to function that was suspended and can be resumed
42-
#[derive(Debug)]
43-
pub struct SuspendedFunc {
44-
func: SuspendedFuncInner,
45-
module_addr: ModuleInstanceAddr,
46-
store_id: usize,
47-
}
48-
49-
impl crate::coro::CoroState<Vec<WasmValue>, &mut Store> for SuspendedFunc {
50-
fn resume(&mut self, store: &mut Store, arg: ResumeArgument) -> Result<FuncHandleResumeOutcome> {
51-
if store.id() != self.store_id {
52-
return Err(Error::InvalidStore);
53-
}
54-
55-
let ctx = FuncContext { store, module_addr: self.module_addr };
56-
match &mut self.func {
57-
SuspendedFuncInner::Wasm(wasm) => wasm.resume(ctx, arg),
58-
SuspendedFuncInner::Host(host) => Ok(host.coro_state.resume(ctx, arg)?),
59-
}
60-
}
61-
}
62-
63-
type FuncHandleCallOutcome = crate::coro::PotentialCoroCallResult<Vec<WasmValue>, SuspendedFunc>;
64-
6521
impl FuncHandle {
6622
/// Call a function (Invocation)
6723
///
@@ -130,7 +86,7 @@ impl FuncHandle {
13086
let runtime = store.runtime();
13187
let exec_outcome = runtime.exec(store, stack)?;
13288
Ok(exec_outcome
133-
.map_result(|mut stack|->Vec<WasmValue> {
89+
.map_result(|mut stack| -> Vec<WasmValue> {
13490
// Once the function returns:
13591
// let result_m = func_ty.results.len();
13692

@@ -184,6 +140,92 @@ impl<P: IntoWasmValueTuple, R: FromWasmValueTuple> FuncHandleTyped<P, R> {
184140
// Convert the Vec<WasmValue> back to R
185141
R::from_wasm_value_tuple(&result)
186142
}
143+
144+
/// call a typed function, anticipating possible suspension of execution
145+
pub fn call_coro(&self, store: &mut Store, params: P) -> Result<TypedFuncHandleCallOutcome<R>> {
146+
// Convert params into Vec<WasmValue>
147+
let wasm_values = params.into_wasm_value_tuple();
148+
149+
// Call the underlying WASM function
150+
let result = self.func.call_coro(store, &wasm_values)?;
151+
152+
// Convert the Vec<WasmValue> back to R
153+
result
154+
.map_result(|vals| R::from_wasm_value_tuple(&vals))
155+
.map_state(|state| SuspendedFuncTyped::<R> { func: state, _marker: core::marker::PhantomData {} })
156+
.propagate_err_result()
157+
}
158+
}
159+
160+
pub(crate) type FuncHandleCallOutcome = crate::coro::PotentialCoroCallResult<Vec<WasmValue>, SuspendedFunc>;
161+
pub(crate) type TypedFuncHandleCallOutcome<R> = crate::coro::PotentialCoroCallResult<R, SuspendedFuncTyped<R>>;
162+
163+
#[derive(Debug)]
164+
struct SuspendedWasmFunc {
165+
runtime: interpreter::SuspendedRuntime,
166+
result_types: Box<[ValType]>,
167+
}
168+
impl SuspendedWasmFunc {
169+
fn resume(
170+
&mut self,
171+
ctx: FuncContext<'_>,
172+
arg: ResumeArgument,
173+
) -> Result<crate::CoroStateResumeResult<Vec<WasmValue>>> {
174+
Ok(self.runtime.resume(ctx, arg)?.map_result(|mut stack| stack.values.pop_results(&self.result_types)))
175+
}
176+
}
177+
178+
#[derive(Debug)]
179+
#[allow(clippy::large_enum_variant)] // Wasm is bigger, but also much more common variant
180+
enum SuspendedFuncInner {
181+
Wasm(SuspendedWasmFunc),
182+
Host(SuspendedHostCoroState),
183+
}
184+
185+
/// handle to function that was suspended and can be resumed
186+
#[derive(Debug)]
187+
pub struct SuspendedFunc {
188+
func: SuspendedFuncInner,
189+
module_addr: ModuleInstanceAddr,
190+
store_id: usize,
191+
}
192+
193+
impl crate::coro::CoroState<Vec<WasmValue>, &mut Store> for SuspendedFunc {
194+
fn resume(
195+
&mut self,
196+
store: &mut Store,
197+
arg: ResumeArgument,
198+
) -> Result<crate::CoroStateResumeResult<Vec<WasmValue>>> {
199+
if store.id() != self.store_id {
200+
return Err(Error::InvalidStore);
201+
}
202+
203+
let ctx = FuncContext { store, module_addr: self.module_addr };
204+
match &mut self.func {
205+
SuspendedFuncInner::Wasm(wasm) => wasm.resume(ctx, arg),
206+
SuspendedFuncInner::Host(host) => Ok(host.coro_state.resume(ctx, arg)?),
207+
}
208+
}
209+
}
210+
211+
pub struct SuspendedFuncTyped<R> {
212+
pub func: SuspendedFunc,
213+
pub(crate) _marker: core::marker::PhantomData<R>,
214+
}
215+
216+
impl<R> core::fmt::Debug for SuspendedFuncTyped<R> {
217+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
218+
f.debug_struct("SuspendedFuncTyped").field("func", &self.func).finish()
219+
}
220+
}
221+
222+
impl<R> crate::coro::CoroState<R, &mut Store> for SuspendedFuncTyped<R>
223+
where
224+
R: FromWasmValueTuple,
225+
{
226+
fn resume(&mut self, ctx: &mut Store, arg: ResumeArgument) -> Result<crate::CoroStateResumeResult<R>> {
227+
self.func.resume(ctx, arg)?.map_result(|vals| R::from_wasm_value_tuple(&vals)).propagate_err()
228+
}
187229
}
188230

189231
macro_rules! impl_into_wasm_value_tuple {

crates/tinywasm/tests/testsuite/util.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ fn make_sometimes_breaking_cb(probability: f64) -> impl FnMut(&tinywasm::Store)
3131
}
3232
}
3333

34-
3534
#[cfg(not(feature = "test_async"))]
3635
pub fn exec_fn_instance(
3736
instance: Option<&ModuleInstanceAddr>,
@@ -51,7 +50,6 @@ pub fn exec_fn_instance(
5150
func.call(store, args)
5251
}
5352

54-
5553
#[cfg(feature = "test_async")]
5654
pub fn exec_fn_instance(
5755
instance: Option<&ModuleInstanceAddr>,
@@ -66,7 +64,7 @@ pub fn exec_fn_instance(
6664
let mut prev_reason = None;
6765
store.update_suspend_conditions(|old_cond| {
6866
prev_reason = Some(old_cond);
69-
SuspendConditions { suspend_cb: Some(Box::new(make_sometimes_breaking_cb(2.0 / 3.0))), ..Default::default() }
67+
SuspendConditions::new().with_suspend_callback(Box::new(make_sometimes_breaking_cb(2.0 / 3.0)))
7068
});
7169
let res = || -> Result<Vec<tinywasm_types::WasmValue>, tinywasm::Error> {
7270
let Some(instance) = store.get_module_instance(*instance) else {
@@ -124,10 +122,9 @@ pub fn exec_fn(
124122

125123
let mut store = tinywasm::Store::new();
126124

127-
store.set_suspend_conditions(SuspendConditions {
128-
suspend_cb: Some(Box::new(make_sometimes_breaking_cb(2.0 / 3.0))),
129-
..Default::default()
130-
});
125+
store.set_suspend_conditions(
126+
SuspendConditions::new().with_suspend_callback(Box::new(make_sometimes_breaking_cb(2.0 / 3.0))),
127+
);
131128

132129
let module = tinywasm::Module::from(module);
133130
let instance = match module.instantiate_coro(&mut store, imports)? {
@@ -160,7 +157,6 @@ pub fn exec_fn(
160157
}
161158
}
162159

163-
164160
pub fn catch_unwind_silent<R>(f: impl FnOnce() -> R) -> std::thread::Result<R> {
165161
let prev_hook = panic::take_hook();
166162
panic::set_hook(Box::new(|_| {}));

examples/host_coro.rs

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@ use tinywasm::{
66
};
77
use wat;
88

9+
fn main() -> eyre::Result<()> {
10+
untyped()?;
11+
typed()?;
12+
Ok(())
13+
}
14+
915
const WASM: &str = r#"(module
1016
(import "host" "hello" (func $host_hello (param i32)))
1117
(import "host" "wait" (func $host_suspend (param i32)(result i32)))
1218
13-
(func (export "call_hello")
19+
(func (export "call_hello") (result f32)
1420
(call $host_hello (i32.const -3))
1521
(call $host_suspend (i32.const 10))
1622
(call $host_hello)
23+
(f32.const 6.28)
1724
)
1825
)
1926
"#;
@@ -38,7 +45,7 @@ impl<'a> CoroState<Vec<WasmValue>, FuncContext<'a>> for MySuspendedState {
3845
}
3946
}
4047

41-
fn main() -> eyre::Result<()> {
48+
fn untyped() -> eyre::Result<()> {
4249
let wasm = wat::parse_str(WASM).expect("failed to parse wat");
4350
let module = Module::parse_bytes(&wasm)?;
4451
let mut store = Store::default();
@@ -47,9 +54,9 @@ fn main() -> eyre::Result<()> {
4754
imports.define(
4855
"host",
4956
"hello",
50-
Extern::typed_func(|_: FuncContext<'_>, x: i32| {
51-
println!("{x}");
52-
Ok(())
57+
Extern::func(&FuncType { params: Box::new([ValType::I32]), results: Box::new([]) }, |_: FuncContext<'_>, x| {
58+
x.first().map(|x| println!("{:?}", x));
59+
Ok(vec![])
5360
}),
5461
)?;
5562
let my_coro_starter = |_ctx: FuncContext<'_>,
@@ -76,7 +83,7 @@ fn main() -> eyre::Result<()> {
7683
let greeter = instance.exported_func_untyped(&store, "call_hello")?;
7784
let call_res = greeter.call_coro(&mut store, &[])?;
7885
let mut resumable = match call_res {
79-
tinywasm::PotentialCoroCallResult::Return(..) => bail!("it's supposed to return"),
86+
tinywasm::PotentialCoroCallResult::Return(..) => bail!("it's not supposed to return yet"),
8087
tinywasm::PotentialCoroCallResult::Suspended(SuspendReason::Yield(Some(val)), resumable) => {
8188
match val.downcast::<MyUserData>() {
8289
Ok(val) => assert_eq!(val.magic, 42),
@@ -88,7 +95,62 @@ fn main() -> eyre::Result<()> {
8895
};
8996

9097
let final_res = resumable.resume(&mut store, Some(Box::<i32>::new(7)))?;
91-
assert!(final_res.finished());
98+
if let CoroStateResumeResult::Return(vals) = final_res {
99+
println!("{:?}", vals.first().unwrap());
100+
} else {
101+
panic!("should have finished");
102+
}
103+
104+
Ok(())
105+
}
106+
107+
fn typed() -> eyre::Result<()> {
108+
let wasm = wat::parse_str(WASM).expect("failed to parse wat");
109+
let module = Module::parse_bytes(&wasm)?;
110+
let mut store = Store::default();
111+
112+
let mut imports = Imports::new();
113+
imports.define(
114+
"host",
115+
"hello",
116+
Extern::typed_func(|_: FuncContext<'_>, x: i32| {
117+
println!("{x}");
118+
Ok(())
119+
}),
120+
)?;
121+
let my_coro_starter =
122+
|_ctx: FuncContext<'_>, base: i32| -> tinywasm::Result<PotentialCoroCallResult<i32, Box<dyn HostCoroState>>> {
123+
let coro = Box::new(MySuspendedState { base: base });
124+
return Ok(PotentialCoroCallResult::Suspended(
125+
SuspendReason::make_yield::<MyUserData>(MyUserData { magic: 42 }),
126+
coro,
127+
));
128+
};
129+
imports.define("host", "wait", Extern::typed_func_coro(my_coro_starter))?;
130+
131+
let instance = module.instantiate(&mut store, Some(imports))?;
132+
133+
let greeter = instance.exported_func::<(), f32>(&store, "call_hello")?;
134+
let call_res = greeter.call_coro(&mut store, ())?;
135+
let mut resumable = match call_res {
136+
tinywasm::PotentialCoroCallResult::Return(..) => bail!("it's not supposed to return yet"),
137+
tinywasm::PotentialCoroCallResult::Suspended(SuspendReason::Yield(Some(val)), resumable) => {
138+
match val.downcast::<MyUserData>() {
139+
Ok(val) => assert_eq!(val.magic, 42),
140+
Err(_) => bail!("invalid yielded val"),
141+
}
142+
resumable
143+
}
144+
tinywasm::PotentialCoroCallResult::Suspended(..) => bail!("wrong suspend"),
145+
};
146+
147+
let final_res = resumable.resume(&mut store, Some(Box::<i32>::new(7)))?;
148+
149+
if let CoroStateResumeResult::Return(res) = final_res {
150+
println!("{res}");
151+
} else {
152+
panic!("should have returned");
153+
}
92154

93155
Ok(())
94156
}

0 commit comments

Comments
 (0)