wasm/execution/
resumable.rs

1use core::mem;
2
3use alloc::{
4    sync::{Arc, Weak},
5    vec::Vec,
6};
7
8use crate::{
9    core::slotmap::{SlotMap, SlotMapKey},
10    execution::interpreter_loop,
11    hooks::EmptyHookSet,
12    rw_spinlock::RwSpinLock,
13    value_stack::Stack,
14    RuntimeError, Value,
15};
16
17use super::RuntimeInstance;
18
19#[derive(Debug)]
20pub struct Resumable {
21    pub(crate) stack: Stack,
22    pub(crate) pc: usize,
23    pub(crate) stp: usize,
24    pub(crate) current_func_addr: usize,
25}
26
27#[derive(Default)]
28pub struct Dormitory(Arc<RwSpinLock<SlotMap<Resumable>>>);
29
30impl Dormitory {
31    #[allow(unused)]
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    pub fn insert(&self, resumable: Resumable) -> ResumableRef {
37        let key = self.0.write().insert(resumable);
38
39        ResumableRef {
40            dormitory: Arc::downgrade(&self.0),
41            key,
42        }
43    }
44}
45
46pub struct ResumableRef {
47    dormitory: Weak<RwSpinLock<SlotMap<Resumable>>>,
48    key: SlotMapKey<Resumable>,
49}
50
51impl ResumableRef {
52    pub fn resume<T>(
53        mut self,
54        runtime_instance: &mut RuntimeInstance<T>,
55        fuel: u32,
56    ) -> Result<RunState, RuntimeError> {
57        // Resuming requires `self`'s dormitory to still be alive
58        let Some(dormitory) = self.dormitory.upgrade() else {
59            return Err(RuntimeError::ResumableNotFound);
60        };
61
62        // Check the given `RuntimeInstance` is the same one used to create `self`
63        if !Arc::ptr_eq(&dormitory, &runtime_instance.store.dormitory.0) {
64            return Err(RuntimeError::ResumableNotFound);
65        }
66
67        // Obtain a write lock to the `Dormitory`
68        let mut dormitory = dormitory.write();
69
70        // TODO We might want to remove the `Resumable` here already and later reinsert it.
71        // This would prevent holding the lock across the interpreter loop.
72        let resumable = dormitory
73            .get_mut(&self.key)
74            .expect("the key to always be valid as self was not dropped yet");
75
76        // Resume execution
77        let result = interpreter_loop::run(
78            resumable,
79            &mut runtime_instance.store,
80            EmptyHookSet,
81            Some(fuel),
82        );
83
84        match result {
85            Ok(()) => {
86                let resumable = dormitory.remove(&self.key)
87                    .expect("that the resumable could not have been removed already, because then this self could not exist");
88
89                // Take the `Weak` pointing to the dormitory out of `self` and replace it with a default `Weak`.
90                // This causes the `Drop` impl of `self` to directly quit preventing it from unnecessarily locking the dormitory.
91                let _dormitory = mem::take(&mut self.dormitory);
92
93                Ok(RunState::Finished(resumable.stack.into_values()))
94            }
95            Err(RuntimeError::OutOfFuel) => Ok(RunState::Resumable(self)),
96            Err(err) => Err(err),
97        }
98    }
99}
100
101impl Drop for ResumableRef {
102    fn drop(&mut self) {
103        let Some(dormitory) = self.dormitory.upgrade() else {
104            // Either the dormitory was already dropped or `self` was used to finish execution.
105            return;
106        };
107
108        dormitory.write().remove(&self.key)
109            .expect("that the resumable could not have been removed already, because then this self could not exist or the dormitory weak pointer would have been None");
110    }
111}
112
113pub enum RunState {
114    Finished(Vec<Value>),
115    Resumable(ResumableRef),
116}
117
118#[cfg(test)]
119mod test {
120    use crate::value_stack::Stack;
121
122    use super::{Dormitory, Resumable};
123
124    /// Test that a dormitory can be constructed and that a resumable can be inserted
125    #[test]
126    fn dormitory_constructor() {
127        let dorm = Dormitory::new();
128
129        let resumable = Resumable {
130            stack: Stack::new(),
131            pc: 11,
132            stp: 13,
133            current_func_addr: 17,
134        };
135
136        dorm.insert(resumable);
137    }
138}