Skip to main content

hara_native/vm/
fiber.rs

1use std::rc::Rc;
2
3use crate::core::{Promise, PromiseState, Value};
4
5use super::error::VmError;
6use super::machine::{Machine, VmOutcome};
7use super::program::Program;
8
9#[derive(Debug, Clone)]
10pub enum VmFiberState {
11    Running,
12    Suspended,
13    Yielded(Value),
14    Completed(Value),
15    Failed(VmError),
16    Cancelled,
17}
18
19/// Host-independent lifetime for resumable bytecode execution. The machine
20/// owns every frame, local, operand and handler needed to continue.
21pub struct VmFiber {
22    machine: Machine,
23    state: VmFiberState,
24    pending: Option<Promise>,
25}
26
27impl VmFiber {
28    pub fn start(program: Rc<Program>) -> Self {
29        let mut machine = Machine::entry(program);
30        #[cfg(feature = "tracing-jit")]
31        machine.attach_cached_jit();
32        let mut fiber = Self {
33            machine,
34            state: VmFiberState::Running,
35            pending: None,
36        };
37        let outcome = fiber.machine.run();
38        fiber.apply(outcome);
39        fiber
40    }
41
42    /// Starts a specific validated prototype with direct arguments and
43    /// captures. Prepared embedding calls use this to avoid namespace-backed
44    /// request bindings and synthetic source compilation.
45    pub fn start_call(
46        program: Rc<Program>,
47        prototype: u16,
48        arguments: Vec<Value>,
49        captures: Vec<Value>,
50    ) -> Self {
51        let mut machine = Machine::call(program, prototype, arguments, captures);
52        #[cfg(feature = "tracing-jit")]
53        machine.attach_cached_jit();
54        let mut fiber = Self {
55            machine,
56            state: VmFiberState::Running,
57            pending: None,
58        };
59        let outcome = fiber.machine.run();
60        fiber.apply(outcome);
61        fiber
62    }
63
64    pub fn state(&self) -> VmFiberState {
65        self.state.clone()
66    }
67
68    pub fn pending(&self) -> Option<Promise> {
69        self.pending.clone()
70    }
71
72    pub fn resume(&mut self, state: PromiseState) -> VmFiberState {
73        if !matches!(self.state, VmFiberState::Suspended) {
74            return self.state();
75        }
76        self.state = VmFiberState::Running;
77        self.pending = None;
78        let outcome = self.machine.resume(state);
79        self.apply(outcome);
80        self.state()
81    }
82
83    pub fn resume_yield(&mut self, value: Value) -> VmFiberState {
84        if !matches!(self.state, VmFiberState::Yielded(_)) {
85            return self.state();
86        }
87        self.state = VmFiberState::Running;
88        let outcome = self.machine.resume_yield(value);
89        self.apply(outcome);
90        self.state()
91    }
92
93    /// Drains queued child resumptions and advances this fiber when its
94    /// awaited promise became settled. Hosts call this from their event loop.
95    pub fn poll(&mut self) -> VmFiberState {
96        self.machine.poll_async();
97        if matches!(self.state, VmFiberState::Suspended) {
98            let state = self.pending.as_ref().expect("suspended promise").state();
99            if !matches!(state, PromiseState::Pending) {
100                return self.resume(state);
101            }
102        }
103        self.state()
104    }
105
106    pub fn cancel(&mut self) -> bool {
107        if matches!(
108            self.state,
109            VmFiberState::Completed(_) | VmFiberState::Failed(_) | VmFiberState::Cancelled
110        ) {
111            return false;
112        }
113        if let Some(promise) = self.pending.take() {
114            promise.notify_cancel();
115        }
116        #[cfg(feature = "tracing-jit")]
117        self.machine.detach_cached_jit();
118        self.state = VmFiberState::Cancelled;
119        true
120    }
121
122    pub fn drive_sync(&mut self) -> Result<Value, VmError> {
123        loop {
124            match self.state() {
125                VmFiberState::Completed(value) => return Ok(value),
126                VmFiberState::Failed(error) => return Err(error),
127                VmFiberState::Cancelled => {
128                    return Err(VmError::new("cancelled", 0, None));
129                }
130                VmFiberState::Suspended => {
131                    let state = self.pending.as_ref().expect("suspended promise").state();
132                    if matches!(state, PromiseState::Pending) {
133                        return Err(VmError::new(
134                            "VM fiber suspended on an unresolved promise",
135                            0,
136                            None,
137                        ));
138                    }
139                    self.resume(state);
140                }
141                VmFiberState::Yielded(_) => {
142                    return Err(VmError::new(
143                        "VM fiber yielded outside of a coroutine driver",
144                        0,
145                        None,
146                    ));
147                }
148                VmFiberState::Running => {
149                    let outcome = self.machine.run();
150                    self.apply(outcome);
151                }
152            }
153        }
154    }
155
156    fn apply(&mut self, outcome: VmOutcome) {
157        match outcome {
158            VmOutcome::Returned(value) => {
159                #[cfg(feature = "tracing-jit")]
160                self.machine.detach_cached_jit();
161                self.state = VmFiberState::Completed(value);
162            }
163            VmOutcome::Failed(error) => {
164                #[cfg(feature = "tracing-jit")]
165                self.machine.detach_cached_jit();
166                self.state = VmFiberState::Failed(error);
167            }
168            VmOutcome::Suspended(promise) => {
169                self.pending = Some(promise);
170                self.state = VmFiberState::Suspended;
171            }
172            VmOutcome::Yielded(value) => self.state = VmFiberState::Yielded(value),
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    use crate::vm::opcode::Instruction;
181    use crate::vm::program::FunctionPrototype;
182    use crate::vm::source_map::SourceMap;
183
184    fn program(promise: Promise) -> Rc<Program> {
185        let code = vec![
186            Instruction::Constant(0),
187            Instruction::Await,
188            Instruction::Return,
189        ];
190        let mut source_map = SourceMap::default();
191        for _ in &code {
192            source_map.record(None);
193        }
194        Rc::new(Program {
195            namespace: None,
196            constants: vec![Value::Promise(promise)],
197            var_metadata: vec![],
198            schema_types: Default::default(),
199            function_types: Default::default(),
200            inferred_function_types: Default::default(),
201            functions: vec![FunctionPrototype {
202                name: None,
203                async_function: false,
204                arity: 0,
205                variadic: false,
206                capture_count: 0,
207                local_count: 0,
208                max_stack: 1,
209                code,
210                source_map,
211                handlers: vec![],
212            }],
213            entry: 0,
214        })
215    }
216
217    #[test]
218    fn pending_await_preserves_machine_and_resumes() {
219        let promise = Promise::new();
220        let mut fiber = VmFiber::start(program(promise.clone()));
221        assert!(matches!(fiber.state(), VmFiberState::Suspended));
222        promise.resolve(Value::Number(42));
223        assert!(matches!(
224            fiber.resume(promise.state()),
225            VmFiberState::Completed(Value::Number(42))
226        ));
227    }
228
229    #[test]
230    fn settled_await_stays_on_the_synchronous_path() {
231        let promise = Promise::new();
232        promise.resolve(Value::Number(7));
233        let fiber = VmFiber::start(program(promise));
234        assert!(matches!(
235            fiber.state(),
236            VmFiberState::Completed(Value::Number(7))
237        ));
238    }
239}