| 140 | } |
| 141 | |
| 142 | pub fn run(&mut self) -> Result<Val, VmErr> { |
| 143 | self.error_byte_pos = None; |
| 144 | // Resume path: scheduler non-empty means a prior `run()` yielded; wake `WaitingFrame` (rAF fired) and drain. |
| 145 | let fresh_entry = self.scheduler.is_empty(); |
| 146 | if fresh_entry { |
| 147 | // Fresh entry. Initialise imports before user code; DFS gives topological order naturally. |
| 148 | let mut in_progress: crate::util::fx::FxHashSet<String> = crate::util::fx::FxHashSet::default(); |
| 149 | self.init_modules(self.chunk, &mut in_progress)?; |
| 150 | // Wrap the module body as an implicit coroutine; lets top-level statements suspend on deferred host calls (DOM, sleep, receive) through the same scheduler path as `async def`. |
| 151 | let slots = self.fill_builtins(&self.chunk.names); |
| 152 | let coro = self.heap.alloc(HeapObj::Coroutine( |
| 153 | 0, slots, Vec::new(), |
| 154 | BodyRef::Module, |
| 155 | Vec::new(), Vec::new(), Vec::new(), |
| 156 | ))?; |
| 157 | self.scheduler.push(CoroutineHandle { |
| 158 | coro, |
| 159 | state: CoroState::Ready, |
| 160 | }); |
| 161 | } else { |
| 162 | for h in self.scheduler.iter_mut() { |
| 163 | if matches!(h.state, CoroState::WaitingFrame) { |
| 164 | h.state = CoroState::Ready; |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | self.top_loop()?; |
| 169 | // Inspect the module body's outcome (BodyRef::Module). Single entry point for both fresh and resume. |
| 170 | let module_coro = self.scheduler.iter().find(|h| { |
| 171 | matches!(self.heap.get(h.coro), HeapObj::Coroutine(_, _, _, BodyRef::Module, _, _, _)) |
| 172 | }).map(|h| (h.coro, h.state.clone())); |
| 173 | if let Some((_coro, state)) = module_coro { |
| 174 | // Clear the scheduler only once the module body is terminal, otherwise we're mid-yield and need to keep it for the next resume. |
| 175 | let terminal = matches!(state, |
| 176 | CoroState::Done(_) |
| 177 | | CoroState::Errored(_) |
| 178 | | CoroState::Cancelled); |
| 179 | if terminal { self.scheduler.clear(); } |
| 180 | return match state { |
| 181 | CoroState::Done(v) => Ok(v), |
| 182 | CoroState::Errored(e) => Err(e), |
| 183 | CoroState::Cancelled => Err(VmErr::Raised("CancelledError".into())), |
| 184 | _ => Ok(Val::none()), |
| 185 | }; |
| 186 | } |
| 187 | Ok(Val::none()) |
| 188 | } |
| 189 | |
| 190 | /* Init each unique import once; code modules run their top-level, native ones just bind. `in_progress` catches cycles cleanly. */ |
| 191 | fn init_modules(&mut self, chunk: &SSAChunk, in_progress: &mut crate::util::fx::FxHashSet<String>) -> Result<(), VmErr> { |