Main dispatch loop. Walks the fused instruction stream (LoadAttr+Call already collapsed to CallMethod+CallMethodArgs); checks the IC inline for hot arith/compare opcodes. */
(&mut self, chunk: &SSAChunk, slots: &mut [Val])
| 143 | |
| 144 | /* Main dispatch loop. Walks the fused instruction stream (LoadAttr+Call already collapsed to CallMethod+CallMethodArgs); checks the IC inline for hot arith/compare opcodes. */ |
| 145 | pub(crate) fn exec(&mut self, chunk: &SSAChunk, slots: &mut [Val]) -> Result<Val, VmErr> { |
| 146 | |
| 147 | let slots_base = self.live_slots.len(); |
| 148 | // `resume_coroutine` pre-pushes restored exception frames before calling us; honor its override so dispatch's handler search includes them. |
| 149 | let exc_base = self.pending_exec_exc_base.take().unwrap_or(self.exception_stack.len()); |
| 150 | // Cleanup reasons belong to this frame's finally bodies; drop leftovers when it returns. |
| 151 | let unwind_base = self.unwind_stack.len(); |
| 152 | let key = chunk as *const _; |
| 153 | |
| 154 | let mut cache = self.opcode_caches.remove(&key).unwrap_or_else(|| OpcodeCache::new(chunk)); |
| 155 | cache.ensure_fused(chunk); |
| 156 | // Pre-materialise the constant pool here (not in OpcodeCache::new) because Str allocates into the live HeapPool. |
| 157 | if let Err(e) = cache.ensure_const_vals(chunk, &mut self.heap) { |
| 158 | self.opcode_caches.insert(key, cache); |
| 159 | return Err(e); |
| 160 | } |
| 161 | |
| 162 | // Hoist slices out of the loop; cache outlives exec() and isn't mutated meanwhile. |
| 163 | let insns_ptr: *const [Instruction] = cache.fused_ref(); |
| 164 | let consts_ptr: *const [Val] = cache.const_vals_ref(); |
| 165 | self.active_const_pools.push(consts_ptr); |
| 166 | let result: Result<Val, VmErr> = (|| { |
| 167 | // SAFETY: see comment above. |
| 168 | let insns: &[Instruction] = unsafe { &*insns_ptr }; |
| 169 | let consts: &[Val] = unsafe { &*consts_ptr }; |
| 170 | let n = insns.len(); |
| 171 | let mut ip = self.resume_ip; |
| 172 | self.resume_ip = 0; |
| 173 | |
| 174 | loop { |
| 175 | if ip >= n { |
| 176 | self.exception_stack.truncate(exc_base); |
| 177 | self.unwind_stack.truncate(unwind_base); |
| 178 | return Ok(Val::none()); |
| 179 | } |
| 180 | |
| 181 | let rip = ip; |
| 182 | match self.dispatch(chunk, slots, &mut cache, insns, consts, &mut ip, exc_base) { |
| 183 | Ok(None) => { |
| 184 | if self.yielded { |
| 185 | // Event yields keep the None placeholder (overwritten by `run_push_event` before resume). Sync sub-call yields pushed nothing, the helper's return lands on the stack when its frame completes, so don't pop and don't skip the next PopTop. Child-wait yields keep the placeholder (wake-loop overwrites it with the target's result). Host-call yields keep the placeholder (overwritten by `set_host_result`). |
| 186 | let event_yield = self.pending.event_wait_request; |
| 187 | let sub_call_yield = !self.pending_sync_frames.is_empty(); |
| 188 | let child_yield = self.pending.waiting_for_children.is_some(); |
| 189 | let host_yield = self.pending.host_call_request; |
| 190 | let val = if event_yield || sub_call_yield || child_yield || host_yield { Val::none() } else { self.pop().unwrap_or(Val::none()) }; |
| 191 | self.resume_ip = if !event_yield && !sub_call_yield && !child_yield && !host_yield && ip < n && matches!(insns.get(ip), Some(ins) if ins.opcode == OpCode::PopTop) { ip + 1 } else { ip }; |
| 192 | self.live_slots.truncate(slots_base); |
| 193 | // DON'T truncate exception_stack here, frames pushed in this exec belong to active try/except blocks; the enclosing `resume_coroutine` drains them into the coroutine's saved state so `try` survives the yield. |
| 194 | return Ok(val); |
| 195 | } |
| 196 | } |
| 197 | Ok(Some(v)) => { |
| 198 | self.live_slots.truncate(slots_base); |
| 199 | self.exception_stack.truncate(exc_base); |
| 200 | self.unwind_stack.truncate(unwind_base); |
| 201 | return Ok(v); |
| 202 | } |
no test coverage detected