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

Commit b66cd50

Browse files
committed
feat: add support for suspending execution by timer, atomic flag or user callback
1 parent 9ff3d6d commit b66cd50

File tree

7 files changed

+177
-65
lines changed

7 files changed

+177
-65
lines changed

crates/tinywasm/src/coro.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,26 @@ use crate::Result;
44
// use alloc::boxed::Box;
55
pub(crate) use tinywasm_types::{ResumeArgument, YieldedValue};
66

7+
8+
/// explains why did execution suspend, and carries payload if needed
79
#[derive(Debug)]
810
pub enum SuspendReason {
911
/// host function yielded
1012
/// potentially some host functions might expect resume argument when calling resume
1113
Yield(YieldedValue),
12-
// /// timer ran out (not implemented),
13-
// /// host shouldn't provide resume argument when calling resume
14-
// SuspendedEpoch,
1514

16-
// /// async should_suspend flag was set (not implemented)
17-
// /// host shouldn't provide resume argument when calling resume
18-
// SuspendedFlag,
15+
/// time to suspend has come,
16+
/// host shouldn't provide resume argument when calling resume
17+
#[cfg(feature = "std")]
18+
SuspendedEpoch,
19+
20+
/// user's should-suspend-callback,
21+
/// host shouldn't provide resume argument when calling resume
22+
SuspendedCallback,
23+
24+
/// async should_suspend flag was set
25+
/// host shouldn't provide resume argument when calling resume
26+
SuspendedFlag,
1927
}
2028

2129
/// result of a function that might pause in the middle and yield

crates/tinywasm/src/func.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,20 @@ pub(self) enum SuspendFuncInner {
3737
Host(SuspendedHostCoroState),
3838
}
3939

40+
/// handle to function that was suspended and can be resumed
4041
#[derive(Debug)]
4142
pub struct SuspendFunc {
42-
pub(self) func: SuspendFuncInner,
43-
pub(crate) module_addr: ModuleInstanceAddr,
43+
func: SuspendFuncInner,
44+
module_addr: ModuleInstanceAddr,
45+
store_id: usize,
4446
}
4547

4648
impl<'a> crate::coro::CoroState<Vec<WasmValue>, &mut Store> for SuspendFunc {
4749
fn resume(&mut self, store: &mut Store, arg: ResumeArgument) -> Result<FuncHandleResumeOutcome> {
50+
if store.id() != self.store_id {
51+
return Err(Error::InvalidStore);
52+
}
53+
4854
let ctx = FuncContext { store, module_addr: self.module_addr };
4955
match &mut self.func {
5056
SuspendFuncInner::Wasm(wasm) => wasm.resume(ctx, arg),
@@ -110,6 +116,7 @@ impl FuncHandle {
110116
coro_orig_function: self.addr,
111117
}),
112118
module_addr: self.module_addr,
119+
store_id: store.id(),
113120
}));
114121
}
115122
Function::Wasm(wasm_func) => wasm_func,
@@ -145,6 +152,7 @@ impl FuncHandle {
145152
result_types: func_ty.results.clone(),
146153
}),
147154
module_addr: self.module_addr,
155+
store_id: store.id(),
148156
}
149157
}))
150158
}

crates/tinywasm/src/interpreter/executor.rs

Lines changed: 86 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,13 @@ impl<'store, 'stack> Executor<'store, 'stack> {
7878
return Ok(ExecOutcome::Suspended(suspend_reason));
7979
}
8080
};
81-
self.after_resume_host_coro(&res);
81+
self.stack.values.extend_from_wasmvalues(&res);
8282
self.suspended_host_coro = None;
83+
84+
// we don't know how much time we spent in host function
85+
if let ControlFlow::Break(ReasonToBreak::Suspended(reason)) = self.check_should_suspend() {
86+
return Ok(ExecOutcome::Suspended(reason));
87+
}
8388
}
8489

8590
loop {
@@ -93,9 +98,35 @@ impl<'store, 'stack> Executor<'store, 'stack> {
9398
}
9499
}
95100

96-
fn after_resume_host_coro(&mut self, vals: &[WasmValue]) {
97-
self.stack.values.extend_from_wasmvalues(&vals);
98-
self.cf.incr_instr_ptr();
101+
/// for controlling how long execution spends in wasm
102+
/// called when execution loops back, because that might happen indefinite amount of times
103+
/// and before and after function calls, because even without loops or infinite recursion, wasm function calls
104+
/// can mutliply time spent in execution
105+
/// execution may not be suspended in the middle of execution the funcion:
106+
/// so only do it as the last thing or first thing in the intsruction execution
107+
fn check_should_suspend(&mut self) -> ControlFlow<ReasonToBreak> {
108+
if let Some(flag) = &self.store.suspend_cond.suspend_flag {
109+
if flag.load(core::sync::atomic::Ordering::Acquire) {
110+
return ReasonToBreak::Suspended(SuspendReason::SuspendedFlag).into();
111+
}
112+
}
113+
114+
#[cfg(feature = "std")]
115+
if let Some(when) = &self.store.suspend_cond.timeout_instant {
116+
if crate::std::time::Instant::now() >= *when {
117+
return ReasonToBreak::Suspended(SuspendReason::SuspendedEpoch).into();
118+
}
119+
}
120+
121+
if let Some(mut cb) = self.store.suspend_cond.suspend_cb.take() {
122+
let should_suspend = matches!(cb(&self.store), ControlFlow::Break(()));
123+
self.store.suspend_cond.suspend_cb = Some(cb); // put it back
124+
if should_suspend {
125+
return ReasonToBreak::Suspended(SuspendReason::SuspendedCallback).into();
126+
}
127+
}
128+
129+
ControlFlow::Continue(())
99130
}
100131

101132
#[inline(always)]
@@ -382,35 +413,41 @@ impl<'store, 'stack> Executor<'store, 'stack> {
382413
self.module.swap_with(self.cf.module_addr(), self.store);
383414
ControlFlow::Continue(())
384415
}
416+
fn exec_call_host(&mut self, host_func: Rc<HostFunction>, func_ref: u32) -> ControlFlow<ReasonToBreak> {
417+
let params = self.stack.values.pop_params(&host_func.ty.params);
418+
let res =
419+
(host_func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, &params).to_cf()?;
420+
match res {
421+
PotentialCoroCallResult::Return(res) => {
422+
self.stack.values.extend_from_wasmvalues(&res);
423+
self.cf.incr_instr_ptr();
424+
self.check_should_suspend()?; // who knows how long we've spent in host function
425+
return ControlFlow::Continue(());
426+
}
427+
PotentialCoroCallResult::Suspended(suspend_reason, state) => {
428+
self.suspended_host_coro =
429+
Some(SuspendedHostCoroState { coro_state: state, coro_orig_function: func_ref });
430+
self.cf.incr_instr_ptr();
431+
return ReasonToBreak::Suspended(suspend_reason).into();
432+
}
433+
}
434+
}
385435
fn exec_call_direct(&mut self, v: u32) -> ControlFlow<ReasonToBreak> {
436+
self.check_should_suspend()?; // don't commit to function if we should be stopping now
386437
let func_ref = self.module.resolve_func_addr(v);
387438
let func_inst = self.store.get_func(func_ref);
388439
let wasm_func = match &func_inst.func {
389440
crate::Function::Wasm(wasm_func) => wasm_func,
390441
crate::Function::Host(host_func) => {
391-
let func = &host_func.clone();
392-
let params = self.stack.values.pop_params(&host_func.ty.params);
393-
let res =
394-
(func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, &params).to_cf()?;
395-
match res {
396-
PotentialCoroCallResult::Return(res) => {
397-
self.stack.values.extend_from_wasmvalues(&res);
398-
self.cf.incr_instr_ptr();
399-
return ControlFlow::Continue(());
400-
}
401-
PotentialCoroCallResult::Suspended(suspend_reason, state) => {
402-
self.suspended_host_coro =
403-
Some(SuspendedHostCoroState { coro_state: state, coro_orig_function: func_ref });
404-
return ReasonToBreak::Suspended(suspend_reason).into();
405-
}
406-
}
442+
return self.exec_call_host(host_func.clone(), func_ref);
407443
}
408444
};
409445

410446
self.exec_call(wasm_func.clone(), func_inst.owner)
411447
}
412448
fn exec_call_indirect(&mut self, type_addr: u32, table_addr: u32) -> ControlFlow<ReasonToBreak> {
413-
// verify that the table is of the right type, this should be validated by the parser already
449+
self.check_should_suspend()?; // check if we should suspend now before commiting to function
450+
// verify that the table is of the right type, this should be validated by the parser already
414451
let func_ref = {
415452
let table = self.store.get_table(self.module.resolve_table_addr(table_addr));
416453
let table_idx: u32 = self.stack.values.pop::<i32>() as u32;
@@ -436,27 +473,7 @@ impl<'store, 'stack> Executor<'store, 'stack> {
436473
)
437474
.into();
438475
}
439-
440-
let host_func = host_func.clone();
441-
let params = self.stack.values.pop_params(&host_func.ty.params);
442-
let res =
443-
match (host_func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, &params) {
444-
Ok(res) => res,
445-
Err(e) => return ReasonToBreak::Errored(e).into(),
446-
};
447-
match res {
448-
PotentialCoroCallResult::Return(res) => {
449-
self.stack.values.extend_from_wasmvalues(&res);
450-
self.cf.incr_instr_ptr();
451-
}
452-
PotentialCoroCallResult::Suspended(suspend_reason, state) => {
453-
self.suspended_host_coro =
454-
Some(SuspendedHostCoroState { coro_state: state, coro_orig_function: func_ref });
455-
return ReasonToBreak::Suspended(suspend_reason).into();
456-
}
457-
}
458-
459-
return ControlFlow::Continue(());
476+
return self.exec_call_host(host_func.clone(), func_ref);
460477
}
461478
};
462479

@@ -505,20 +522,30 @@ impl<'store, 'stack> Executor<'store, 'stack> {
505522
});
506523
}
507524
fn exec_br(&mut self, to: u32) -> ControlFlow<ReasonToBreak> {
508-
if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() {
525+
let break_type = if let Some(bl_ty) = self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks) {
526+
bl_ty
527+
} else {
509528
return self.exec_return();
510-
}
529+
};
511530

512531
self.cf.incr_instr_ptr();
532+
533+
if matches!(break_type, BlockType::Loop) {
534+
self.check_should_suspend()?;
535+
}
536+
513537
ControlFlow::Continue(())
514538
}
515539
fn exec_br_if(&mut self, to: u32) -> ControlFlow<ReasonToBreak> {
516-
if self.stack.values.pop::<i32>() != 0
517-
&& self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none()
518-
{
540+
let break_type = self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks);
541+
if self.stack.values.pop::<i32>() != 0 && break_type.is_none() {
519542
return self.exec_return();
520543
}
521544
self.cf.incr_instr_ptr();
545+
546+
if matches!(break_type, Some(BlockType::Loop)) {
547+
self.check_should_suspend()?;
548+
}
522549
ControlFlow::Continue(())
523550
}
524551
fn exec_brtable(&mut self, default: u32, len: u32) -> ControlFlow<ReasonToBreak> {
@@ -540,11 +567,19 @@ impl<'store, 'stack> Executor<'store, 'stack> {
540567
_ => return ReasonToBreak::Errored(Error::Other("br_table out of bounds".to_string())).into(),
541568
};
542569

543-
if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() {
570+
let break_type = self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks);
571+
let break_type = if let Some(br_ty) = break_type {
572+
br_ty
573+
} else {
544574
return self.exec_return();
545-
}
575+
};
546576

547577
self.cf.incr_instr_ptr();
578+
579+
if matches!(break_type, BlockType::Loop) {
580+
self.check_should_suspend()?;
581+
}
582+
548583
ControlFlow::Continue(())
549584
}
550585
fn exec_return(&mut self) -> ControlFlow<ReasonToBreak> {
@@ -559,6 +594,8 @@ impl<'store, 'stack> Executor<'store, 'stack> {
559594
}
560595

561596
self.module.swap_with(self.cf.module_addr(), self.store);
597+
598+
self.check_should_suspend()?;
562599
ControlFlow::Continue(())
563600
}
564601
fn exec_end_block(&mut self) {

crates/tinywasm/src/interpreter/stack/block_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub(crate) struct BlockFrame {
6060
pub(crate) ty: BlockType,
6161
}
6262

63-
#[derive(Debug)]
63+
#[derive(Debug, Clone, Copy)]
6464
pub(crate) enum BlockType {
6565
Loop,
6666
If,

crates/tinywasm/src/interpreter/stack/call_stack.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,13 @@ impl CallFrame {
107107
break_to_relative: u32,
108108
values: &mut super::ValueStack,
109109
blocks: &mut super::BlockStack,
110-
) -> Option<()> {
110+
) -> Option<BlockType> {
111111
let break_to = blocks.get_relative_to(break_to_relative, self.block_ptr)?;
112-
112+
113+
let block_ty = break_to.ty;
113114
// instr_ptr points to the label instruction, but the next step
114115
// will increment it by 1 since we're changing the "current" instr_ptr
115-
match break_to.ty {
116+
match block_ty {
116117
BlockType::Loop => {
117118
// this is a loop, so we want to jump back to the start of the loop
118119
self.instr_ptr = break_to.instr_ptr;
@@ -124,7 +125,7 @@ impl CallFrame {
124125
if break_to_relative != 0 {
125126
// we also want to trim the label stack to the loop (but not including the loop)
126127
blocks.truncate(blocks.len() as u32 - break_to_relative);
127-
return Some(());
128+
return Some(BlockType::Loop);
128129
}
129130
}
130131

@@ -141,7 +142,7 @@ impl CallFrame {
141142
}
142143
}
143144

144-
Some(())
145+
Some(block_ty)
145146
}
146147

147148
#[inline]

crates/tinywasm/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ pub(crate) mod log {
9393
mod error;
9494
pub use coro::{CoroState, CoroStateResumeResult, PotentialCoroCallResult, SuspendReason};
9595
pub use error::*;
96-
pub use func::{FuncHandle, FuncHandleTyped};
96+
pub use func::{FuncHandle, FuncHandleTyped, SuspendFunc};
9797
pub use imports::*;
9898
pub use instance::ModuleInstance;
9999
pub use module::Module;

0 commit comments

Comments
 (0)