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
19pub 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 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 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}