Skip to main content

rustpython_vm/vm/
mod.rs

1//! Implement virtual machine to run instructions.
2//!
3//! See also:
4//!   <https://github.com/ProgVal/pythonvm-rust/blob/master/src/processor/mod.rs>
5
6#[cfg(feature = "rustpython-compiler")]
7mod compile;
8pub(crate) mod compile_mode;
9#[cfg(feature = "rustpython-compiler")]
10pub use compile::VmCompileError;
11mod context;
12pub mod crossinterp;
13mod interpreter;
14mod method;
15#[cfg(feature = "rustpython-compiler")]
16mod python_run;
17pub mod runtime;
18mod setting;
19pub mod thread;
20mod vm_new;
21mod vm_object;
22mod vm_ops;
23
24use crate::{
25    AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
26    builtins::{
27        self, PyBaseExceptionRef, PyBaseObject, PyDict, PyDictRef, PyFrozenSet, PyInt, PyList,
28        PyModule, PySet, PyStr, PyStrInterned, PyStrRef, PyTypeRef, PyUtf8Str, PyUtf8StrInterned,
29        PyWeak,
30        code::PyCode,
31        dict::{PyDictItems, PyDictKeys, PyDictValues},
32        pystr::AsPyStr,
33        tuple::PyTuple,
34    },
35    codecs::CodecsRegistry,
36    common::{hash::HashSecret, lock::PyMutex, rc::PyRc},
37    convert::ToPyObject,
38    exceptions::types::{PyBaseException, PyMemoryError},
39    frame::{ExecutionResult, FrameObject, FrameObjectRef},
40    frozen::FrozenModule,
41    function::{ArgMapping, FuncArgs, PySetterValue},
42    import,
43    protocol::{PyIterIter, PyIterReturn},
44    scope::Scope,
45    signal::{self, SignalHandlers},
46    stdlib,
47    types::{GetattroFunc, fn_addr},
48    warn::WarningsState,
49};
50use alloc::{borrow::Cow, collections::BTreeMap};
51#[cfg(all(not(unix), feature = "threading"))]
52use core::ptr::NonNull;
53use core::{
54    cell::{Cell, OnceCell, RefCell},
55    sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering},
56};
57use crossbeam_utils::atomic::AtomicCell;
58use std::{
59    collections::{HashMap, HashSet},
60    ffi::{OsStr, OsString},
61};
62
63pub use context::Context;
64pub use interpreter::{Interpreter, InterpreterBuilder};
65pub(crate) use method::PyMethod;
66pub use runtime::{
67    InterpFeatureFlags, InterpreterConfig, InterpreterGil, InterpreterInfo, InterpreterWhence,
68    MAIN_INTERPRETER_ID,
69};
70pub use setting::{CheckHashPycsMode, Paths, PyConfig, Settings};
71
72pub const MAX_MEMORY_SIZE: usize = isize::MAX as usize;
73
74// Objects are live when they are on stack, or referenced by a name (for now)
75
76/// Per-thread execution context for a single interpreter (≈ CPython `PyThreadState`).
77///
78/// A `VirtualMachine` holds thread-local eval state (exceptions, recursion, frames,
79/// datastack) plus shared references to interpreter-owned data (`state`,
80/// `builtins`, `sys_module`, `ctx`). Multiple VMs may share the same
81/// [`PyGlobalState`] via `VirtualMachine::new_thread`; distinct interpreters
82/// each have their own `PyGlobalState` (see [`Interpreter::create_subinterpreter`]).
83///
84/// To construct the main VM of an interpreter, use [`Interpreter`].
85pub struct VirtualMachine {
86    pub builtins: PyRef<PyModule>,
87    pub sys_module: PyRef<PyModule>,
88    pub ctx: PyRc<Context>,
89    /// Thread-local data stack for bump-allocating frame-local data
90    /// (localsplus arrays for non-generator frames).
91    datastack: core::cell::UnsafeCell<crate::datastack::DataStack>,
92    pub wasm_id: Option<String>,
93    exceptions: RefCell<ExceptionStack>,
94    pub import_func: PyObjectRef,
95    pub(crate) importlib: PyObjectRef,
96    pub profile_func: RefCell<PyObjectRef>,
97    pub trace_func: RefCell<PyObjectRef>,
98    pub use_tracing: Cell<bool>,
99    /// Event currently being monitored (`tstate->what_event`).
100    /// `None` when not in a monitoring callback.
101    pub(crate) what_event: Cell<Option<crate::stdlib::sys::monitoring::MonitoringEvent>>,
102    tracing_depth: Cell<usize>,
103    pub recursion_limit: Cell<usize>,
104    pub(crate) signal_handlers: OnceCell<SignalHandlers>,
105    pub(crate) signal_rx: Option<signal::UserSignalReceiver>,
106    pub repr_guards: RefCell<HashSet<usize>>,
107    pub state: PyRc<PyGlobalState>,
108    pub initialized: bool,
109    recursion_depth: Cell<usize>,
110    /// Depth of native recursion that pushes no Python frame, counted only
111    /// where the stack pointer cannot be read. Everywhere else the native
112    /// stack itself answers, and nothing needs counting.
113    #[cfg(any(miri, target_env = "musl"))]
114    native_recursion_depth: Cell<usize>,
115    /// C stack soft limit for detecting stack overflow (like c_stack_soft_limit)
116    #[cfg_attr(any(miri, target_env = "musl"), allow(dead_code))]
117    c_stack_soft_limit: Cell<usize>,
118    /// Async generator firstiter hook (per-thread, set via sys.set_asyncgen_hooks)
119    pub async_gen_firstiter: RefCell<Option<PyObjectRef>>,
120    /// Async generator finalizer hook (per-thread, set via sys.set_asyncgen_hooks)
121    pub async_gen_finalizer: RefCell<Option<PyObjectRef>>,
122    /// Current running asyncio event loop for this thread
123    pub asyncio_running_loop: RefCell<Option<PyObjectRef>>,
124    /// Current running asyncio task for this thread
125    pub asyncio_running_task: RefCell<Option<PyObjectRef>>,
126    /// Active Context stack for this thread and interpreter (PEP 567 / contextvars)
127    pub context_stack: RefCell<Vec<PyObjectRef>>,
128    pub(crate) callable_cache: CallableCache,
129    /// Side channel for TailCall: the bytecode loop stores the new frame
130    /// pointer here before returning `ExecutionResult::TailCall`.
131    /// Access only via `set_pending_tailcall` / `take_pending_tailcall`.
132    pending_tailcall_frame: Cell<Option<PendingFrame>>,
133    /// Owned reference that keeps callee raw pointers valid during TailCall.
134    /// Set by the exact-call handlers and moved into the trampoline's
135    /// `SuspendedFrame`. Uses UnsafeCell because the VM is per-thread and this
136    /// field is only accessed on the owning thread.
137    pending_tailcall_owner: core::cell::UnsafeCell<Option<PyObjectRef>>,
138    /// Side channel for GenResume, the counterpart of `pending_tailcall_*`:
139    /// the bytecode loop parks the generator to resume, the value to send it
140    /// and what to do with its outcome here before returning
141    /// `ExecutionResult::GenResume`.
142    pending_gen_resume: core::cell::UnsafeCell<Option<PendingGenResume>>,
143    /// Reusable backing store for the trampoline's suspended-frame stack.
144    /// Trampoline invocations nest strictly LIFO, so each one owns the region
145    /// above the length it found on entry and truncates back to it on the way
146    /// out; reusing one allocation keeps a trampoline entry free of malloc,
147    /// which matters because a generator body enters one per resume.
148    /// UnsafeCell because the VM is per-thread and no reference into the Vec
149    /// is held across anything that could push to it.
150    trampoline_stack: core::cell::UnsafeCell<Vec<SuspendedFrame>>,
151}
152
153/// Non-owning frame pointer for the non-unix threading frames stack.
154/// The pointed-to frame is kept alive by the caller of with_frame/resume_gen_frame.
155/// Unix threading builds publish the top frame through `ThreadSlot::top_frame`
156/// and walk the rest via `FrameObject::previous`, so they do not use this type.
157#[cfg(all(not(unix), feature = "threading"))]
158#[derive(Copy, Clone)]
159pub struct FramePtr(NonNull<Py<FrameObject>>);
160
161#[cfg(all(not(unix), feature = "threading"))]
162impl FramePtr {
163    /// # Safety
164    /// The pointed-to frame must still be alive.
165    #[must_use]
166    pub unsafe fn as_ref(&self) -> &Py<FrameObject> {
167        unsafe { self.0.as_ref() }
168    }
169}
170
171// SAFETY: FramePtr is only stored in a thread's shared frame stack
172// (`ThreadSlot::frames`) while the corresponding FrameObjectRef is alive on that
173// thread's call stack; readers dereference it under the slot mutex.
174#[cfg(all(not(unix), feature = "threading"))]
175unsafe impl Send for FramePtr {}
176
177#[derive(Debug)]
178struct ExceptionStack {
179    /// Linked list of handled-exception slots (`_PyErr_StackItem` chain).
180    /// Bottom element is the thread's base slot; generator/coroutine resume
181    /// pushes an additional slot.  Normal frame calls do **not** push/pop.
182    stack: Vec<Option<PyBaseExceptionRef>>,
183}
184
185impl Default for ExceptionStack {
186    fn default() -> Self {
187        // Thread's base `_PyErr_StackItem` – always present.
188        Self { stack: vec![None] }
189    }
190}
191
192/// Stop-the-world state for fork safety. Before `fork()`, the requester
193/// stops all other Python threads so they are not holding internal locks.
194#[cfg(feature = "threading")]
195pub struct StopTheWorldState {
196    /// Fast-path flag checked in the bytecode loop (like `_PY_EVAL_PLEASE_STOP_BIT`)
197    pub(crate) requested: AtomicBool,
198    /// Whether the world is currently stopped (`stw->world_stopped`).
199    world_stopped: AtomicBool,
200    /// Ident of the thread that requested the stop (like `stw->requester`)
201    requester: AtomicU64,
202    /// Single exclusion held for the whole stop→start span. Fork and GC are
203    /// both stop-the-world requesters driving this shared state; only one may
204    /// hold it at a time. Acquired before any stop bookkeeping (see
205    /// `acquire_exclusion`) and released by `start_the_world`/`reset_after_fork`.
206    exclusion: AtomicBool,
207    /// Signaled by suspending threads when their state transitions to SUSPENDED
208    notify_mutex: std::sync::Mutex<()>,
209    notify_cv: std::sync::Condvar,
210    /// Number of non-requester threads still expected to park for current stop request.
211    thread_countdown: AtomicI64,
212    /// Number of stop-the-world attempts.
213    stats_stop_calls: AtomicU64,
214    /// Most recent stop-the-world wait duration in ns.
215    stats_last_wait_ns: AtomicU64,
216    /// Total accumulated stop-the-world wait duration in ns.
217    stats_total_wait_ns: AtomicU64,
218    /// Max observed stop-the-world wait duration in ns.
219    stats_max_wait_ns: AtomicU64,
220    /// Number of poll-loop iterations spent waiting.
221    stats_poll_loops: AtomicU64,
222    /// Number of ATTACHED threads observed while polling.
223    stats_attached_seen: AtomicU64,
224    /// Number of DETACHED->SUSPENDED parks requested by requester.
225    stats_forced_parks: AtomicU64,
226    /// Number of suspend notifications from worker threads.
227    stats_suspend_notifications: AtomicU64,
228    /// Number of yield loops while attach waited on SUSPENDED->DETACHED.
229    stats_attach_wait_yields: AtomicU64,
230    /// Number of yield loops while suspend waited on SUSPENDED->DETACHED.
231    stats_suspend_wait_yields: AtomicU64,
232}
233
234#[cfg(feature = "threading")]
235#[derive(Debug, Clone, Copy)]
236pub struct StopTheWorldStats {
237    pub stop_calls: u64,
238    pub last_wait_ns: u64,
239    pub total_wait_ns: u64,
240    pub max_wait_ns: u64,
241    pub poll_loops: u64,
242    pub attached_seen: u64,
243    pub forced_parks: u64,
244    pub suspend_notifications: u64,
245    pub attach_wait_yields: u64,
246    pub suspend_wait_yields: u64,
247    pub world_stopped: bool,
248}
249
250#[cfg(feature = "threading")]
251impl Default for StopTheWorldState {
252    fn default() -> Self {
253        Self::new()
254    }
255}
256
257#[cfg(feature = "threading")]
258impl StopTheWorldState {
259    #[must_use]
260    pub const fn new() -> Self {
261        Self {
262            requested: AtomicBool::new(false),
263            world_stopped: AtomicBool::new(false),
264            requester: AtomicU64::new(0),
265            exclusion: AtomicBool::new(false),
266            notify_mutex: std::sync::Mutex::new(()),
267            notify_cv: std::sync::Condvar::new(),
268            thread_countdown: AtomicI64::new(0),
269            stats_stop_calls: AtomicU64::new(0),
270            stats_last_wait_ns: AtomicU64::new(0),
271            stats_total_wait_ns: AtomicU64::new(0),
272            stats_max_wait_ns: AtomicU64::new(0),
273            stats_poll_loops: AtomicU64::new(0),
274            stats_attached_seen: AtomicU64::new(0),
275            stats_forced_parks: AtomicU64::new(0),
276            stats_suspend_notifications: AtomicU64::new(0),
277            stats_attach_wait_yields: AtomicU64::new(0),
278            stats_suspend_wait_yields: AtomicU64::new(0),
279        }
280    }
281
282    /// Wake the stop-the-world requester (called by each thread that suspends).
283    pub(crate) fn notify_suspended(&self) {
284        self.stats_suspend_notifications
285            .fetch_add(1, Ordering::Relaxed);
286        // Synchronize with requester wait loop to avoid lost wakeups.
287        let _guard = self.notify_mutex.lock().unwrap();
288        self.decrement_thread_countdown(1);
289        self.notify_cv.notify_one();
290    }
291
292    #[inline]
293    fn init_thread_countdown(&self, state: &PyGlobalState) -> i64 {
294        let requester = self.requester.load(Ordering::Relaxed);
295        let registry = state.thread_frames.lock();
296        // Keep requested/count initialization serialized with thread-slot
297        // registration (which also takes this lock), matching the
298        // HEAD_LOCK-guarded stop-the-world bookkeeping.
299        self.requested.store(true, Ordering::Release);
300        let count = registry
301            .iter()
302            .filter(|(thread_id, slot)| {
303                **thread_id != requester
304                    && slot.state.load(Ordering::Relaxed)
305                        != thread::ThreadState::ShuttingDown as i32
306            })
307            .count();
308        let count = (count.min(i64::MAX as usize)) as i64;
309        self.thread_countdown.store(count, Ordering::Release);
310        count
311    }
312
313    #[inline]
314    fn decrement_thread_countdown(&self, n: u64) {
315        if n == 0 {
316            return;
317        }
318        let n = (n.min(i64::MAX as u64)) as i64;
319        let prev = self.thread_countdown.fetch_sub(n, Ordering::AcqRel);
320        if prev <= n {
321            // Clamp at 0 for safety in case of duplicate notifications.
322            self.thread_countdown.store(0, Ordering::Release);
323        }
324    }
325
326    /// Try to CAS detached threads directly to SUSPENDED and check whether
327    /// stop countdown reached zero after parking detached threads.
328    fn park_detached_threads(&self, state: &PyGlobalState) -> bool {
329        use thread::ThreadState;
330        let requester = self.requester.load(Ordering::Relaxed);
331        let registry = state.thread_frames.lock();
332        let mut attached_seen = 0u64;
333        let mut forced_parks = 0u64;
334
335        #[expect(
336            clippy::iter_over_hash_type,
337            reason = "Iteration order doesn't matter here"
338        )]
339        for (&id, slot) in registry.iter() {
340            if id == requester {
341                continue;
342            }
343
344            let state = slot.state.load(Ordering::Relaxed);
345            if state == ThreadState::Detached as i32 {
346                // CAS DETACHED → SUSPENDED (park without thread cooperation)
347                match slot.state.compare_exchange(
348                    ThreadState::Detached as i32,
349                    ThreadState::Suspended as i32,
350                    Ordering::AcqRel,
351                    Ordering::Relaxed,
352                ) {
353                    Ok(_) => {
354                        slot.stop_requested.store(false, Ordering::Release);
355                        forced_parks = forced_parks.saturating_add(1);
356                    }
357                    Err(actual) => match ThreadState::from_i32(actual) {
358                        Some(ThreadState::Attached) => {
359                            // Set per-thread stop bit (_PY_EVAL_PLEASE_STOP_BIT).
360                            slot.stop_requested.store(true, Ordering::Release);
361                            crate::signal::set_stop_bit();
362                            // Raced with a thread re-attaching; it will self-suspend.
363                            attached_seen = attached_seen.saturating_add(1);
364                        }
365                        Some(ThreadState::Detached) => {
366                            // Extremely unlikely race; next poll will handle it.
367                        }
368                        Some(ThreadState::Suspended) => {
369                            slot.stop_requested.store(false, Ordering::Release);
370                            // Another path parked it first.
371                        }
372                        Some(ThreadState::ShuttingDown) => {
373                            slot.stop_requested.store(false, Ordering::Release);
374                        }
375                        None => {
376                            debug_assert!(
377                                false,
378                                "unexpected thread state in park_detached_threads: {actual}"
379                            );
380                        }
381                    },
382                }
383            } else if state == ThreadState::Attached as i32 {
384                // Set per-thread stop bit (_PY_EVAL_PLEASE_STOP_BIT).
385                slot.stop_requested.store(true, Ordering::Release);
386                crate::signal::set_stop_bit();
387                // Thread is in bytecode — it will see `requested` and self-suspend
388                attached_seen = attached_seen.saturating_add(1);
389            }
390            // Suspended / ShuttingDown → already parked
391        }
392        if attached_seen != 0 {
393            self.stats_attached_seen
394                .fetch_add(attached_seen, Ordering::Relaxed);
395        }
396        if forced_parks != 0 {
397            self.decrement_thread_countdown(forced_parks);
398            self.stats_forced_parks
399                .fetch_add(forced_parks, Ordering::Relaxed);
400        }
401        forced_parks != 0 && self.thread_countdown.load(Ordering::Acquire) == 0
402    }
403
404    /// Acquire the single stop-the-world exclusion in a park-friendly way.
405    ///
406    /// Fork and GC both request stop-the-world through the same shared state;
407    /// without this exclusion their `requester`/`requested`/countdown words
408    /// could be clobbered by an interleaving requester, so the completion
409    /// check could never converge and a requester would wait on itself forever.
410    ///
411    /// The acquire must be park-friendly. While another requester's stop is in
412    /// progress it sets this thread's stop bit and waits for it to suspend;
413    /// blocking on a plain lock here would keep this thread from ever reaching
414    /// that safepoint, so the active requester would wait for this thread while
415    /// this thread waits for the lock — a deadlock swap. Instead we poll and
416    /// honor the suspend request between tries. Suspending here is safe as long
417    /// as any lock a spinning requester still holds is never acquired
418    /// attached-blocking by another thread. The fork requester holds IMP_LOCK,
419    /// but its acquisition detaches (`allow_threads`), so no attached thread
420    /// blocks on it; the GC requester holds only the `collecting` mutex, which
421    /// is only ever `try_lock`'d. The active requester therefore force-parks
422    /// this thread, finishes its whole stop→start span, releases the exclusion,
423    /// and only then does this thread resume and acquire it.
424    fn acquire_exclusion(&self, state: &PyGlobalState) {
425        if self
426            .exclusion
427            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
428            .is_ok()
429        {
430            return;
431        }
432        loop {
433            crate::vm::thread::suspend_if_needed(state);
434            std::thread::yield_now();
435            if self
436                .exclusion
437                .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
438                .is_ok()
439            {
440                return;
441            }
442        }
443    }
444
445    /// Release the stop-the-world exclusion taken by `acquire_exclusion`.
446    fn release_exclusion(&self) {
447        self.exclusion.store(false, Ordering::Release);
448    }
449
450    /// Stop all non-requester threads (`stop_the_world`).
451    ///
452    /// 1. Sets `requested`, marking the requester thread.
453    /// 2. CAS detached threads to SUSPENDED.
454    /// 3. Waits (polling with 1 ms condvar timeout) for attached threads
455    ///    to self-suspend in `check_signals`.
456    ///
457    /// Takes the shared exclusion first so at most one requester (fork or GC)
458    /// drives the stop→start span at a time; it is released by
459    /// `start_the_world`/`reset_after_fork`.
460    pub fn stop_the_world(&self, state: &PyGlobalState) {
461        self.acquire_exclusion(state);
462        let start = std::time::Instant::now();
463        let requester_ident = crate::stdlib::_thread::get_ident();
464        self.requester.store(requester_ident, Ordering::Relaxed);
465        self.stats_stop_calls.fetch_add(1, Ordering::Relaxed);
466        let initial_countdown = self.init_thread_countdown(state);
467        stw_trace(format_args!("stop begin requester={requester_ident}"));
468        // Park detached threads and set stop bits, then confirm every other
469        // thread is SUSPENDED. The completion condition is level-triggered
470        // (`all_non_requester_suspended`) so an already-suspended thread that
471        // was counted but will not notify again cannot stall the stop.
472        self.park_detached_threads(state);
473        if initial_countdown == 0 || self.all_non_requester_suspended(state) {
474            self.world_stopped.store(true, Ordering::Release);
475            crate::common::lock::set_world_stopped(true);
476            #[cfg(debug_assertions)]
477            self.debug_assert_all_non_requester_suspended(state);
478            stw_trace(format_args!(
479                "stop end requester={requester_ident} wait_ns=0 polls=0"
480            ));
481            return;
482        }
483
484        let mut polls = 0u64;
485        loop {
486            self.park_detached_threads(state);
487            if self.all_non_requester_suspended(state) {
488                break;
489            }
490            polls = polls.saturating_add(1);
491            // Wait up to 1 ms for a thread to notify us it suspended.
492            // Re-check under the wait mutex first to avoid a lost-wake race:
493            // a thread may have suspended and notified right before we enter wait.
494            let guard = self.notify_mutex.lock().unwrap();
495            if self.all_non_requester_suspended(state) {
496                drop(guard);
497                break;
498            }
499            let _ = self
500                .notify_cv
501                .wait_timeout(guard, core::time::Duration::from_millis(1));
502        }
503        if polls != 0 {
504            self.stats_poll_loops.fetch_add(polls, Ordering::Relaxed);
505        }
506        let wait_ns = start.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64;
507        self.stats_last_wait_ns.store(wait_ns, Ordering::Relaxed);
508        self.stats_total_wait_ns
509            .fetch_add(wait_ns, Ordering::Relaxed);
510        let mut prev_max = self.stats_max_wait_ns.load(Ordering::Relaxed);
511        while wait_ns > prev_max {
512            match self.stats_max_wait_ns.compare_exchange_weak(
513                prev_max,
514                wait_ns,
515                Ordering::Relaxed,
516                Ordering::Relaxed,
517            ) {
518                Ok(_) => break,
519                Err(observed) => prev_max = observed,
520            }
521        }
522        self.world_stopped.store(true, Ordering::Release);
523        crate::common::lock::set_world_stopped(true);
524        #[cfg(debug_assertions)]
525        self.debug_assert_all_non_requester_suspended(state);
526        stw_trace(format_args!(
527            "stop end requester={requester_ident} wait_ns={wait_ns} polls={polls}"
528        ));
529    }
530
531    /// Resume all suspended threads (`start_the_world`).
532    pub fn start_the_world(&self, state: &PyGlobalState) {
533        use thread::ThreadState;
534        let requester = self.requester.load(Ordering::Relaxed);
535        stw_trace(format_args!("start begin requester={requester}"));
536        let registry = state.thread_frames.lock();
537        // Clear the request flag BEFORE waking threads. Otherwise a thread
538        // returning from allow_threads → attach_thread could observe
539        // `requested == true`, re-suspend itself, and stay parked forever.
540        // Keep this write under the registry lock to serialize with new
541        // thread-slot initialization.
542        self.requested.store(false, Ordering::Release);
543        self.world_stopped.store(false, Ordering::Release);
544        crate::common::lock::set_world_stopped(false);
545
546        #[expect(
547            clippy::iter_over_hash_type,
548            reason = "Iteration order doesn't matter here"
549        )]
550        for (&id, slot) in registry.iter() {
551            if id == requester {
552                continue;
553            }
554
555            slot.stop_requested.store(false, Ordering::Release);
556            let state = slot.state.load(Ordering::Relaxed);
557            if state == ThreadState::ShuttingDown as i32 {
558                // `_PyThreadState_RemoveExcept` + SetShuttingDown already
559                // took this thread off the resume path. Leave it hanging.
560                continue;
561            }
562            debug_assert!(
563                state == ThreadState::Suspended as i32,
564                "non-requester thread not suspended at start-the-world: id={id} state={state}"
565            );
566            if state == ThreadState::Suspended as i32 {
567                slot.state
568                    .store(ThreadState::Detached as i32, Ordering::Release);
569                slot.thread.unpark();
570            }
571        }
572
573        drop(registry);
574        self.thread_countdown.store(0, Ordering::Release);
575        self.requester.store(0, Ordering::Relaxed);
576        // Drop the process-wide stop hint. Another interpreter may still
577        // have `stop_requested` threads; those keep parking via the
578        // per-thread check in `eval_breaker_tripped`.
579        crate::signal::clear_stop_bit();
580        #[cfg(debug_assertions)]
581        self.debug_assert_all_non_requester_detached(state);
582        // Release the exclusion last, ending the stop→start span so the next
583        // requester (fork or GC) can proceed.
584        self.release_exclusion();
585        stw_trace(format_args!("start end requester={requester}"));
586    }
587
588    /// Reset after fork in the child (only one thread alive).
589    pub fn reset_after_fork(&self) {
590        self.requested.store(false, Ordering::Relaxed);
591        self.world_stopped.store(false, Ordering::Relaxed);
592        crate::common::lock::set_world_stopped(false);
593        self.requester.store(0, Ordering::Relaxed);
594        self.thread_countdown.store(0, Ordering::Relaxed);
595        // Only one thread survives fork; any stop-the-world bit inherited
596        // from the parent is stale.
597        crate::signal::clear_stop_bit();
598        // The surviving child thread inherited the exclusion taken by the
599        // pre-fork `stop_the_world`; release it (no start_the_world runs here).
600        self.release_exclusion();
601        stw_trace(format_args!("reset-after-fork"));
602    }
603
604    #[inline]
605    pub(crate) fn requester_ident(&self) -> u64 {
606        self.requester.load(Ordering::Relaxed)
607    }
608
609    #[inline]
610    pub(crate) fn notify_thread_gone(&self) {
611        let _guard = self.notify_mutex.lock().unwrap();
612        self.decrement_thread_countdown(1);
613        self.notify_cv.notify_one();
614    }
615
616    pub fn stats_snapshot(&self) -> StopTheWorldStats {
617        StopTheWorldStats {
618            stop_calls: self.stats_stop_calls.load(Ordering::Relaxed),
619            last_wait_ns: self.stats_last_wait_ns.load(Ordering::Relaxed),
620            total_wait_ns: self.stats_total_wait_ns.load(Ordering::Relaxed),
621            max_wait_ns: self.stats_max_wait_ns.load(Ordering::Relaxed),
622            poll_loops: self.stats_poll_loops.load(Ordering::Relaxed),
623            attached_seen: self.stats_attached_seen.load(Ordering::Relaxed),
624            forced_parks: self.stats_forced_parks.load(Ordering::Relaxed),
625            suspend_notifications: self.stats_suspend_notifications.load(Ordering::Relaxed),
626            attach_wait_yields: self.stats_attach_wait_yields.load(Ordering::Relaxed),
627            suspend_wait_yields: self.stats_suspend_wait_yields.load(Ordering::Relaxed),
628            world_stopped: self.world_stopped.load(Ordering::Relaxed),
629        }
630    }
631
632    pub fn reset_stats(&self) {
633        self.stats_stop_calls.store(0, Ordering::Relaxed);
634        self.stats_last_wait_ns.store(0, Ordering::Relaxed);
635        self.stats_total_wait_ns.store(0, Ordering::Relaxed);
636        self.stats_max_wait_ns.store(0, Ordering::Relaxed);
637        self.stats_poll_loops.store(0, Ordering::Relaxed);
638        self.stats_attached_seen.store(0, Ordering::Relaxed);
639        self.stats_forced_parks.store(0, Ordering::Relaxed);
640        self.stats_suspend_notifications.store(0, Ordering::Relaxed);
641        self.stats_attach_wait_yields.store(0, Ordering::Relaxed);
642        self.stats_suspend_wait_yields.store(0, Ordering::Relaxed);
643    }
644
645    #[inline]
646    pub(crate) fn add_attach_wait_yields(&self, n: u64) {
647        if n != 0 {
648            self.stats_attach_wait_yields
649                .fetch_add(n, Ordering::Relaxed);
650        }
651    }
652
653    #[inline]
654    pub(crate) fn add_suspend_wait_yields(&self, n: u64) {
655        if n != 0 {
656            self.stats_suspend_wait_yields
657                .fetch_add(n, Ordering::Relaxed);
658        }
659    }
660
661    /// Whether every non-requester registered thread is currently SUSPENDED.
662    ///
663    /// Level-triggered stop-the-world completion check. Relying on this rather
664    /// than solely on the edge-triggered `thread_countdown` avoids a
665    /// lost-decrement race under rapid back-to-back stops: a thread that is
666    /// already SUSPENDED when a new stop counts it neither notifies nor is
667    /// force-parked again, so an edge-based countdown could never reach zero.
668    fn all_non_requester_suspended(&self, state: &PyGlobalState) -> bool {
669        use thread::ThreadState;
670        let requester = self.requester.load(Ordering::Relaxed);
671        let registry = state.thread_frames.lock();
672
673        #[expect(
674            clippy::iter_over_hash_type,
675            reason = "Iteration order doesn't matter here"
676        )]
677        for (&id, slot) in registry.iter() {
678            if id == requester {
679                continue;
680            }
681            let slot_state = slot.state.load(Ordering::Acquire);
682            if slot_state != ThreadState::Suspended as i32
683                && slot_state != ThreadState::ShuttingDown as i32
684            {
685                return false;
686            }
687        }
688        true
689    }
690
691    #[cfg(debug_assertions)]
692    fn debug_assert_all_non_requester_suspended(&self, state: &PyGlobalState) {
693        use thread::ThreadState;
694        let requester = self.requester.load(Ordering::Relaxed);
695        let registry = state.thread_frames.lock();
696
697        #[expect(
698            clippy::iter_over_hash_type,
699            reason = "Iteration order doesn't matter here"
700        )]
701        for (&id, slot) in registry.iter() {
702            if id == requester {
703                continue;
704            }
705
706            let state = slot.state.load(Ordering::Relaxed);
707            debug_assert!(
708                state == ThreadState::Suspended as i32 || state == ThreadState::ShuttingDown as i32,
709                "non-requester thread not suspended during stop-the-world: id={id} state={state}"
710            );
711        }
712    }
713
714    #[cfg(debug_assertions)]
715    fn debug_assert_all_non_requester_detached(&self, state: &PyGlobalState) {
716        use thread::ThreadState;
717        let requester = self.requester.load(Ordering::Relaxed);
718        let registry = state.thread_frames.lock();
719
720        #[expect(
721            clippy::iter_over_hash_type,
722            reason = "Iteration order doesn't matter here"
723        )]
724        for (&id, slot) in registry.iter() {
725            if id == requester {
726                continue;
727            }
728
729            let state = slot.state.load(Ordering::Relaxed);
730            debug_assert!(
731                state != ThreadState::Suspended as i32,
732                "non-requester thread still suspended after start-the-world: id={id} state={state}"
733            );
734        }
735    }
736}
737
738#[cfg(feature = "threading")]
739pub(super) fn stw_trace_enabled() -> bool {
740    static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
741    *ENABLED.get_or_init(|| crate::host_env::os::var_os("RUSTPYTHON_STW_TRACE").is_some())
742}
743
744#[cfg(feature = "threading")]
745pub(super) fn stw_trace(msg: core::fmt::Arguments<'_>) {
746    if stw_trace_enabled() {
747        use core::fmt::Write as _;
748
749        // Avoid stdio locking here: this path runs around fork where a child
750        // may inherit a borrowed stderr lock and panic on eprintln!/stderr.
751        struct FixedBuf {
752            buf: [u8; 512],
753            len: usize,
754        }
755
756        impl core::fmt::Write for FixedBuf {
757            fn write_str(&mut self, s: &str) -> core::fmt::Result {
758                if self.len >= self.buf.len() {
759                    return Ok(());
760                }
761                let remain = self.buf.len() - self.len;
762                let src = s.as_bytes();
763                let n = src.len().min(remain);
764                self.buf[self.len..self.len + n].copy_from_slice(&src[..n]);
765                self.len += n;
766                Ok(())
767            }
768        }
769
770        let mut out = FixedBuf {
771            buf: [0u8; 512],
772            len: 0,
773        };
774        let _ = writeln!(
775            &mut out,
776            "[rp-stw tid={}] {}",
777            crate::stdlib::_thread::get_ident(),
778            msg
779        );
780        #[cfg(unix)]
781        crate::host_env::io::write_stderr_raw(&out.buf[..out.len]);
782        #[cfg(not(unix))]
783        {
784            use std::io::Write as _;
785            let _ = std::io::stderr().write_all(&out.buf[..out.len]);
786        }
787    }
788}
789
790#[derive(Clone, Debug, Default)]
791pub(crate) struct CallableCache {
792    pub len: Option<PyObjectRef>,
793    pub isinstance: Option<PyObjectRef>,
794    pub list_append: Option<PyObjectRef>,
795    pub builtin_all: Option<PyObjectRef>,
796    pub builtin_any: Option<PyObjectRef>,
797}
798
799/// Per-interpreter shared state (≈ CPython `PyInterpreterState`).
800///
801/// Not process-global: each [`Interpreter`] (main or subinterpreter) owns its own
802/// `PyGlobalState`. Process-wide pieces live elsewhere (`Context::genesis`,
803/// GC, the interpreter registry in [`runtime`]).
804pub struct PyGlobalState {
805    /// Unique process-global interpreter id (main is [`MAIN_INTERPRETER_ID`]).
806    pub interpreter_id: i64,
807    /// Top-level interpreter whose runtime owns this interpreter.
808    pub runtime_root_id: i64,
809    /// How this interpreter was created.
810    pub whence: runtime::InterpreterWhence,
811    /// True for every top-level (non-sub) interpreter, each of which keeps its
812    /// own signal and main-thread bookkeeping. Only the first one registered
813    /// becomes *the* process main — see [`runtime::main_interpreter_id`].
814    pub is_main: bool,
815    pub config: PyConfig,
816    pub module_defs: BTreeMap<&'static str, &'static builtins::PyModuleDef>,
817    pub frozen: HashMap<&'static str, FrozenModule, rapidhash::quality::RandomState>,
818    pub stacksize: AtomicCell<usize>,
819    pub thread_count: AtomicCell<usize>,
820    /// Registered `atexit` callbacks, newest first. Shared ownership so
821    /// `atexit.unregister` can keep the entry it is comparing alive while the
822    /// list is unlocked, and still recognize it afterwards by identity.
823    pub atexit_funcs: PyMutex<Vec<PyRc<(PyObjectRef, FuncArgs)>>>,
824    /// `sys.addaudithook` hooks, shared by all threads of this interpreter.
825    pub(crate) audit_hooks: PyMutex<Vec<PyObjectRef>>,
826    pub codec_registry: CodecsRegistry,
827    pub struct_format_cache: crate::buffer::FormatSpecCache,
828    pub finalizing: AtomicBool,
829    /// The thread performing finalization, which need not be the process main thread.
830    #[cfg(feature = "threading")]
831    pub(crate) finalizing_thread_ident: AtomicCell<u64>,
832    pub warnings: WarningsState,
833    pub override_frozen_modules: AtomicCell<isize>,
834    pub before_forkers: PyMutex<Vec<PyObjectRef>>,
835    pub after_forkers_child: PyMutex<Vec<PyObjectRef>>,
836    pub after_forkers_parent: PyMutex<Vec<PyObjectRef>>,
837    pub int_max_str_digits: AtomicCell<usize>,
838    pub switch_interval: AtomicCell<f64>,
839    /// Global trace function for all threads (set by sys._settraceallthreads)
840    pub global_trace_func: PyMutex<Option<PyObjectRef>>,
841    /// Global profile function for all threads (set by sys._setprofileallthreads)
842    pub global_profile_func: PyMutex<Option<PyObjectRef>>,
843    /// Global type mutation/versioning mutex for CPython-style FT type operations.
844    pub type_mutex: PyMutex<()>,
845    /// Main thread identifier (pthread_self on Unix)
846    #[cfg(feature = "threading")]
847    pub main_thread_ident: AtomicCell<u64>,
848    /// Registry of all threads' slots for sys._current_frames() and sys._current_exceptions()
849    #[cfg(feature = "threading")]
850    pub thread_frames: parking_lot::Mutex<HashMap<u64, stdlib::_thread::CurrentFrameSlot>>,
851    /// Registry of all ThreadHandles for fork cleanup
852    #[cfg(feature = "threading")]
853    pub thread_handles: parking_lot::Mutex<Vec<stdlib::_thread::HandleEntry>>,
854    /// Registry for non-daemon threads that need to be joined at shutdown
855    #[cfg(feature = "threading")]
856    pub shutdown_handles: parking_lot::Mutex<Vec<stdlib::_thread::ShutdownEntry>>,
857    /// sys.monitoring state (tool names, events, callbacks)
858    pub monitoring: PyMutex<stdlib::sys::monitoring::MonitoringState>,
859    /// Fast-path mask: OR of all tools' events. 0 means no monitoring overhead.
860    pub monitoring_events: stdlib::sys::monitoring::MonitoringEventsMask,
861    /// Incremented on every monitoring state change. Code objects compare their
862    /// local version against this to decide whether re-instrumentation is needed.
863    pub instrumentation_version: AtomicU64,
864    /// Stop-the-world state for pre-fork thread suspension
865    #[cfg(feature = "threading")]
866    pub stop_the_world: StopTheWorldState,
867    /// This interpreter's garbage collector policy and results.
868    pub gc: crate::gc_state::GcInterpreterState,
869    /// Isolated-interpreter feature flags (PEP 684 / PEP 734 config).
870    pub feature_flags: runtime::InterpFeatureFlags,
871    /// Whether the interpreter was configured with `gil="own"`.
872    pub own_gil: bool,
873    /// Whether `__main__` is currently executing via `_interpreters.exec` / `run_*`.
874    pub running_main: AtomicBool,
875    /// True after `initialize()` has finished (CPython "ready").
876    pub ready: AtomicBool,
877    /// Optional ID refcount used by `_interpreters.create(reqrefs=True)`.
878    pub id_refcount: AtomicI64,
879    /// When true, dropping the last ID ref destroys the interpreter.
880    pub require_idref: AtomicBool,
881}
882
883impl PyGlobalState {
884    #[inline]
885    #[must_use]
886    pub fn is_main_interpreter(&self) -> bool {
887        self.is_main
888    }
889
890    #[inline]
891    #[must_use]
892    pub fn allow_fork(&self) -> bool {
893        self.feature_flags.allow_fork
894    }
895
896    #[inline]
897    #[must_use]
898    pub fn allow_exec(&self) -> bool {
899        self.feature_flags.allow_exec
900    }
901
902    #[inline]
903    #[must_use]
904    pub fn allow_threads(&self) -> bool {
905        self.feature_flags.allow_threads
906    }
907
908    #[inline]
909    #[must_use]
910    pub fn allow_daemon_threads(&self) -> bool {
911        self.feature_flags.allow_daemon_threads
912    }
913
914    /// The config this interpreter was created with, rebuilt from the flags it
915    /// kept (`_PyInterpreterConfig_InitFromState`).
916    #[must_use]
917    pub fn config(&self) -> runtime::InterpreterConfig {
918        runtime::InterpreterConfig::from_state(self.feature_flags, self.own_gil)
919    }
920}
921
922/// Process-wide `_Py_HashSecret`. The first top-level interpreter sets it;
923/// later calls keep that value.
924static HASH_SECRET: std::sync::OnceLock<HashSecret> = std::sync::OnceLock::new();
925
926/// Set the process-wide hash secret from the first top-level interpreter.
927///
928/// `hash_seed` is used only when the secret is not set yet. `None` draws a
929/// random seed. A later call keeps the existing secret and ignores `hash_seed`.
930pub(crate) fn init_hash_secret(hash_seed: Option<u32>) {
931    let _ = HASH_SECRET.get_or_init(|| {
932        let seed = hash_seed.unwrap_or_else(|| {
933            // os_random is expensive, but this runs only once per process.
934            u32::from_ne_bytes(rustpython_common::rand::os_random())
935        });
936        HashSecret::new(seed)
937    });
938}
939
940/// Process-wide `_Py_HashSecret` used for str/bytes hashing.
941#[inline]
942#[must_use]
943pub(crate) fn hash_secret() -> &'static HashSecret {
944    HASH_SECRET
945        .get()
946        .expect("hash secret is set by the first top-level interpreter")
947}
948
949/// A `NonNull<T>` wrapper that implements `Send + Sync`.
950///
951/// # Safety contract
952///
953/// This type bypasses Rust's `Send`/`Sync` bounds on `NonNull`. It is
954/// sound **only** when the pointer is exclusively accessed by one thread
955/// at a time. In this codebase, that invariant is upheld because
956/// `VirtualMachine` is per-thread.
957///
958/// **Do not use this type outside `pending_tailcall_frame`.** It exists
959/// solely to let a `Cell<Option<PendingFrame>>` field on the per-thread
960/// VM satisfy `Send + Sync`. If you need a `Send`-able pointer
961/// elsewhere, justify and document the safety invariant at that site.
962#[repr(transparent)]
963struct PendingFrame(core::ptr::NonNull<crate::frame::InterpreterFrame>);
964
965impl Copy for PendingFrame {}
966impl Clone for PendingFrame {
967    fn clone(&self) -> Self {
968        *self
969    }
970}
971
972// SAFETY: VirtualMachine is per-thread; the pointer is only ever
973// accessed on the thread that wrote it. The pointed-to InterpreterFrame
974// lives on that thread's datastack and is valid from set to take.
975unsafe impl Send for PendingFrame {}
976unsafe impl Sync for PendingFrame {}
977
978/// Saved state from `gen_frame_link`, needed by `gen_frame_unlink` to
979/// restore the previous frame chain and the frame's owner.
980pub(crate) struct GenFrameLink {
981    old_chain: *const crate::frame::InterpreterFrame,
982    old_owner: i8,
983}
984
985/// Saved state from `enter_iframe`, needed by `exit_iframe` to restore
986/// the previous frame chain and exception state.
987pub(crate) struct IframeEntryState {
988    pub(crate) iframe_ptr: *const crate::frame::InterpreterFrame,
989    pub(crate) old_chain: *const crate::frame::InterpreterFrame,
990    pub(crate) saved_exc: Option<PyBaseExceptionRef>,
991    pub(crate) save_exc: bool,
992}
993
994/// A generator or coroutine the bytecode loop asked the trampoline to resume.
995struct PendingGenResume {
996    /// The generator or coroutine object; an exact builtin one, neither
997    /// running nor closed when it was parked.
998    jen: PyObjectRef,
999    /// The value its `yield` produces.
1000    value: PyObjectRef,
1001    /// What the parking frame does with the outcome.
1002    cont: crate::frame::GenCont,
1003}
1004
1005/// Where a frame running under the trampoline came from, and therefore what
1006/// the trampoline owes it when it finishes.
1007///
1008/// The trampoline is entered with one frame already running — `Entry` or
1009/// `GenEntry` — and pushes one record per frame it enters itself.
1010enum FrameKind {
1011    /// The frame `run_frame_fast` was called with. Its caller allocated the
1012    /// data stack storage and releases it, but the `enter_iframe`
1013    /// bookkeeping is the trampoline's to undo.
1014    Entry(IframeEntryState),
1015    /// The body of a generator or coroutine, entered from `run_gen_frame`.
1016    /// `resume_gen_frame` already linked it into the frame chain and will
1017    /// unlink it, so the trampoline touches neither the bookkeeping nor the
1018    /// storage; a `Yield` out of it is the trampoline's own result.
1019    GenEntry,
1020    /// A data stack frame the trampoline itself entered for a `TailCall`.
1021    Callee(IframeEntryState),
1022    /// A generator or coroutine frame the trampoline itself resumed for a
1023    /// `GenResume`.
1024    Gen(crate::coroutine::FlatResume),
1025}
1026
1027impl FrameKind {
1028    /// What a frame of this kind may hand back to the trampoline.
1029    #[inline]
1030    const fn flatten(&self) -> crate::frame::Flatten {
1031        match self {
1032            Self::Entry(_) | Self::Callee(_) => crate::frame::Flatten::CallAndGenResume,
1033            Self::GenEntry | Self::Gen(_) => crate::frame::Flatten::GenResume,
1034        }
1035    }
1036}
1037
1038/// Unique right to mutably run an interpreter frame in the trampoline.
1039///
1040/// Not `Copy`: two handles to the same allocation would let two
1041/// `&mut InterpreterFrame` exist at once. `from_mut` consumes an exclusive
1042/// borrow; `from_ptr` is `unsafe` and must not alias another live handle.
1043/// Pointer validity (datastack LIFO, generator `PyRef` + running claim) is
1044/// still a construction contract, not something this type can prove.
1045struct TrampolineIFrame {
1046    ptr: *mut crate::frame::InterpreterFrame,
1047}
1048
1049impl TrampolineIFrame {
1050    fn from_mut(iframe: &mut crate::frame::InterpreterFrame) -> Self {
1051        Self { ptr: iframe }
1052    }
1053
1054    /// # Safety
1055    /// `ptr` must point to a live frame, and no other `TrampolineIFrame`
1056    /// may alias it until this handle is dropped.
1057    unsafe fn from_ptr(ptr: *mut crate::frame::InterpreterFrame) -> Self {
1058        Self { ptr }
1059    }
1060
1061    fn as_mut(&mut self) -> &mut crate::frame::InterpreterFrame {
1062        // SAFETY: unique handle; construction established the pointer.
1063        unsafe { &mut *self.ptr }
1064    }
1065}
1066
1067/// Caller frame suspended by a TailCall in the trampoline.
1068struct SuspendedFrame {
1069    iframe: TrampolineIFrame,
1070    kind: FrameKind,
1071    /// Function that owns the callee's raw pointers (code, globals, builtins,
1072    /// closure, and func_obj). Moved from `vm.pending_tailcall_owner` when the
1073    /// callee's TailCall is consumed.
1074    /// Dropped as soon as this SuspendedFrame is popped — the callee has
1075    /// returned or raised and its frame is already released by then.
1076    callee_owner: Option<PyObjectRef>,
1077    /// What this frame does with the outcome of the frame it entered.
1078    /// `GenCont::NONE` for an ordinary call, whose return value is simply
1079    /// pushed.
1080    cont: crate::frame::GenCont,
1081}
1082
1083// SAFETY: the VM is per-thread, and a suspended-frame record is pushed, read
1084// and popped only on the thread that created it. The pointers it holds address
1085// that thread's data stack or objects it keeps alive, and the shared stack is
1086// empty whenever no trampoline is running on the thread — so a VM handed to
1087// another thread carries no frame pointers with it.
1088unsafe impl Send for SuspendedFrame {}
1089// SAFETY: as above; no two threads ever reach the same record.
1090unsafe impl Sync for SuspendedFrame {}
1091
1092/// What a finished frame hands back to the frame that entered it.
1093enum Outcome {
1094    /// A returned value, to push onto the caller's stack.
1095    Value(PyObjectRef),
1096    /// A resumed generator came to an end, with the `StopIteration` value it
1097    /// ended on.
1098    GenStop(Option<PyObjectRef>),
1099    /// An exception, to feed into the caller's exception table.
1100    Raise(PyBaseExceptionRef),
1101}
1102
1103/// How a trampoline invocation begins.
1104enum TrampolineStart {
1105    /// The entry frame ran and handed this back.
1106    Ran(PyResult<crate::frame::ExecutionResult>),
1107    /// The entry frame is parked at a `yield from`; its delegate runs in its
1108    /// place, and `cont` says what to do with what the delegate produces.
1109    Delegating {
1110        delegate: PyObjectRef,
1111        value: PyObjectRef,
1112        cont: crate::frame::GenCont,
1113    },
1114}
1115
1116/// What `trampoline_resume_gen` ended up with.
1117enum GenEntered {
1118    /// The generator at the bottom of the chain ran and produced `result`;
1119    /// `state` and `iframe` are its own.
1120    Ran {
1121        iframe: TrampolineIFrame,
1122        state: crate::coroutine::FlatResume,
1123        result: PyResult<crate::frame::ExecutionResult>,
1124    },
1125    /// Nothing was entered: the generator was exhausted, or the resume itself
1126    /// failed. The outcome belongs to the frame that asked for the resume.
1127    Failed(Outcome),
1128}
1129
1130/// Whether a sequence being built asks the iterable it was handed how much room
1131/// to take. `list_extend()` asks and reserves; `PySequence_Tuple()` and the
1132/// rest ask nothing at all.
1133#[derive(Clone, Copy)]
1134enum LengthHint<'a> {
1135    /// Grows as the loop goes, the way `tuple()`, `set()`, `min()` and
1136    /// `deque()` do, so an object slow to answer is never asked.
1137    Unasked,
1138    /// Reserves what the iterable answers, unless it leaves no room for the
1139    /// count this returns.
1140    Iterable(&'a dyn Fn() -> usize),
1141}
1142
1143impl VirtualMachine {
1144    fn init_callable_cache(&mut self) -> PyResult<()> {
1145        self.callable_cache.len = Some(self.builtins.get_attr("len", self)?);
1146        self.callable_cache.isinstance = Some(self.builtins.get_attr("isinstance", self)?);
1147        let list_append = self
1148            .ctx
1149            .types
1150            .list_type
1151            .get_attr(self.ctx.intern_str("append"))
1152            .ok_or_else(|| self.new_runtime_error("failed to cache list.append"))?;
1153        self.callable_cache.list_append = Some(list_append);
1154        self.callable_cache.builtin_all = Some(self.builtins.get_attr("all", self)?);
1155        self.callable_cache.builtin_any = Some(self.builtins.get_attr("any", self)?);
1156        Ok(())
1157    }
1158
1159    /// Bump-allocate `size` bytes from the thread data stack.
1160    ///
1161    /// # Safety
1162    /// The returned pointer must be freed by calling `datastack_pop` in LIFO order.
1163    #[inline(always)]
1164    pub(crate) fn datastack_push(&self, size: usize) -> *mut u8 {
1165        unsafe { (*self.datastack.get()).push(size) }
1166    }
1167
1168    /// Bump-allocate a full frame, returning whether the same cleared LIFO
1169    /// block and size were reused.
1170    #[inline(always)]
1171    pub(crate) fn datastack_push_frame(&self, size: usize) -> (*mut u8, bool) {
1172        unsafe { (*self.datastack.get()).push_frame(size) }
1173    }
1174
1175    /// Check whether the thread data stack currently has room for `size` bytes.
1176    #[inline(always)]
1177    pub(crate) fn datastack_has_space(&self, size: usize) -> bool {
1178        unsafe { (*self.datastack.get()).has_space(size) }
1179    }
1180
1181    /// Pop a previous data stack allocation.
1182    ///
1183    /// # Safety
1184    /// `base` must be a pointer returned by `datastack_push` on this VM,
1185    /// and all allocations made after it must already have been popped.
1186    #[inline(always)]
1187    pub(crate) unsafe fn datastack_pop(&self, base: *mut u8) {
1188        unsafe { (*self.datastack.get()).pop(base) }
1189    }
1190
1191    /// Pop a full frame after its localsplus slots have been cleared.
1192    #[inline(always)]
1193    pub(crate) unsafe fn datastack_pop_frame(&self, base: *mut u8, size: usize) {
1194        unsafe { (*self.datastack.get()).pop_frame(base, size) }
1195    }
1196
1197    /// Temporarily detach the current thread (ATTACHED → DETACHED) while
1198    /// running `f`, then re-attach afterwards.  Allows `stop_the_world` to
1199    /// park this thread during blocking syscalls.
1200    ///
1201    /// Equivalent to CPython's `Py_BEGIN_ALLOW_THREADS` / `Py_END_ALLOW_THREADS`.
1202    #[inline]
1203    pub fn allow_threads<R>(&self, f: impl FnOnce() -> R) -> R {
1204        thread::allow_threads(self, f)
1205    }
1206
1207    /// Re-attach the current thread for the duration of `f`, then return it to
1208    /// where it was. The inverse of [`allow_threads`](Self::allow_threads), for
1209    /// a callback that runs Python from inside a call this thread detached for.
1210    ///
1211    /// Equivalent to `PyGILState_Ensure` / `PyGILState_Release` around such a
1212    /// callback.
1213    #[inline]
1214    pub fn attach_for_callback<R>(&self, f: impl FnOnce() -> R) -> R {
1215        thread::attach_for_callback(self, f)
1216    }
1217
1218    /// Check whether the current thread is the main thread.
1219    /// Mirrors `_Py_ThreadCanHandleSignals`.
1220    #[allow(dead_code)]
1221    pub(crate) fn is_main_thread(&self) -> bool {
1222        cfg_select! {
1223            feature = "threading" => {
1224                crate::stdlib::_thread::get_ident() == self.state.main_thread_ident.load()
1225            }
1226            _ => true,
1227        }
1228    }
1229
1230    /// Create a new `VirtualMachine` structure.
1231    pub(crate) fn new(ctx: PyRc<Context>, state: PyRc<PyGlobalState>) -> Self {
1232        flame_guard!("new VirtualMachine");
1233
1234        // make a new module without access to the vm; doesn't
1235        // set __spec__, __loader__, etc. attributes
1236        let new_module = |def| {
1237            PyRef::new_ref(
1238                PyModule::from_def(def),
1239                ctx.types.module_type.to_owned(),
1240                Some(ctx.new_dict()),
1241            )
1242        };
1243
1244        // Hard-core modules:
1245        let builtins = new_module(stdlib::builtins::module_def(&ctx));
1246        let sys_module = new_module(stdlib::sys::module_def(&ctx));
1247
1248        let import_func = ctx.none();
1249        let importlib = ctx.none();
1250        let profile_func = RefCell::new(ctx.none());
1251        let trace_func = RefCell::new(ctx.none());
1252        let signal_handlers = OnceCell::from(SignalHandlers::default());
1253
1254        let vm = Self {
1255            builtins,
1256            sys_module,
1257            ctx,
1258            datastack: core::cell::UnsafeCell::new(crate::datastack::DataStack::new()),
1259            wasm_id: None,
1260            exceptions: RefCell::default(),
1261            import_func,
1262            importlib,
1263            profile_func,
1264            trace_func,
1265            use_tracing: Cell::new(false),
1266            what_event: Cell::new(None),
1267            tracing_depth: Cell::new(0),
1268            recursion_limit: Cell::new(if cfg!(debug_assertions) { 256 } else { 1000 }),
1269            signal_handlers,
1270            signal_rx: None,
1271            repr_guards: RefCell::default(),
1272            state,
1273            initialized: false,
1274            recursion_depth: Cell::new(0),
1275            #[cfg(any(miri, target_env = "musl"))]
1276            native_recursion_depth: Cell::new(0),
1277            c_stack_soft_limit: Cell::new(Self::calculate_c_stack_soft_limit()),
1278            async_gen_firstiter: RefCell::new(None),
1279            async_gen_finalizer: RefCell::new(None),
1280            asyncio_running_loop: RefCell::new(None),
1281            asyncio_running_task: RefCell::new(None),
1282            context_stack: RefCell::default(),
1283            callable_cache: CallableCache::default(),
1284            pending_tailcall_frame: Cell::new(None),
1285            pending_tailcall_owner: core::cell::UnsafeCell::new(None),
1286            pending_gen_resume: core::cell::UnsafeCell::new(None),
1287            trampoline_stack: core::cell::UnsafeCell::new(Vec::new()),
1288        };
1289
1290        vm.builtins.init_dict(
1291            vm.ctx.intern_str("builtins"),
1292            crate::function::plain_doc(stdlib::builtins::DOC)
1293                .map(|doc| vm.ctx.intern_str(doc).to_owned()),
1294            &vm,
1295        );
1296        vm.sys_module.init_dict(
1297            vm.ctx.intern_str("sys"),
1298            crate::function::plain_doc(stdlib::sys::DOC)
1299                .map(|doc| vm.ctx.intern_str(doc).to_owned()),
1300            &vm,
1301        );
1302        // let name = vm.sys_module.get_attr("__name__", &vm).unwrap();
1303        vm
1304    }
1305
1306    /// set up the encodings search function
1307    /// init_importlib must be called before this call
1308    #[cfg(feature = "encodings")]
1309    fn import_encodings(&mut self) -> PyResult<()> {
1310        self.import("encodings", 0).map_err(|import_err| {
1311            let rustpythonpath_env = crate::host_env::os::var("RUSTPYTHONPATH").ok();
1312            let pythonpath_env = crate::host_env::os::var("PYTHONPATH").ok();
1313            let env_set = rustpythonpath_env.as_ref().is_some() || pythonpath_env.as_ref().is_some();
1314            let path_contains_env = self.state.config.paths.module_search_paths.iter().any(|s| {
1315                Some(s.as_str()) == rustpythonpath_env.as_deref() || Some(s.as_str()) == pythonpath_env.as_deref()
1316            });
1317
1318            let guide_message = if cfg!(feature = "freeze-stdlib") {
1319                "`rustpython_pylib` may not be set while using `freeze-stdlib` feature. Try using `rustpython::InterpreterBuilder::init_stdlib` or manually call `builder.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)` in `rustpython_vm::Interpreter::builder()`."
1320            } else if !env_set {
1321                "Neither RUSTPYTHONPATH nor PYTHONPATH is set. Try setting one of them to the stdlib directory."
1322            } else if path_contains_env {
1323                "RUSTPYTHONPATH or PYTHONPATH is set, but it doesn't contain the encodings library. If you are customizing the RustPython vm/interpreter, try adding the stdlib directory to the path. If you are developing the RustPython interpreter, it might be a bug during development."
1324            } else {
1325                "RUSTPYTHONPATH or PYTHONPATH is set, but it wasn't loaded to `PyConfig::paths::module_search_paths`. If you are going to customize the RustPython vm/interpreter, those environment variables are not loaded in the Settings struct by default. Please try creating a customized instance of the Settings struct. If you are developing the RustPython interpreter, it might be a bug during development."
1326            };
1327
1328            let mut msg = format!(
1329                "RustPython could not import the encodings module. It usually means something went wrong. Please carefully read the following messages and follow the steps.\n\
1330                \n\
1331                {guide_message}");
1332            if !cfg!(feature = "freeze-stdlib") {
1333                msg += "\n\
1334                If you don't have access to a consistent external environment (e.g. targeting wasm, embedding \
1335                    rustpython in another application), try enabling the `freeze-stdlib` feature.\n\
1336                If this is intended and you want to exclude the encodings module from your interpreter, please remove the `encodings` feature from `rustpython-vm` crate.";
1337            }
1338
1339            let err = self.new_runtime_error(msg);
1340            err.set_cause(Some(import_err));
1341            err
1342        })?;
1343        Ok(())
1344    }
1345
1346    fn import_ascii_utf8_encodings(&mut self) -> PyResult<()> {
1347        // Use the Python import machinery (FrozenImporter) so modules get
1348        // proper __spec__ and __loader__ attributes.
1349        self.import("codecs", 0)?;
1350
1351        // Use dotted names when freeze-stdlib is enabled (modules come from Lib/encodings/),
1352        // otherwise use underscored names (modules come from core_modules/).
1353        let (ascii_module_name, utf8_module_name, latin1_module_name) =
1354            if cfg!(feature = "freeze-stdlib") {
1355                ("encodings.ascii", "encodings.utf_8", "encodings.latin_1")
1356            } else {
1357                ("encodings_ascii", "encodings_utf_8", "encodings_latin_1")
1358            };
1359
1360        // __import__("encodings.ascii") returns top-level "encodings", so
1361        // look up the actual submodule in sys.modules.
1362        self.import(ascii_module_name, 0)?;
1363        let sys_modules = self.sys_module.get_attr(identifier!(self, modules), self)?;
1364        let ascii_module = sys_modules.get_item(ascii_module_name, self)?;
1365        let getregentry = ascii_module.get_attr("getregentry", self)?;
1366        let codec_info = getregentry.call((), self)?;
1367        self.state
1368            .codec_registry
1369            .register_manual("ascii", codec_info.try_into_value(self)?);
1370
1371        // Register utf-8 encoding (also as "utf8" alias since normalize_encoding_name
1372        // maps "utf-8" → "utf_8" but leaves "utf8" as-is)
1373        self.import(utf8_module_name, 0)?;
1374        let utf8_module = sys_modules.get_item(utf8_module_name, self)?;
1375        let getregentry = utf8_module.get_attr("getregentry", self)?;
1376        let codec_info = getregentry.call((), self)?;
1377        let utf8_codec: crate::codecs::PyCodec = codec_info.try_into_value(self)?;
1378        self.state
1379            .codec_registry
1380            .register_manual("utf-8", utf8_codec.clone());
1381        self.state
1382            .codec_registry
1383            .register_manual("utf8", utf8_codec);
1384
1385        // latin-1 is a built-in codec (needed very early for stdio bootstrap,
1386        // e.g. PYTHONIOENCODING=latin-1).
1387        self.import(latin1_module_name, 0)?;
1388        let latin1_module = sys_modules.get_item(latin1_module_name, self)?;
1389        let getregentry = latin1_module.get_attr("getregentry", self)?;
1390        let codec_info = getregentry.call((), self)?;
1391        let latin1_codec: crate::codecs::PyCodec = codec_info.try_into_value(self)?;
1392        for name in ["latin-1", "latin_1", "latin1", "iso8859-1", "iso8859_1"] {
1393            self.state
1394                .codec_registry
1395                .register_manual(name, latin1_codec.clone());
1396        }
1397        Ok(())
1398    }
1399
1400    fn initialize(&mut self) {
1401        flame_guard!("init VirtualMachine");
1402
1403        assert!(!self.initialized, "Double Initialize Error");
1404
1405        // Process main-thread identity is owned by the main interpreter only
1406        // (used for signal handling / `_thread._is_main_interpreter` helpers).
1407        #[cfg(feature = "threading")]
1408        if self.state.is_main_interpreter() {
1409            stdlib::_thread::init_main_thread_ident(self);
1410        }
1411
1412        let prewarmed_memory_errors: Vec<_> = (0..PyMemoryError::MAX_FREELIST)
1413            .map(|_| self.no_memory_error())
1414            .collect();
1415        drop(prewarmed_memory_errors);
1416
1417        stdlib::builtins::init_module(self, &self.builtins);
1418        let callable_cache_init = self.init_callable_cache();
1419        self.expect_pyresult(callable_cache_init, "failed to initialize callable cache");
1420        stdlib::sys::init_module(self, &self.sys_module, &self.builtins);
1421        self.expect_pyresult(
1422            stdlib::sys::set_bootstrap_stderr(self),
1423            "failed to initialize bootstrap stderr",
1424        );
1425
1426        let mut essential_init = || -> PyResult {
1427            import::import_builtin(self, "_typing")?;
1428            #[cfg(all(not(target_arch = "wasm32"), feature = "host_env"))]
1429            import::import_builtin(self, "_signal")?;
1430            #[cfg(any(feature = "parser", feature = "compiler"))]
1431            import::import_builtin(self, "_ast")?;
1432            #[cfg(not(feature = "threading"))]
1433            import::import_frozen(self, "_thread")?;
1434            let importlib = import::init_importlib_base(self)?;
1435            self.import_ascii_utf8_encodings()?;
1436
1437            {
1438                let io = import::import_builtin(self, "_io")?;
1439
1440                // Full stdio: FileIO → BufferedWriter → TextIOWrapper
1441                #[cfg(all(feature = "host_env", feature = "stdio"))]
1442                let make_stdio = |name: &str, fd: i32, write: bool| -> PyResult<PyObjectRef> {
1443                    let buffered_stdio = self.state.config.settings.buffered_stdio;
1444                    let unbuffered = write && !buffered_stdio;
1445                    let buf = crate::stdlib::_io::open(
1446                        self.ctx.new_int(fd).into(),
1447                        Some(if write { "wb" } else { "rb" }),
1448                        crate::stdlib::_io::OpenArgs {
1449                            buffering: if unbuffered { 0 } else { -1 },
1450                            closefd: false,
1451                            ..Default::default()
1452                        },
1453                        self,
1454                    )?;
1455                    let raw = if unbuffered {
1456                        buf.clone()
1457                    } else {
1458                        buf.get_attr("raw", self)?
1459                    };
1460                    raw.set_attr("name", self.ctx.new_str(format!("<{name}>")), self)?;
1461                    let isatty = self.call_method(&raw, "isatty", ())?.is_true(self)?;
1462                    let write_through = !buffered_stdio;
1463                    let line_buffering = buffered_stdio && (isatty || fd == 2);
1464
1465                    let newline = if cfg!(windows) { None } else { Some("\n") };
1466                    let encoding = self.state.config.settings.stdio_encoding.as_deref();
1467                    // stderr always uses backslashreplace (ignores stdio_errors)
1468                    let errors = if fd == 2 {
1469                        Some("backslashreplace")
1470                    } else {
1471                        self.state
1472                            .config
1473                            .settings
1474                            .stdio_errors
1475                            .as_deref()
1476                            .or_else(|| {
1477                                Some(if self.state.config.settings.stdio_encoding.is_some() {
1478                                    "strict"
1479                                } else {
1480                                    "surrogateescape"
1481                                })
1482                            })
1483                    };
1484
1485                    let stdio = self.call_method(
1486                        &io,
1487                        "TextIOWrapper",
1488                        (
1489                            buf,
1490                            encoding,
1491                            errors,
1492                            newline,
1493                            line_buffering,
1494                            write_through,
1495                        ),
1496                    )?;
1497                    let mode = if write { "w" } else { "r" };
1498                    stdio.set_attr("mode", self.ctx.new_str(mode), self)?;
1499                    Ok::<_, self::PyBaseExceptionRef>(stdio)
1500                };
1501
1502                // Sandbox stdio: lightweight wrapper using Rust's std::io directly
1503                #[cfg(all(not(feature = "host_env"), feature = "stdio"))]
1504                let make_stdio = |name: &str, fd: i32, write: bool| {
1505                    let mode = if write { "w" } else { "r" };
1506                    let stdio = stdlib::sys::SandboxStdio {
1507                        fd,
1508                        name: format!("<{name}>"),
1509                        mode: mode.to_owned(),
1510                    }
1511                    .into_ref(&self.ctx);
1512                    Ok(stdio.into())
1513                };
1514
1515                // No stdio: set to None (embedding use case)
1516                #[cfg(not(feature = "stdio"))]
1517                let make_stdio = |_name: &str, _fd: i32, _write: bool| {
1518                    Ok(crate::builtins::PyNone.into_pyobject(self))
1519                };
1520
1521                let set_stdio = |name, fd, write| {
1522                    let stdio: PyObjectRef = make_stdio(name, fd, write)?;
1523                    let dunder_name = self.ctx.intern_str(format!("__{name}__"));
1524                    self.sys_module.set_attr(
1525                        dunder_name, // e.g. __stdin__
1526                        stdio.clone(),
1527                        self,
1528                    )?;
1529                    self.sys_module.set_attr(name, stdio, self)?;
1530                    Ok(())
1531                };
1532                set_stdio("stdin", 0, false)?;
1533                set_stdio("stdout", 1, true)?;
1534                set_stdio("stderr", 2, true)?;
1535
1536                let io_open = io.get_attr("open", self)?;
1537                self.builtins.set_attr("open", io_open, self)?;
1538            }
1539
1540            Ok(importlib)
1541        };
1542
1543        let res = essential_init();
1544        let importlib = self.expect_pyresult(res, "essential initialization failed");
1545
1546        #[cfg(feature = "host_env")]
1547        if self.state.config.settings.allow_external_library
1548            && cfg!(feature = "rustpython-compiler")
1549            && let Err(e) = import::init_importlib_package(self, &importlib)
1550        {
1551            eprintln!(
1552                "importlib initialization failed. This is critical for many complicated packages."
1553            );
1554            self.print_exception(&e);
1555        }
1556
1557        #[cfg(not(feature = "host_env"))]
1558        let _ = importlib;
1559
1560        let _expect_stdlib = cfg!(feature = "freeze-stdlib")
1561            || !self.state.config.paths.module_search_paths.is_empty();
1562
1563        #[cfg(feature = "encodings")]
1564        if _expect_stdlib {
1565            if let Err(e) = self.import_encodings() {
1566                eprintln!(
1567                    "encodings initialization failed. Only utf-8 encoding will be supported."
1568                );
1569                self.print_exception(&e);
1570            }
1571        } else {
1572            // Here may not be the best place to give general `path_list` advice,
1573            // but bare rustpython_vm::VirtualMachine users skipped proper settings must hit here while properly setup vm never enters here.
1574            eprintln!(
1575                "feature `encodings` is enabled but `paths.module_search_paths` is empty. \
1576                Please add the library path to `settings.path_list`. If you intended to disable the entire standard library (including the `encodings` feature), please also make sure to disable the `encodings` feature.\n\
1577                Tip: You may also want to add `\"\"` to `settings.path_list` in order to enable importing from the current working directory."
1578            );
1579        }
1580
1581        self.initialized = true;
1582    }
1583
1584    /// Set the custom signal channel for the interpreter
1585    pub fn set_user_signal_channel(&mut self, signal_rx: signal::UserSignalReceiver) {
1586        self.signal_rx = Some(signal_rx);
1587    }
1588
1589    /// Execute Python bytecode (`.pyc`) from an in-memory buffer.
1590    ///
1591    /// When the RustPython CLI is available, `.pyc` files are normally executed by
1592    /// invoking `rustpython <input>.pyc`. This method provides an alternative for
1593    /// environments where the binary is unavailable or file I/O is restricted
1594    /// (e.g. WASM).
1595    ///
1596    /// ## Preparing a `.pyc` file
1597    ///
1598    /// First, compile a Python source file into bytecode:
1599    ///
1600    /// ```sh
1601    /// # Generate a .pyc file
1602    /// $ rustpython -m py_compile <input>.py
1603    /// ```
1604    ///
1605    /// ## Running the bytecode
1606    ///
1607    /// Load the resulting `.pyc` file into memory and execute it using the VM:
1608    ///
1609    /// ```no_run
1610    /// use rustpython_vm::Interpreter;
1611    /// Interpreter::without_stdlib(Default::default()).enter(|vm| {
1612    ///     let bytes = std::fs::read("__pycache__/<input>.rustpython-314.pyc").unwrap();
1613    ///     let main_scope = vm.new_scope_with_main().unwrap();
1614    ///     vm.run_pyc_bytes(&bytes, main_scope);
1615    /// });
1616    /// ```
1617    pub fn run_pyc_bytes(&self, pyc_bytes: &[u8], scope: Scope) -> PyResult<()> {
1618        let code = PyCode::from_pyc(pyc_bytes, Some("<pyc_bytes>"), None, None, self)?;
1619        self.with_simple_run("<source>", |_module_dict| {
1620            self.run_code_obj(code, scope)?;
1621            Ok(())
1622        })
1623    }
1624
1625    pub fn run_code_obj(&self, code: PyRef<PyCode>, scope: Scope) -> PyResult {
1626        self.run_code_obj_with_closure(code, scope, None)
1627    }
1628
1629    pub(crate) fn run_code_obj_with_closure(
1630        &self,
1631        code: PyRef<PyCode>,
1632        scope: Scope,
1633        closure: Option<PyRef<crate::builtins::PyTuple<crate::builtins::function::PyCellRef>>>,
1634    ) -> PyResult {
1635        use crate::builtins::PyFunction;
1636
1637        // Create a function object for module code, similar to PyEval_EvalCode
1638        let mut func = PyFunction::new(code, scope.globals.clone(), self)?;
1639        if let Some(closure) = closure {
1640            func.closure = Some(closure);
1641        }
1642        let func = func.into_ref(&self.ctx);
1643        func.invoke_with_locals(FuncArgs::default(), scope.locals, self)
1644    }
1645
1646    #[cold]
1647    pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option<String>, object: PyObjectRef) {
1648        // During interpreter finalization, sys.unraisablehook may not be available,
1649        // but we still need to report exceptions (especially from atexit callbacks).
1650        // Write directly to stderr like PyErr_FormatUnraisable.
1651        if self.state.finalizing.load(Ordering::Acquire) {
1652            self.write_unraisable_to_stderr(&e, msg.as_deref(), &object);
1653            return;
1654        }
1655
1656        let sys_module = self.import("sys", 0).unwrap();
1657        let unraisablehook = sys_module.get_attr("unraisablehook", self).unwrap();
1658
1659        let exc_type = e.class().to_owned();
1660        let exc_traceback = e.traceback().to_pyobject(self); // TODO: actual traceback
1661        let exc_value = e.into();
1662        let args = stdlib::sys::UnraisableHookArgsData {
1663            exc_type,
1664            exc_value,
1665            exc_traceback,
1666            err_msg: self.new_pyobj(msg),
1667            object,
1668        };
1669        if let Err(e) = unraisablehook.call((args,), self) {
1670            println!("{}", e.as_object().repr(self).unwrap());
1671        }
1672    }
1673
1674    /// Write unraisable exception to stderr during finalization.
1675    /// Similar to _PyErr_WriteUnraisableDefaultHook in CPython.
1676    fn write_unraisable_to_stderr(
1677        &self,
1678        e: &Py<PyBaseException>,
1679        msg: Option<&str>,
1680        object: &PyObject,
1681    ) {
1682        // Get stderr once and reuse it
1683        let stderr = crate::stdlib::sys::get_stderr(self).ok();
1684
1685        let write_to_stderr = |s: &str, stderr: &Option<PyObjectRef>, vm: &Self| {
1686            if let Some(stderr) = stderr {
1687                let _ = vm.call_method(stderr, "write", (s.to_owned(),));
1688            } else {
1689                eprint!("{s}");
1690            }
1691        };
1692
1693        if self.is_none(object) {
1694            if let Some(msg) = msg {
1695                write_to_stderr(&format!("{msg}:\n"), &stderr, self);
1696            }
1697        } else {
1698            let msg_str = if let Some(msg) = msg {
1699                format!("{msg}: ")
1700            } else {
1701                "Exception ignored in: ".to_owned()
1702            };
1703            write_to_stderr(&msg_str, &stderr, self);
1704
1705            let repr_result = object.repr(self);
1706            let repr_wtf8 = repr_result
1707                .as_ref()
1708                .map_or_else(|_| "<object repr failed>".as_ref(), |s| s.as_wtf8());
1709            write_to_stderr(&format!("{repr_wtf8}\n"), &stderr, self);
1710        }
1711
1712        // Write exception type and message
1713        let exc_type_name = e.class().name();
1714        let msg = match e.as_object().str(self) {
1715            Ok(exc_str) if !exc_str.as_wtf8().is_empty() => {
1716                format!("{}: {}\n", exc_type_name, exc_str.as_wtf8())
1717            }
1718            _ => format!("{exc_type_name}\n"),
1719        };
1720        write_to_stderr(&msg, &stderr, self);
1721
1722        // Flush stderr to ensure output is visible
1723        if let Some(ref stderr) = stderr {
1724            let _ = self.call_method(stderr, "flush", ());
1725        }
1726    }
1727
1728    /// Store a callee frame pointer for the trampoline to pick up after
1729    /// `TailCall` is returned. The pointed-to InterpreterFrame must live
1730    /// on the current thread's datastack and remain valid until the
1731    /// trampoline calls `take_pending_tailcall`.
1732    #[inline(always)]
1733    pub(crate) fn set_pending_tailcall(&self, iframe: &mut crate::frame::InterpreterFrame) {
1734        self.pending_tailcall_frame
1735            .set(Some(PendingFrame(core::ptr::NonNull::from(iframe))));
1736    }
1737
1738    /// Store the function that owns the fields borrowed by the pending callee.
1739    #[inline(always)]
1740    pub(crate) fn set_pending_tailcall_owner(&self, owner: PyObjectRef) {
1741        let slot = unsafe { &mut *self.pending_tailcall_owner.get() };
1742        debug_assert!(slot.is_none(), "pending TailCall owner was not consumed");
1743        *slot = Some(owner);
1744    }
1745
1746    /// Take the pending callee owner, resetting the side channel.
1747    #[inline(always)]
1748    fn take_pending_tailcall_owner(&self) -> PyObjectRef {
1749        unsafe { &mut *self.pending_tailcall_owner.get() }
1750            .take()
1751            .expect("TailCall without pending owner")
1752    }
1753
1754    /// Park a generator for the trampoline to resume, along with the value
1755    /// to send it and what this frame does with the outcome. The bytecode
1756    /// loop then returns `ExecutionResult::GenResume`.
1757    #[inline]
1758    pub(crate) fn set_pending_gen_resume(
1759        &self,
1760        jen: PyObjectRef,
1761        value: PyObjectRef,
1762        cont: crate::frame::GenCont,
1763    ) {
1764        // SAFETY: per-thread VM; the slot is written here and taken by the
1765        // trampoline before anything else can run.
1766        let slot = unsafe { &mut *self.pending_gen_resume.get() };
1767        debug_assert!(slot.is_none(), "pending GenResume was not consumed");
1768        *slot = Some(PendingGenResume { jen, value, cont });
1769    }
1770
1771    /// Take the parked generator resume, resetting the side channel.
1772    #[inline]
1773    fn take_pending_gen_resume(&self) -> PendingGenResume {
1774        // SAFETY: per-thread VM; see `set_pending_gen_resume`.
1775        unsafe { &mut *self.pending_gen_resume.get() }
1776            .take()
1777            .expect("GenResume without a parked generator")
1778    }
1779
1780    /// Suspend a frame on the trampoline's shared stack.
1781    #[inline]
1782    fn trampoline_push(&self, frame: SuspendedFrame) {
1783        // SAFETY: per-thread VM; no reference into the Vec outlives this call.
1784        unsafe { (*self.trampoline_stack.get()).push(frame) }
1785    }
1786
1787    /// Take back the innermost frame this trampoline invocation suspended, or
1788    /// `None` once it has taken back all of them.
1789    #[inline]
1790    fn trampoline_pop(&self, base: usize) -> Option<SuspendedFrame> {
1791        // SAFETY: per-thread VM; no reference into the Vec outlives this call.
1792        let stack = unsafe { &mut *self.trampoline_stack.get() };
1793        if stack.len() > base {
1794            stack.pop()
1795        } else {
1796            None
1797        }
1798    }
1799
1800    /// How many frames the trampoline's shared stack holds; the base a nested
1801    /// invocation must not pop below.
1802    #[inline]
1803    fn trampoline_depth(&self) -> usize {
1804        // SAFETY: per-thread VM; no reference into the Vec outlives this call.
1805        unsafe { (*self.trampoline_stack.get()).len() }
1806    }
1807
1808    /// Take the pending tailcall frame pointer, resetting the side channel.
1809    #[inline(always)]
1810    fn take_pending_tailcall(&self) -> *mut crate::frame::InterpreterFrame {
1811        self.pending_tailcall_frame
1812            .take()
1813            .expect("TailCall without pending frame")
1814            .0
1815            .as_ptr()
1816    }
1817
1818    /// Run a stack-allocated InterpreterFrame without heap allocation.
1819    /// Uses a trampoline loop to flatten Python-to-Python calls: when the
1820    /// bytecode loop returns `TailCall`, the trampoline swaps to the new
1821    /// frame without adding a Rust stack frame.
1822    #[inline(always)]
1823    pub fn run_frame_fast(&self, iframe: &mut crate::frame::InterpreterFrame) -> PyResult {
1824        use crate::frame::ExecutionResult;
1825
1826        let entry_state = self.enter_iframe(iframe)?;
1827        let result =
1828            crate::frame::run_iframe(iframe, crate::frame::Flatten::CallAndGenResume, self);
1829
1830        match result {
1831            Ok(ExecutionResult::Return(value)) => {
1832                self.exit_iframe(entry_state);
1833                Ok(value)
1834            }
1835            Ok(first @ (ExecutionResult::TailCall | ExecutionResult::GenResume)) => {
1836                match self.run_trampoline(
1837                    iframe,
1838                    FrameKind::Entry(entry_state),
1839                    TrampolineStart::Ran(Ok(first)),
1840                )? {
1841                    ExecutionResult::Return(value) => Ok(value),
1842                    _ => panic!("non-return result from a plain call frame"),
1843                }
1844            }
1845            Ok(ExecutionResult::Yield(_)) => panic!("Yield in non-generator frame"),
1846            Err(exc) => {
1847                self.exit_iframe(entry_state);
1848                Err(exc)
1849            }
1850        }
1851    }
1852
1853    /// Run the body of a generator or coroutine whose frame is already linked
1854    /// into the frame chain (see `resume_gen_frame`), flattening the ordinary
1855    /// Python calls it makes through the same trampoline `run_frame_fast`
1856    /// uses.
1857    ///
1858    /// The frame is heap-resident and its resume bookkeeping belongs to the
1859    /// caller, so the trampoline neither enters nor exits it and hands back
1860    /// its `Yield` unchanged.
1861    #[inline(always)]
1862    pub(crate) fn run_gen_frame(
1863        &self,
1864        iframe: &mut crate::frame::InterpreterFrame,
1865    ) -> PyResult<ExecutionResult> {
1866        match crate::frame::run_iframe(iframe, crate::frame::Flatten::GenResume, self) {
1867            Ok(first @ (ExecutionResult::TailCall | ExecutionResult::GenResume)) => {
1868                self.run_trampoline(iframe, FrameKind::GenEntry, TrampolineStart::Ran(Ok(first)))
1869            }
1870            result => result,
1871        }
1872    }
1873
1874    /// Resume a generator body that is itself parked at a `yield from`, by
1875    /// running its delegate in its place — the same collapse the trampoline
1876    /// applies to the levels below, extended to the outermost one, which is
1877    /// where every `Coro::send` from Rust (asyncio's task step, `next()`)
1878    /// enters a chain.
1879    #[inline(always)]
1880    pub(crate) fn run_gen_frame_delegating(
1881        &self,
1882        iframe: &mut crate::frame::InterpreterFrame,
1883        delegate: PyObjectRef,
1884        value: PyObjectRef,
1885        cont: crate::frame::GenCont,
1886    ) -> PyResult<ExecutionResult> {
1887        self.run_trampoline(
1888            iframe,
1889            FrameKind::GenEntry,
1890            TrampolineStart::Delegating {
1891                delegate,
1892                value,
1893                cont,
1894            },
1895        )
1896    }
1897
1898    /// Run a frame under the trampoline the way its kind calls for: an
1899    /// ordinary frame may tail-call, a generator body may not.
1900    #[inline(always)]
1901    fn trampoline_run(
1902        &self,
1903        iframe: &mut crate::frame::InterpreterFrame,
1904        kind: &FrameKind,
1905    ) -> PyResult<ExecutionResult> {
1906        crate::frame::run_iframe(iframe, kind.flatten(), self)
1907    }
1908
1909    /// Free a callee frame's data stack storage, if it still owns any.
1910    #[inline]
1911    fn release_trampoline_callee(&self, mut iframe: TrampolineIFrame) {
1912        // SAFETY: the callee has finished; its storage is the top of this
1913        // thread's data stack because frames are released in LIFO order.
1914        unsafe {
1915            if let Some((base, size)) = iframe.as_mut().release_datastack_frame() {
1916                self.datastack_pop_frame(base, size);
1917            }
1918        }
1919    }
1920
1921    /// Resume a generator parked for the trampoline, walking straight down a
1922    /// `yield from` / `await` chain: every frame it finds suspended at a
1923    /// `yield from` whose delegate can be resumed is parked without running an
1924    /// instruction of its own, and the value is handed a level further down.
1925    ///
1926    /// Each level is still claimed, linked and (later) unlinked exactly as a
1927    /// recursive `Coro::send` would, so `gi_running`, `f_back`, `gi_frame`,
1928    /// tracebacks and `sys._getframe` see the same chain; only the frames'
1929    /// `SEND`/`YIELD_VALUE`/`RESUME`/`JUMP_BACKWARD` dispatch is skipped,
1930    /// which is what [`crate::frame::yield_from_delegate`] proves redundant.
1931    fn trampoline_resume_gen(&self, jen: PyObjectRef, value: PyObjectRef) -> GenEntered {
1932        use crate::coroutine::FlatEnter;
1933
1934        let mut jen = jen;
1935        let mut value = value;
1936        loop {
1937            let (state, sent) = match crate::coroutine::flat_resume_enter(jen, value, self) {
1938                Ok(FlatEnter::Entered { state, value }) => (state, value),
1939                Ok(FlatEnter::Exhausted) => {
1940                    return GenEntered::Failed(Outcome::GenStop(None));
1941                }
1942                Err(exc) => return GenEntered::Failed(Outcome::Raise(exc)),
1943            };
1944            // SAFETY: the frame is linked and claimed, so this thread is its
1945            // only executor for as long as the handle below lives.
1946            let mut iframe = unsafe { TrampolineIFrame::from_ptr(state.iframe_ptr()) };
1947            if let Some(sent) = sent {
1948                if let Some((delegate, cont)) =
1949                    crate::frame::yield_from_delegate(iframe.as_mut(), self)
1950                    && crate::frame::gen_collapse_allowed(self)
1951                {
1952                    crate::frame::park_at_send(iframe.as_mut(), cont);
1953                    self.trampoline_push(SuspendedFrame {
1954                        iframe,
1955                        kind: FrameKind::Gen(state),
1956                        callee_owner: None,
1957                        cont,
1958                    });
1959                    jen = delegate;
1960                    value = sent;
1961                    continue;
1962                }
1963                iframe.as_mut().localsplus.push_stack(sent);
1964            }
1965            let result =
1966                crate::frame::run_iframe(iframe.as_mut(), crate::frame::Flatten::GenResume, self);
1967            return GenEntered::Ran {
1968                iframe,
1969                state,
1970                result,
1971            };
1972        }
1973    }
1974
1975    /// Unlink a generator frame that has finished a resume and turn what it
1976    /// produced into the outcome its caller is waiting for.
1977    fn trampoline_finish_gen(
1978        &self,
1979        state: crate::coroutine::FlatResume,
1980        result: PyResult<ExecutionResult>,
1981    ) -> Outcome {
1982        match crate::coroutine::flat_resume_exit(state, result, self) {
1983            Ok(PyIterReturn::Return(value)) => Outcome::Value(value),
1984            Ok(PyIterReturn::StopIteration(value)) => Outcome::GenStop(value),
1985            Err(exc) => Outcome::Raise(exc),
1986        }
1987    }
1988
1989    /// Cold path: the entry frame handed something to the trampoline. Run it.
1990    /// All frame dispatch happens in this single loop — no mutual recursion
1991    /// between helper functions, so C stack depth is bounded.
1992    #[cold]
1993    #[inline(never)]
1994    fn run_trampoline(
1995        &self,
1996        iframe: &mut crate::frame::InterpreterFrame,
1997        kind: FrameKind,
1998        start: TrampolineStart,
1999    ) -> PyResult<ExecutionResult> {
2000        use crate::frame::ExecutionResult;
2001
2002        let iframe = TrampolineIFrame::from_mut(iframe);
2003
2004        /// What the loop does next. Deliberately small, and holding no frame
2005        /// state: every ordinary Python-to-Python call passes through here.
2006        enum Action {
2007            /// Enter and run the data stack frame a `TailCall` prepared.
2008            EnterCallee(TrampolineIFrame),
2009            /// Resume the generator a `GenResume` parked, walking down its
2010            /// `yield from` chain.
2011            ResumeGen {
2012                jen: PyObjectRef,
2013                value: PyObjectRef,
2014            },
2015            /// Hand an outcome to the frame on top of the trampoline's stack.
2016            Deliver(Outcome),
2017        }
2018
2019        // Frames this invocation suspends live above `base` on the VM's
2020        // shared trampoline stack, reused across invocations so that entering
2021        // the trampoline — which a generator body does on every resume —
2022        // costs no allocation. Every exit below has already popped them.
2023        let base = self.trampoline_depth();
2024
2025        // Turn what a frame produced into the next `Action`, suspending the
2026        // frame if it wants to enter another and unlinking it if it is done.
2027        // A finished entry frame returns out of the trampoline.
2028        macro_rules! dispatch {
2029            ($iframe:expr, $kind:expr, $result:expr) => {{
2030                let iframe = $iframe;
2031                let kind = $kind;
2032                match $result {
2033                    Ok(ExecutionResult::TailCall) => {
2034                        let callee_owner = self.take_pending_tailcall_owner();
2035                        // SAFETY: the callee was just allocated on this
2036                        // thread's data stack; this is the first handle.
2037                        let callee =
2038                            unsafe { TrampolineIFrame::from_ptr(self.take_pending_tailcall()) };
2039                        self.trampoline_push(SuspendedFrame {
2040                            iframe,
2041                            kind,
2042                            callee_owner: Some(callee_owner),
2043                            cont: crate::frame::GenCont::NONE,
2044                        });
2045                        Action::EnterCallee(callee)
2046                    }
2047                    Ok(ExecutionResult::GenResume) => {
2048                        let PendingGenResume { jen, value, cont } = self.take_pending_gen_resume();
2049                        // The generator owns its own frame and everything the
2050                        // frame borrows, so this record needs no callee owner.
2051                        self.trampoline_push(SuspendedFrame {
2052                            iframe,
2053                            kind,
2054                            callee_owner: None,
2055                            cont,
2056                        });
2057                        Action::ResumeGen { jen, value }
2058                    }
2059                    // The frame is done; undo the entry bookkeeping its kind
2060                    // calls for and hand its outcome on.
2061                    result => match kind {
2062                        FrameKind::GenEntry => {
2063                            debug_assert_eq!(self.trampoline_depth(), base);
2064                            return result;
2065                        }
2066                        FrameKind::Entry(state) => {
2067                            debug_assert_eq!(self.trampoline_depth(), base);
2068                            self.exit_iframe(state);
2069                            return match result {
2070                                Ok(ExecutionResult::Yield(_)) => {
2071                                    panic!("Yield in non-generator frame")
2072                                }
2073                                result => result,
2074                            };
2075                        }
2076                        FrameKind::Callee(state) => {
2077                            self.exit_iframe(state);
2078                            self.release_trampoline_callee(iframe);
2079                            match result {
2080                                Ok(ExecutionResult::Return(value)) => {
2081                                    Action::Deliver(Outcome::Value(value))
2082                                }
2083                                Ok(ExecutionResult::Yield(_)) => {
2084                                    panic!("Yield in non-generator frame")
2085                                }
2086                                Ok(_) => unreachable!("unfinished frame result"),
2087                                Err(exc) => Action::Deliver(Outcome::Raise(exc)),
2088                            }
2089                        }
2090                        FrameKind::Gen(state) => {
2091                            Action::Deliver(self.trampoline_finish_gen(state, result))
2092                        }
2093                    },
2094                }
2095            }};
2096        }
2097
2098        let mut action = match start {
2099            TrampolineStart::Ran(result) => dispatch!(iframe, kind, result),
2100            TrampolineStart::Delegating {
2101                delegate,
2102                value,
2103                cont,
2104            } => {
2105                self.trampoline_push(SuspendedFrame {
2106                    iframe,
2107                    kind,
2108                    callee_owner: None,
2109                    cont,
2110                });
2111                Action::ResumeGen {
2112                    jen: delegate,
2113                    value,
2114                }
2115            }
2116        };
2117
2118        loop {
2119            action = match action {
2120                Action::EnterCallee(mut callee) => {
2121                    match self.enter_iframe_unchecked(callee.as_mut()) {
2122                        Ok(state) => {
2123                            let result = crate::frame::run_iframe(
2124                                callee.as_mut(),
2125                                crate::frame::Flatten::CallAndGenResume,
2126                                self,
2127                            );
2128                            dispatch!(callee, FrameKind::Callee(state), result)
2129                        }
2130                        Err(exc) => {
2131                            self.release_trampoline_callee(callee);
2132                            Action::Deliver(Outcome::Raise(exc))
2133                        }
2134                    }
2135                }
2136
2137                Action::ResumeGen { jen, value } => match self.trampoline_resume_gen(jen, value) {
2138                    GenEntered::Ran {
2139                        iframe,
2140                        state,
2141                        result,
2142                    } => dispatch!(iframe, FrameKind::Gen(state), result),
2143                    GenEntered::Failed(outcome) => Action::Deliver(outcome),
2144                },
2145
2146                Action::Deliver(outcome) => {
2147                    // Every frame the trampoline enters is entered from a
2148                    // frame it has already suspended, so an outcome always has
2149                    // a caller waiting for it.
2150                    let SuspendedFrame {
2151                        mut iframe,
2152                        kind,
2153                        callee_owner,
2154                        cont,
2155                    } = self
2156                        .trampoline_pop(base)
2157                        .expect("trampoline outcome with no frame to deliver it to");
2158                    // The callee's frame was released before this outcome was
2159                    // formed, and a materialized frame object holds its own
2160                    // references, so nothing borrows the callee's function any
2161                    // more. Release it here, at the callee's return, rather
2162                    // than holding it across the caller's next stretch of
2163                    // bytecode.
2164                    drop(callee_owner);
2165                    match outcome {
2166                        // A frame parked mid `yield from` re-yields what its
2167                        // delegate produced, running no instruction of its own.
2168                        Outcome::Value(value)
2169                            if cont.is_some() && crate::frame::gen_collapse_allowed(self) =>
2170                        {
2171                            crate::frame::park_after_yield_from(iframe.as_mut(), cont.resumed_at);
2172                            match kind {
2173                                FrameKind::Gen(state) => {
2174                                    Action::Deliver(self.trampoline_finish_gen(
2175                                        state,
2176                                        Ok(ExecutionResult::Yield(value)),
2177                                    ))
2178                                }
2179                                // The entry frame re-yields the same way, which
2180                                // ends this invocation: its resume bookkeeping
2181                                // is its caller's, not the trampoline's.
2182                                kind => {
2183                                    debug_assert!(matches!(kind, FrameKind::GenEntry));
2184                                    debug_assert_eq!(self.trampoline_depth(), base);
2185                                    return Ok(ExecutionResult::Yield(value));
2186                                }
2187                            }
2188                        }
2189                        Outcome::Value(value) => {
2190                            iframe.as_mut().localsplus.push_stack(value);
2191                            let result = self.trampoline_run(iframe.as_mut(), &kind);
2192                            dispatch!(iframe, kind, result)
2193                        }
2194                        Outcome::GenStop(value) => {
2195                            debug_assert!(
2196                                cont.is_some(),
2197                                "a generator finished with no continuation to apply"
2198                            );
2199                            let result = match crate::frame::trampoline_gen_stop(
2200                                iframe.as_mut(),
2201                                value,
2202                                cont,
2203                                self,
2204                            ) {
2205                                Ok(()) => self.trampoline_run(iframe.as_mut(), &kind),
2206                                Err(exc) => Err(exc),
2207                            };
2208                            dispatch!(iframe, kind, result)
2209                        }
2210                        Outcome::Raise(exc) => {
2211                            let result = match crate::frame::trampoline_handle_exception(
2212                                iframe.as_mut(),
2213                                &exc,
2214                                self,
2215                            ) {
2216                                // Handler found — resume the caller's loop.
2217                                Ok(None) => self.trampoline_run(iframe.as_mut(), &kind),
2218                                Ok(Some(result)) => Ok(result),
2219                                Err(exc) => Err(exc),
2220                            };
2221                            dispatch!(iframe, kind, result)
2222                        }
2223                    }
2224                }
2225            };
2226        }
2227    }
2228
2229    pub fn run_frame(&self, frame: FrameObjectRef) -> PyResult {
2230        // Only ordinary (datastack) call frames reach `run_frame`; generator
2231        // and coroutine frames are resumed through `resume_gen_frame`. A
2232        // datastack frame is created untracked and is tracked lazily only when
2233        // it escapes, which happens no earlier than `release_datastack_frame`
2234        // after this call returns. So it must be untracked on entry.
2235        debug_assert!(
2236            !frame.as_object().is_gc_tracked(),
2237            "datastack frame is GC-tracked before execution"
2238        );
2239        match self.with_frame(frame, |f| f.run(self))? {
2240            ExecutionResult::Return(value) => Ok(value),
2241            _ => panic!("Got unexpected result from function"),
2242        }
2243    }
2244
2245    /// Run `run` with main scope.
2246    fn with_simple_run(
2247        &self,
2248        path: &str,
2249        run: impl FnOnce(&Py<PyDict>) -> PyResult<()>,
2250    ) -> PyResult<()> {
2251        let sys_modules = self.sys_module.get_attr(identifier!(self, modules), self)?;
2252        let main_module = sys_modules.get_item(identifier!(self, __main__), self)?;
2253        let module_dict = main_module.dict().expect("main module must have __dict__");
2254
2255        // Track whether we set __file__ (for cleanup)
2256        let set_file_name = !module_dict.contains_key(identifier!(self, __file__), self);
2257        if set_file_name {
2258            module_dict.set_item(
2259                identifier!(self, __file__),
2260                self.ctx.new_str(path).into(),
2261                self,
2262            )?;
2263            module_dict.set_item(identifier!(self, __cached__), self.ctx.none(), self)?;
2264        }
2265
2266        let result = run(&module_dict);
2267
2268        self.flush_io();
2269
2270        // Cleanup __file__ and __cached__ after execution
2271        if set_file_name {
2272            let _ = module_dict.del_item(identifier!(self, __file__), self);
2273            let _ = module_dict.del_item(identifier!(self, __cached__), self);
2274        }
2275
2276        result
2277    }
2278
2279    /// flush_io
2280    ///
2281    /// Flush stdout and stderr. Errors are silently ignored.
2282    fn flush_io(&self) {
2283        if let Ok(stdout) = self.sys_module.get_attr("stdout", self) {
2284            let _ = self.call_method(&stdout, identifier!(self, flush).as_str(), ());
2285        }
2286        if let Ok(stderr) = self.sys_module.get_attr("stderr", self) {
2287            let _ = self.call_method(&stderr, identifier!(self, flush).as_str(), ());
2288        }
2289    }
2290
2291    /// Clear module references during shutdown.
2292    /// Follows the same phased algorithm as pylifecycle.c finalize_modules():
2293    /// no hardcoded module names, reverse import order, only builtins/sys last.
2294    pub fn finalize_modules(&self) {
2295        // Phase 1: Set special sys/builtins attributes to None, restore stdio
2296        self.finalize_modules_delete_special();
2297
2298        // Phase 2: Remove all modules from sys.modules (set values to None),
2299        // and collect weakrefs to modules preserving import order.
2300        // No strong refs are kept — modules freed when their last ref drops.
2301        let module_weakrefs = self.finalize_remove_modules();
2302
2303        // Phase 3: Clear sys.modules dict
2304        self.finalize_clear_modules_dict();
2305
2306        // Phase 4: GC collect — modules removed from sys.modules are freed,
2307        // exposing cycles (e.g., dict ↔ function.__globals__). GC collects
2308        // these and calls __del__ while module dicts are still intact.
2309        self.state.gc.collect_force(2);
2310
2311        // Phase 5: Clear module dicts in reverse import order using 2-pass algorithm.
2312        // Skip builtins and sys — those are cleared last.
2313        self.finalize_clear_module_dicts(&module_weakrefs);
2314
2315        // Phase 6: GC collect — pick up anything freed by dict clearing.
2316        self.state.gc.collect_force(2);
2317
2318        // Phase 7: Clear sys and builtins dicts last
2319        self.finalize_clear_sys_builtins_dict();
2320    }
2321
2322    /// Phase 1: Set special sys attributes to None and restore stdio.
2323    fn finalize_modules_delete_special(&self) {
2324        let none = self.ctx.none();
2325        let sys_dict = self.sys_module.dict();
2326
2327        // Set special sys attributes to None
2328        for attr in &[
2329            "path",
2330            "argv",
2331            "ps1",
2332            "ps2",
2333            "last_exc",
2334            "last_type",
2335            "last_value",
2336            "last_traceback",
2337            "path_importer_cache",
2338            "meta_path",
2339            "path_hooks",
2340        ] {
2341            let _ = sys_dict.set_item(*attr, none.clone(), self);
2342        }
2343
2344        // Restore stdin/stdout/stderr from __stdin__/__stdout__/__stderr__
2345        for (std_name, dunder_name) in &[
2346            ("stdin", "__stdin__"),
2347            ("stdout", "__stdout__"),
2348            ("stderr", "__stderr__"),
2349        ] {
2350            let restored = sys_dict
2351                .get_item_opt(*dunder_name, self)
2352                .ok()
2353                .flatten()
2354                .unwrap_or_else(|| none.clone());
2355            let _ = sys_dict.set_item(*std_name, restored, self);
2356        }
2357
2358        // builtins._ = None
2359        let _ = self.builtins.dict().set_item("_", none, self);
2360    }
2361
2362    /// Phase 2: Set all sys.modules values to None and collect weakrefs.
2363    /// No strong refs are kept — modules are freed when removed from sys.modules
2364    /// (if nothing else references them), allowing GC to collect their cycles.
2365    fn finalize_remove_modules(&self) -> Vec<(String, PyRef<PyWeak>)> {
2366        let mut module_weakrefs = Vec::new();
2367
2368        let Ok(modules) = self.sys_module.get_attr(identifier!(self, modules), self) else {
2369            return module_weakrefs;
2370        };
2371        let Some(modules_dict) = modules.downcast_ref::<PyDict>() else {
2372            return module_weakrefs;
2373        };
2374
2375        let none = self.ctx.none();
2376        let items: Vec<_> = modules_dict.into_iter().collect();
2377
2378        for (key, value) in items {
2379            let name = key
2380                .downcast_ref::<PyUtf8Str>()
2381                .map(|s| s.as_str().to_owned())
2382                .unwrap_or_default();
2383
2384            // Save weakref to module (for later dict clearing)
2385            if value.downcast_ref::<PyModule>().is_some()
2386                && let Ok(weak) = value.downgrade(None, self)
2387            {
2388                module_weakrefs.push((name, weak));
2389            }
2390
2391            // Set the value to None in sys.modules
2392            let _ = modules_dict.set_item(&*key, none.clone(), self);
2393        }
2394
2395        module_weakrefs
2396    }
2397
2398    /// Phase 3: Clear sys.modules dict.
2399    fn finalize_clear_modules_dict(&self) {
2400        if let Ok(modules) = self.sys_module.get_attr(identifier!(self, modules), self)
2401            && let Some(modules_dict) = modules.downcast_ref::<PyDict>()
2402        {
2403            modules_dict.clear();
2404        }
2405    }
2406
2407    /// Phase 5: Clear module dicts in reverse import order.
2408    /// Skip builtins and sys — those are cleared last in Phase 7.
2409    fn finalize_clear_module_dicts(&self, module_weakrefs: &[(String, PyRef<PyWeak>)]) {
2410        let builtins_dict = self.builtins.dict();
2411        let sys_dict = self.sys_module.dict();
2412
2413        for (_name, weakref) in module_weakrefs.iter().rev() {
2414            let Some(module_obj) = weakref.upgrade() else {
2415                continue;
2416            };
2417            let Some(module) = module_obj.downcast_ref::<PyModule>() else {
2418                continue;
2419            };
2420
2421            let dict = module.dict();
2422            // Skip builtins and sys — they are cleared last
2423            if dict.is(&builtins_dict) || dict.is(&sys_dict) {
2424                continue;
2425            }
2426
2427            Self::module_clear_dict(&dict, self);
2428        }
2429    }
2430
2431    /// 2-pass module dict clearing (_PyModule_ClearDict algorithm).
2432    /// Pass 1: Set names starting with '_' (except __builtins__) to None.
2433    /// Pass 2: Set all remaining names (except __builtins__) to None.
2434    pub(crate) fn module_clear_dict(dict: &Py<PyDict>, vm: &Self) {
2435        let none = vm.ctx.none();
2436
2437        // Pass 1: names starting with '_' (except __builtins__)
2438        for (key, value) in dict.into_iter().collect::<Vec<_>>() {
2439            if vm.is_none(&value) {
2440                continue;
2441            }
2442            if let Some(key_str) = key.downcast_ref::<PyStr>() {
2443                let name = key_str.as_wtf8();
2444                if name.starts_with("_") && name != "__builtins__" {
2445                    let _ = dict.set_item(key_str, none.clone(), vm);
2446                }
2447            }
2448        }
2449
2450        // Pass 2: all remaining (except __builtins__)
2451        for (key, value) in dict.into_iter().collect::<Vec<_>>() {
2452            if vm.is_none(&value) {
2453                continue;
2454            }
2455            if let Some(key_str) = key.downcast_ref::<PyStr>()
2456                && key_str.as_bytes() != b"__builtins__"
2457            {
2458                let _ = dict.set_item(key_str.as_wtf8(), none.clone(), vm);
2459            }
2460        }
2461    }
2462
2463    /// Phase 7: Clear sys and builtins dicts last.
2464    fn finalize_clear_sys_builtins_dict(&self) {
2465        Self::module_clear_dict(&self.sys_module.dict(), self);
2466        Self::module_clear_dict(&self.builtins.dict(), self);
2467    }
2468
2469    pub fn current_recursion_depth(&self) -> usize {
2470        self.recursion_depth.get()
2471    }
2472
2473    /// Stack margin bytes (like _PyOS_STACK_MARGIN_BYTES).
2474    /// The margin is doubled for debug/sanitized builds because frame
2475    /// evaluation consumes more native stack in those configurations.
2476    #[cfg_attr(any(miri, target_env = "musl"), allow(dead_code))]
2477    // 2× CPython's _PY_STACK_MARGIN_BYTES to account for both heavy and
2478    // light frame native stack usage per recursion step.
2479    pub(crate) const STACK_MARGIN_BYTES: usize =
2480        (if cfg!(debug_assertions) { 16384 } else { 4096 }) * core::mem::size_of::<usize>();
2481
2482    /// How deep native recursion may go where the stack cannot be measured
2483    /// (`Py_C_RECURSION_LIMIT`). A native step costs far more stack than a
2484    /// Python one and debug builds cost more again, so this sits well under
2485    /// what a default stack holds rather than at what it would just fit.
2486    #[cfg(any(miri, target_env = "musl"))]
2487    const NATIVE_RECURSION_LIMIT_UNMEASURED: usize =
2488        if cfg!(debug_assertions) { 500 } else { 1500 };
2489
2490    /// Get the stack boundaries using platform-specific APIs.
2491    /// Returns (base, top) where base is the lowest address and top is the highest.
2492    #[cfg(all(not(miri), not(target_env = "musl"), windows))]
2493    fn get_stack_bounds() -> (usize, usize) {
2494        crate::host_env::windows::current_thread_stack_bounds()
2495    }
2496
2497    /// Get stack boundaries on non-Windows platforms.
2498    /// Falls back to estimating based on current stack pointer.
2499    #[cfg(all(not(miri), not(target_env = "musl"), not(windows)))]
2500    fn get_stack_bounds() -> (usize, usize) {
2501        // Use pthread_attr_getstack on platforms that support it
2502        #[cfg(any(target_os = "linux", target_os = "android"))]
2503        {
2504            use libc::{
2505                pthread_attr_destroy, pthread_attr_getstack, pthread_attr_t, pthread_getattr_np,
2506                pthread_self,
2507            };
2508            let mut attr: pthread_attr_t = unsafe { core::mem::zeroed() };
2509            unsafe {
2510                if pthread_getattr_np(pthread_self(), &mut attr) == 0 {
2511                    let mut stack_addr: *mut libc::c_void = core::ptr::null_mut();
2512                    let mut stack_size: libc::size_t = 0;
2513                    if pthread_attr_getstack(&attr, &mut stack_addr, &mut stack_size) == 0 {
2514                        pthread_attr_destroy(&mut attr);
2515                        let base = stack_addr as usize;
2516                        let top = base + stack_size;
2517                        return (base, top);
2518                    }
2519                    pthread_attr_destroy(&mut attr);
2520                }
2521            }
2522        }
2523
2524        #[cfg(target_os = "macos")]
2525        {
2526            use libc::{pthread_get_stackaddr_np, pthread_get_stacksize_np, pthread_self};
2527            unsafe {
2528                let thread = pthread_self();
2529                let stack_top = pthread_get_stackaddr_np(thread) as usize;
2530                let stack_size = pthread_get_stacksize_np(thread);
2531                let stack_base = stack_top - stack_size;
2532                return (stack_base, stack_top);
2533            }
2534        }
2535
2536        // Fallback: estimate based on current SP and a default stack size
2537        #[allow(unreachable_code)]
2538        {
2539            let current_sp = psm::stack_pointer() as usize;
2540            // Assume 8MB stack, estimate base
2541            let estimated_size = 8 * 1024 * 1024;
2542            let base = current_sp.saturating_sub(estimated_size);
2543            let top = current_sp + 1024 * 1024; // Assume we're not at the very top
2544            (base, top)
2545        }
2546    }
2547
2548    /// Calculate the C stack soft limit based on actual stack boundaries.
2549    /// soft_limit = base + 2 * margin (for downward-growing stacks).
2550    /// The margin is clamped to half the stack so threads created with a stack
2551    /// smaller than 2 * (2 * margin) still get usable headroom instead of a
2552    /// soft limit above their stack top (which would trip on entry).
2553    #[cfg(all(not(miri), not(target_env = "musl")))]
2554    fn calculate_c_stack_soft_limit() -> usize {
2555        let (base, top) = Self::get_stack_bounds();
2556        let stack_size = top.saturating_sub(base);
2557        let margin = (Self::STACK_MARGIN_BYTES * 2).min(stack_size / 2);
2558        base + margin
2559    }
2560
2561    /// Musl currently reports stack bounds in a way that trips the VM's
2562    /// native stack guard during frozen stdlib bootstrap, so keep the Python
2563    /// recursion limit as the only guard there.
2564    #[cfg(any(miri, target_env = "musl"))]
2565    fn calculate_c_stack_soft_limit() -> usize {
2566        0
2567    }
2568
2569    /// Check if we're near the C stack limit (like _Py_MakeRecCheck).
2570    /// One-sided: any stack pointer below the soft limit is in danger, since a
2571    /// single native frame can exceed the margin and step past it.
2572    #[cfg(all(not(miri), not(target_env = "musl")))]
2573    #[inline(always)]
2574    pub(crate) fn check_c_stack_overflow(&self) -> bool {
2575        let current_sp = psm::stack_pointer() as usize;
2576        let soft_limit = self.c_stack_soft_limit.get();
2577        current_sp < soft_limit
2578    }
2579
2580    /// Miri does not support the native stack probe, and musl currently trips
2581    /// the probe during stdlib bootstrap.
2582    #[cfg(any(miri, target_env = "musl"))]
2583    #[inline(always)]
2584    pub(crate) fn check_c_stack_overflow(&self) -> bool {
2585        false
2586    }
2587
2588    /// Used to run the body of a (possibly) recursive function. It will raise a
2589    /// RecursionError if recursive functions are nested far too many times,
2590    /// preventing a stack overflow.
2591    /// `Py_EnterRecursiveCall`: bounds native recursion that pushes no Python
2592    /// frame, against the native stack. That is a separate budget from the
2593    /// frame limit `sys.setrecursionlimit()` sets, so nesting counted here does
2594    /// not come out of what Python code has left to call with.
2595    pub fn with_recursion<R, F: FnOnce() -> PyResult<R>>(&self, _where: &str, f: F) -> PyResult<R> {
2596        // `check_c_stack_overflow()` answers no unconditionally where the stack
2597        // pointer cannot be read, which would leave this guard with nothing to
2598        // stop. A count of the nesting stands in for the measurement there.
2599        #[cfg(any(miri, target_env = "musl"))]
2600        let counted_too_deep =
2601            self.native_recursion_depth.get() >= Self::NATIVE_RECURSION_LIMIT_UNMEASURED;
2602        #[cfg(not(any(miri, target_env = "musl")))]
2603        let counted_too_deep = false;
2604
2605        if counted_too_deep || self.check_c_stack_overflow() {
2606            return Err(
2607                self.new_recursion_error(format!("maximum recursion depth exceeded {_where}"))
2608            );
2609        }
2610
2611        #[cfg(any(miri, target_env = "musl"))]
2612        let _native_depth_guard = {
2613            self.native_recursion_depth.update(|d| d + 1);
2614            scopeguard::guard((), |()| {
2615                self.native_recursion_depth.update(|d| d.saturating_sub(1))
2616            })
2617        };
2618
2619        f()
2620    }
2621
2622    pub fn with_frame<R, F: FnOnce(FrameObjectRef) -> PyResult<R>>(
2623        &self,
2624        frame: FrameObjectRef,
2625        f: F,
2626    ) -> PyResult<R> {
2627        self.check_recursive_call("")?;
2628
2629        // Every entry, not every eighth. The margin only has to cover what a
2630        // single frame takes if the check runs each time; sampling asks it to
2631        // cover eight, and a recursion whose steps re-enter through native
2632        // code -- an `__add__` chain, a sort key that sorts -- takes more than
2633        // the margin in that many.
2634        if self.check_c_stack_overflow() {
2635            return Err(self.new_recursion_error(String::new()));
2636        }
2637
2638        self.recursion_depth.update(|d| d + 1);
2639        // Decrement on all exit paths (including panic between here and
2640        // the explicit decrement at the bottom).
2641        let _depth_guard = scopeguard::guard((), |()| {
2642            self.recursion_depth.update(|d| d.saturating_sub(1))
2643        });
2644
2645        #[cfg(all(not(unix), feature = "threading"))]
2646        crate::vm::thread::push_thread_frame(FramePtr(NonNull::from(&*frame)));
2647        let iframe = frame.iframe() as *const crate::frame::InterpreterFrame;
2648        let old_chain = crate::vm::thread::set_current_frame(iframe);
2649        {
2650            #[allow(unused_imports)]
2651            use rustpython_common::atomic::Radium;
2652            frame
2653                .iframe()
2654                .previous
2655                .store(old_chain as usize, core::sync::atomic::Ordering::Relaxed);
2656        }
2657        let save_exc = frame.iframe().code().has_exc_handling;
2658        let saved_exc = if save_exc {
2659            self.current_exception()
2660        } else {
2661            None
2662        };
2663        let old_owner = frame.iframe().owner.swap(
2664            crate::frame::FrameOwner::Thread as i8,
2665            core::sync::atomic::Ordering::AcqRel,
2666        );
2667
2668        let result = self.dispatch_traced_frame(&frame, |frame| f(frame.to_owned()));
2669
2670        // Capture f_back before clearing previous so code holding a
2671        // reference to this FrameObject can walk the chain after return.
2672        if !old_chain.is_null() {
2673            let strong = frame.as_object().strong_count();
2674            // Only set retained_back if someone else holds a reference (escaped)
2675            // AND the caller already has a FrameObject. Materializing the caller
2676            // here would add refcounts on its local variables, preventing timely
2677            // __del__ / ResourceWarning on dealloc. If the caller hasn't been
2678            // materialized, f_back will resolve via the TLS chain while the
2679            // caller is still executing, or return None after it returns.
2680            if strong > 1 {
2681                let mut guard = frame.iframe().cold().retained_back.lock();
2682                if guard.is_none() {
2683                    let prev_iframe = unsafe { &*old_chain };
2684                    if let Some(fo) = prev_iframe.frame_obj() {
2685                        *guard = Some(fo.to_owned());
2686                    }
2687                }
2688            }
2689        }
2690
2691        frame
2692            .iframe()
2693            .owner
2694            .store(old_owner, core::sync::atomic::Ordering::Release);
2695        if save_exc {
2696            self.restore_exception(saved_exc);
2697        }
2698        // Clear previous before popping — it may point to a stack-allocated
2699        // iframe that will be freed when the caller releases its frame.
2700        {
2701            #[allow(unused_imports)]
2702            use rustpython_common::atomic::Radium;
2703            frame
2704                .iframe()
2705                .previous
2706                .store(0, core::sync::atomic::Ordering::Relaxed);
2707        }
2708        let _ = crate::vm::thread::set_current_frame(old_chain);
2709        #[cfg(all(not(unix), feature = "threading"))]
2710        crate::vm::thread::pop_thread_frame();
2711        // Disarm the panic guard — normal decrement.
2712        scopeguard::ScopeGuard::into_inner(_depth_guard);
2713        self.recursion_depth.update(|d| d - 1);
2714
2715        result
2716    }
2717
2718    /// Push `iframe` onto the frame chain: recursion/C-stack check, TLS
2719    /// link, exception save.  Returns the saved state needed by
2720    /// `exit_iframe`.
2721    #[inline]
2722    pub(crate) fn enter_iframe(
2723        &self,
2724        iframe: &mut crate::frame::InterpreterFrame,
2725    ) -> PyResult<IframeEntryState> {
2726        self.check_recursive_call("")?;
2727
2728        // The C stack is checked by `enter_iframe_unchecked` below.
2729        self.enter_iframe_unchecked(iframe)
2730    }
2731
2732    /// Like `enter_iframe` but skips the Python recursion depth check
2733    /// (already verified by `specialization_call_recursion_guard`).
2734    /// Still checks C-stack overflow since each `run_iframe` call
2735    /// consumes Rust stack space.
2736    #[inline(always)]
2737    pub(crate) fn enter_iframe_unchecked(
2738        &self,
2739        iframe: &mut crate::frame::InterpreterFrame,
2740    ) -> PyResult<IframeEntryState> {
2741        if self.check_c_stack_overflow() {
2742            return Err(self.new_recursion_error(String::new()));
2743        }
2744
2745        self.recursion_depth.update(|d| d + 1);
2746
2747        let iframe_ptr = iframe as *const crate::frame::InterpreterFrame;
2748        let old_chain = crate::vm::thread::set_current_frame(iframe_ptr);
2749        {
2750            #[allow(unused_imports)]
2751            use rustpython_common::atomic::Radium;
2752            iframe
2753                .previous
2754                .store(old_chain as usize, core::sync::atomic::Ordering::Relaxed);
2755        }
2756        let save_exc = iframe.code().has_exc_handling;
2757        let saved_exc = if save_exc {
2758            self.current_exception()
2759        } else {
2760            None
2761        };
2762
2763        Ok(IframeEntryState {
2764            iframe_ptr,
2765            old_chain,
2766            saved_exc,
2767            save_exc,
2768        })
2769    }
2770
2771    /// Pop `iframe` from the frame chain: sync materialized state, restore
2772    /// exception, TLS unlink, GC tracking.
2773    pub(crate) fn exit_iframe(&self, state: IframeEntryState) {
2774        let IframeEntryState {
2775            iframe_ptr,
2776            old_chain,
2777            saved_exc,
2778            save_exc,
2779        } = state;
2780
2781        // If this iframe was materialized, capture f_back so that code
2782        // holding a reference to the FrameObject can walk the chain after
2783        // return.  Read materialized through read_volatile to bypass
2784        // LLVM's noalias on the &mut iframe borrow.
2785        {
2786            let mat_ptr = unsafe {
2787                let field_ptr = core::ptr::addr_of!((*iframe_ptr).materialized);
2788                core::ptr::read_volatile(field_ptr as *const usize)
2789            };
2790            if mat_ptr != 0 {
2791                let fo = unsafe { &*(mat_ptr as *const crate::Py<crate::frame::FrameObject>) };
2792                unsafe {
2793                    let live_iframe = &*iframe_ptr;
2794                    fo.iframe_mut()
2795                        .localsplus
2796                        .sync_fastlocals_from(&live_iframe.localsplus);
2797                    fo.iframe_mut().prev_line.set(live_iframe.prev_line.get());
2798                    #[allow(unused_imports)]
2799                    use rustpython_common::atomic::Radium;
2800                    fo.iframe_mut().lasti.store(
2801                        live_iframe
2802                            .lasti
2803                            .load(core::sync::atomic::Ordering::Relaxed),
2804                        core::sync::atomic::Ordering::Relaxed,
2805                    );
2806                }
2807                // The slots above are the last write this thread makes into
2808                // the frame object, so it is now readable from anywhere.
2809                fo.iframe().detach();
2810                if !old_chain.is_null() {
2811                    let prev_iframe = unsafe { &*old_chain };
2812                    let back_fo = prev_iframe.materialize_chain(self);
2813                    *fo.iframe().cold().retained_back.lock() = Some(back_fo);
2814                }
2815                fo.iframe().owner.store(
2816                    crate::frame::FrameOwner::FrameObject as i8,
2817                    core::sync::atomic::Ordering::Release,
2818                );
2819            }
2820        }
2821
2822        if save_exc {
2823            self.restore_exception(saved_exc);
2824        }
2825        // Clear previous before popping — it may point to a stack-allocated
2826        // iframe that will be freed when the caller releases its frame.
2827        {
2828            #[allow(unused_imports)]
2829            use rustpython_common::atomic::Radium;
2830            unsafe {
2831                (*iframe_ptr)
2832                    .previous
2833                    .store(0, core::sync::atomic::Ordering::Relaxed);
2834            }
2835        }
2836        let _ = crate::vm::thread::set_current_frame(old_chain);
2837        self.recursion_depth.update(|d| d - 1);
2838
2839        // Track the materialized FrameObject in GC and release
2840        // temporary_refs after the frame is off the chain.
2841        {
2842            let mat_ptr = unsafe {
2843                let field_ptr = core::ptr::addr_of!((*iframe_ptr).materialized);
2844                core::ptr::read_volatile(field_ptr as *const usize)
2845            };
2846            if mat_ptr != 0 {
2847                let fo = unsafe { &*(mat_ptr as *const crate::Py<crate::frame::FrameObject>) };
2848                unsafe {
2849                    crate::gc_state::gc_state().track_object(
2850                        core::ptr::NonNull::from(fo.as_object()),
2851                        crate::gc_state::current_owner(),
2852                    );
2853                    let live_iframe = &*iframe_ptr;
2854                    live_iframe.cold().temporary_refs.lock().clear();
2855                }
2856            }
2857        }
2858    }
2859
2860    /// Push a generator or coroutine frame onto this thread's frame chain,
2861    /// the half of a resume that runs before the frame does.
2862    ///
2863    /// In order: the recursion and C-stack checks, the thread-frames entry,
2864    /// the current-frame and `previous` links, an extra handled-exception
2865    /// slot (`gi_exc_state`) holding `exc`, and the owner swap to `Thread`.
2866    /// `gen_frame_unlink` undoes exactly these, in reverse.
2867    ///
2868    /// Two callers drive this pair: `resume_gen_frame`, which brackets a
2869    /// recursive `ExecutingFrame::run`, and the trampoline, which resumes a
2870    /// generator inside the delegating frame's own eval loop and so calls the
2871    /// halves one step apart (see `coroutine::flat_resume_enter`). Anything
2872    /// added here has to hold for both, so keep the two calls balanced and
2873    /// leave the rest of a resume — the running claim, the sent value, the
2874    /// closed flag — to `Coro`, which is where it is shared.
2875    #[inline(always)]
2876    pub(crate) fn gen_frame_link(
2877        &self,
2878        frame: &Py<FrameObject>,
2879        exc: Option<PyBaseExceptionRef>,
2880    ) -> PyResult<GenFrameLink> {
2881        self.check_recursive_call("")?;
2882        if self.check_c_stack_overflow() {
2883            return Err(self.new_recursion_error(String::new()));
2884        }
2885        self.recursion_depth.update(|d| d + 1);
2886
2887        // SAFETY: the caller holds the frame alive for as long as it is
2888        // linked, so NonNull is valid until the matching unlink pops it.
2889        #[cfg(all(not(unix), feature = "threading"))]
2890        crate::vm::thread::push_thread_frame(FramePtr(NonNull::from(frame)));
2891        let iframe = frame.iframe() as *const crate::frame::InterpreterFrame;
2892        let old_chain = crate::vm::thread::set_current_frame(iframe);
2893        {
2894            #[allow(unused_imports)]
2895            use rustpython_common::atomic::Radium;
2896            frame
2897                .iframe()
2898                .previous
2899                .store(old_chain as usize, core::sync::atomic::Ordering::Relaxed);
2900        }
2901        // Push generator's exc_info slot onto the chain
2902        self.push_exception(exc);
2903        let old_owner = frame.iframe().owner.swap(
2904            crate::frame::FrameOwner::Thread as i8,
2905            core::sync::atomic::Ordering::AcqRel,
2906        );
2907        Ok(GenFrameLink {
2908            old_chain,
2909            old_owner,
2910        })
2911    }
2912
2913    /// Pop a generator or coroutine frame off this thread's frame chain,
2914    /// undoing `gen_frame_link` step for step.
2915    #[inline(always)]
2916    pub(crate) fn gen_frame_unlink(&self, frame: &Py<FrameObject>, link: GenFrameLink) {
2917        frame
2918            .iframe()
2919            .owner
2920            .store(link.old_owner, core::sync::atomic::Ordering::Release);
2921        self.pop_exception();
2922        // Clear previous before popping — it may point to a stack-allocated
2923        // iframe that will be freed when the caller releases its frame.
2924        {
2925            #[allow(unused_imports)]
2926            use rustpython_common::atomic::Radium;
2927            frame
2928                .iframe()
2929                .previous
2930                .store(0, core::sync::atomic::Ordering::Relaxed);
2931        }
2932        let _ = crate::vm::thread::set_current_frame(link.old_chain);
2933        #[cfg(all(not(unix), feature = "threading"))]
2934        crate::vm::thread::pop_thread_frame();
2935        self.recursion_depth.update(|d| d - 1);
2936    }
2937
2938    /// FrameObject execution for generator/coroutine resume.
2939    /// Pushes a new exc_info slot (gi_exc_state) onto the chain,
2940    /// linking the generator's saved handled-exception.
2941    pub fn resume_gen_frame<R, F: FnOnce(&Py<FrameObject>) -> PyResult<R>>(
2942        &self,
2943        frame: &FrameObjectRef,
2944        exc: Option<PyBaseExceptionRef>,
2945        f: F,
2946    ) -> PyResult<R> {
2947        let link = self.gen_frame_link(frame, exc)?;
2948        // Guard only the recursion-depth decrement against a panic unwinding
2949        // through Python code (matches `with_frame`); the state restored
2950        // below (owner/previous/exc slot/current-frame) is not similarly
2951        // guarded there either, since a panic in this codebase is a bug, not
2952        // a control-flow path any Python-level construct can observe or
2953        // resume from.
2954        let _depth_guard = scopeguard::guard((), |()| {
2955            self.recursion_depth.update(|d| d.saturating_sub(1))
2956        });
2957
2958        let result = self.dispatch_traced_frame(frame, |frame| f(frame));
2959
2960        // Restore owner, pop exc_info slot, frame chain and frames Vec on
2961        // every normal exit (Ok or Err) — captured above rather than
2962        // propagated with `?`, so this always runs.
2963        scopeguard::ScopeGuard::into_inner(_depth_guard);
2964        self.gen_frame_unlink(frame, link);
2965
2966        result
2967    }
2968
2969    /// Fire trace/profile 'call' and 'return' events around a frame body.
2970    ///
2971    /// Matches `call_trace_protected` / `trace_trampoline` protocol:
2972    /// - Fire `TraceEvent::Call`; if the trace function returns non-None,
2973    ///   install it as the per-frame `f_trace`.
2974    /// - Execute the closure (the actual frame body).
2975    /// - Fire `TraceEvent::Return` on both normal return **and** exception
2976    ///   unwind (`PY_UNWIND` → `PyTrace_RETURN` with `arg = None`).
2977    ///   Propagate any trace-function error, replacing the original exception.
2978    fn dispatch_traced_frame<R, F: FnOnce(&Py<FrameObject>) -> PyResult<R>>(
2979        &self,
2980        frame: &Py<FrameObject>,
2981        f: F,
2982    ) -> PyResult<R> {
2983        use crate::protocol::TraceEvent;
2984
2985        // 'call' is PY_START / PY_RESUME, fired from RESUME once lasti is
2986        // the resume unit. Wrapping the body would report lasti=0 (and
2987        // trace RETURN_GENERATOR on async-def construction).
2988
2989        let result = f(frame);
2990
2991        // PY_RETURN / PY_YIELD are fired from RETURN_VALUE / YIELD_VALUE.
2992        // PY_UNWIND fires PyTrace_RETURN with arg=None when the exception
2993        // leaves this frame.
2994        if result.is_err()
2995            && self.use_tracing.get()
2996            && (!self.is_none(&self.profile_func.borrow())
2997                || frame
2998                    .iframe()
2999                    .cold_opt()
3000                    .is_some_and(|c| c.trace.lock().is_some()))
3001        {
3002            let ret_result = self.trace_event_what(
3003                TraceEvent::Return,
3004                crate::stdlib::sys::monitoring::MonitoringEvent::PyUnwind,
3005                None,
3006            );
3007            // call_trace_protected: if trace function raises, its error
3008            // replaces the original exception.
3009            ret_result?;
3010        }
3011
3012        result
3013    }
3014
3015    /// Returns a basic CompileOpts instance with options accurate to the vm. Used
3016    /// as the CompileOpts for `vm.compile()`.
3017    #[cfg(feature = "rustpython-codegen")]
3018    pub fn compile_opts(&self) -> crate::compiler::CompileOpts {
3019        crate::compiler::CompileOpts {
3020            optimize: self.state.config.settings.optimize.min(2),
3021            debug_ranges: self.state.config.settings.code_debug_ranges,
3022            int_max_str_digits: self.state.int_max_str_digits.load(),
3023            allow_top_level_await: false,
3024            future_features: crate::bytecode::CodeFlags::empty(),
3025            dont_imply_dedent: false,
3026            recursion_limit: self.recursion_limit.get(),
3027        }
3028    }
3029
3030    /// `PySys_Audit`: raise audit `event` to the registered hooks. `args` is only built when a hook
3031    /// is registered.
3032    pub fn audit<A: crate::function::IntoFuncArgs>(
3033        &self,
3034        event: &str,
3035        args: impl FnOnce() -> A,
3036    ) -> PyResult<()> {
3037        if self.state.audit_hooks.lock().is_empty() {
3038            return Ok(());
3039        }
3040        let event = self.ctx.new_str(event);
3041        let args = self.ctx.new_tuple(args().into_args(self).args);
3042        crate::stdlib::sys::sys::run_audit_hooks(&event, args.as_object(), self)
3043    }
3044
3045    #[inline]
3046    pub(crate) fn enter_tracing(&self) {
3047        self.tracing_depth.set(self.tracing_depth.get() + 1);
3048    }
3049
3050    #[inline]
3051    pub(crate) fn leave_tracing(&self) {
3052        let depth = self.tracing_depth.get();
3053        debug_assert!(depth > 0);
3054        self.tracing_depth.set(depth.saturating_sub(1));
3055    }
3056
3057    #[inline]
3058    pub(crate) fn tracing_is_suppressed(&self) -> bool {
3059        self.tracing_depth.get() != 0
3060    }
3061
3062    // To be called right before raising the recursion depth.
3063    fn check_recursive_call(&self, _where: &str) -> PyResult<()> {
3064        if self.recursion_depth.get() >= self.recursion_limit.get() {
3065            Err(self.new_recursion_error(format!("maximum recursion depth exceeded {_where}")))
3066        } else {
3067            Ok(())
3068        }
3069    }
3070
3071    pub fn current_frame(&self) -> Option<FrameObjectRef> {
3072        crate::frame::current_thread_frame_materialize(self)
3073    }
3074
3075    pub fn current_locals(&self) -> PyResult<ArgMapping> {
3076        // Must include light frames so locals() returns the correct scope.
3077        crate::frame::current_thread_frame_materialize(self)
3078            .expect("called current_locals but no frames on the stack")
3079            .locals(self)
3080    }
3081
3082    pub fn current_globals(&self) -> PyDictRef {
3083        let ptr = crate::vm::thread::get_current_frame();
3084        if !ptr.is_null() {
3085            return unsafe { (*ptr).globals().to_owned() };
3086        }
3087        crate::frame::current_globals().expect("called current_globals but no frames on the stack")
3088    }
3089
3090    pub fn try_class(&self, module: &'static str, class: &'static str) -> PyResult<PyTypeRef> {
3091        let class = self
3092            .import(module, 0)?
3093            .get_attr(class, self)?
3094            .downcast()
3095            .expect("not a class");
3096        Ok(class)
3097    }
3098
3099    pub fn class(&self, module: &'static str, class: &'static str) -> PyTypeRef {
3100        let module = self
3101            .import(module, 0)
3102            .unwrap_or_else(|_| panic!("unable to import {module}"));
3103
3104        let class = module
3105            .get_attr(class, self)
3106            .unwrap_or_else(|_| panic!("module {module:?} has no class {class}"));
3107        class.downcast().expect("not a class")
3108    }
3109
3110    /// Call Python __import__ function without from_list.
3111    /// Roughly equivalent to `import module_name` or `import top.submodule`.
3112    ///
3113    /// See also [`VirtualMachine::import_from`] for more advanced import.
3114    /// See also [`rustpython_vm::import::import_source`] and other primitive import functions.
3115    #[inline]
3116    pub fn import<'a>(&self, module_name: impl AsPyStr<'a>, level: usize) -> PyResult {
3117        let module_name = module_name.as_pystr(&self.ctx);
3118        self.import_inner(module_name, self.ctx.none(), level)
3119    }
3120
3121    /// Call Python __import__ function caller with from_list.
3122    /// Roughly equivalent to `from module_name import item1, item2` or `from top.submodule import item1, item2`
3123    #[inline]
3124    pub fn import_from<'a>(
3125        &self,
3126        module_name: impl AsPyStr<'a>,
3127        from_list: impl Into<PyObjectRef>,
3128        level: usize,
3129    ) -> PyResult {
3130        let module_name = module_name.as_pystr(&self.ctx);
3131        self.import_inner(module_name, from_list.into(), level)
3132    }
3133
3134    /// Look up `name` in the current frame's builtins (`_PyEval_GetBuiltin`).
3135    /// A missing key becomes AttributeError with that name.
3136    pub fn eval_get_builtin(&self, name: &'static PyStrInterned) -> PyResult {
3137        let builtins =
3138            crate::frame::current_builtins().unwrap_or_else(|| self.builtins.dict().into());
3139        if let Some(dict) = builtins.downcast_ref::<PyDict>() {
3140            match dict.get_item_opt(name, self)? {
3141                Some(value) => Ok(value),
3142                None => Err(self.new_attribute_error(name.to_string())),
3143            }
3144        } else {
3145            match builtins.get_item(name, self) {
3146                Ok(value) => Ok(value),
3147                Err(e) if e.fast_isinstance(self.ctx.exceptions.key_error) => {
3148                    Err(self.new_attribute_error(name.to_string()))
3149                }
3150                Err(e) => Err(e),
3151            }
3152        }
3153    }
3154
3155    fn import_inner(&self, module: &Py<PyStr>, from_list: PyObjectRef, level: usize) -> PyResult {
3156        let builtins =
3157            crate::frame::current_builtins().unwrap_or_else(|| self.builtins.dict().into());
3158        // The module-cache fast path assumes interpreter builtins. A frame
3159        // whose f_builtins is a custom mapping (eval/exec) must go through
3160        // that mapping's __import__. None and an empty tuple are both an
3161        // empty from-list (`import name`).
3162        let fromlist_empty = self.is_none(&from_list)
3163            || from_list
3164                .downcast_ref::<PyTuple>()
3165                .is_some_and(|tuple| tuple.as_slice().is_empty());
3166        if level == 0
3167            && fromlist_empty
3168            && builtins.is(self.builtins.dict().as_object())
3169            && let Some(cached) = self.try_import_cached(module)?
3170        {
3171            return Ok(cached);
3172        }
3173
3174        let import_func = if let Some(dict) = builtins.downcast_ref::<PyDict>() {
3175            match dict.get_item_opt(identifier!(self, __import__), self)? {
3176                Some(func) => func,
3177                None => {
3178                    return Err(self.new_import_error("__import__ not found", module.to_owned()));
3179                }
3180            }
3181        } else {
3182            match builtins.get_item(identifier!(self, __import__), self) {
3183                Ok(func) => func,
3184                Err(e) if e.fast_isinstance(self.ctx.exceptions.key_error) => {
3185                    return Err(self.new_import_error("__import__ not found", module.to_owned()));
3186                }
3187                Err(e) => return Err(e),
3188            }
3189        };
3190
3191        let (locals, globals) = if let Some(globals) = crate::frame::current_globals() {
3192            // Locals fallback: use the heavy frame if available, otherwise
3193            // use globals as locals (light frame locals are on the data stack).
3194            let locals_mapping = self.current_frame().map_or_else(
3195                || ArgMapping::from_dict_exact(globals.clone()),
3196                |f| f.iframe().locals.clone_mapping(self),
3197            );
3198            (Some(locals_mapping), Some(globals))
3199        } else {
3200            (None, None)
3201        };
3202        import_func
3203            .call((module.to_owned(), globals, locals, from_list, level), self)
3204            .inspect_err(|exc| import::remove_importlib_frames(self, exc))
3205    }
3206
3207    /// Fast path equivalent to CPython's `PyImport_ImportModuleLevelObject`
3208    /// cache hit: for a plain absolute import with no from-list, if
3209    /// `builtins.__import__` is still the original import function (i.e.
3210    /// nobody has monkey-patched it) and the module -- or, for a dotted
3211    /// name, its top-level package -- is already present and fully
3212    /// initialized in `sys.modules`, hand it back directly instead of going
3213    /// through `__import__`'s `FuncArgs`/`ImportArgs::from_args` dispatch
3214    /// and `import_module_level`. Returns `Ok(None)` whenever the slow path
3215    /// needs to run instead (uncached, initializing, or `__import__`
3216    /// overridden), never an error for those cases.
3217    fn try_import_cached(&self, module: &Py<PyStr>) -> PyResult<Option<PyObjectRef>> {
3218        let current_import = self
3219            .builtins
3220            .get_attr(identifier!(self, __import__), self)
3221            .map_err(|_| self.new_import_error("__import__ not found", module.to_owned()))?;
3222        if !current_import.is(&self.import_func) {
3223            // `builtins.__import__` was replaced by user code; must go
3224            // through it so overrides (test_import, test_importlib,
3225            // test_builtin) still take effect.
3226            return Ok(None);
3227        }
3228
3229        // Surrogate-containing names can't be looked up as a `&str`; let the
3230        // slow path (which keys `sys.modules` with the `PyStr` itself) handle it.
3231        let Some(name_str) = module.to_str() else {
3232            return Ok(None);
3233        };
3234        let sys_modules = self.sys_module.get_attr("modules", self)?;
3235        let Ok(found) = sys_modules.get_item(name_str, self) else {
3236            return Ok(None);
3237        };
3238        if self.is_none(&found) || import::is_module_initializing(&found, self)? {
3239            return Ok(None);
3240        }
3241
3242        let Some(dot) = name_str.find('.') else {
3243            return Ok(Some(found));
3244        };
3245        // Dotted name with an empty from-list: like CPython, the top-level
3246        // package is what gets returned (and bound by `import a.b.c`), not
3247        // the submodule itself.
3248        let top_name = &name_str[..dot];
3249        match sys_modules.get_item(top_name, self) {
3250            Ok(top) if !self.is_none(&top) => Ok(Some(top)),
3251            _ => Ok(None),
3252        }
3253    }
3254
3255    pub fn extract_elements_with<T, F>(&self, value: &PyObject, func: F) -> PyResult<Vec<T>>
3256    where
3257        F: Fn(PyObjectRef) -> PyResult<T>,
3258    {
3259        self.extract_elements_inner(value, LengthHint::Unasked, func)
3260    }
3261
3262    /// [`Self::extract_elements_with`] for a caller that asks the iterable
3263    /// itself how much room to take, the way `list_extend()` does. `held`
3264    /// answers how many elements the caller already has, and is read after the
3265    /// iterable has been asked, since asking runs its code.
3266    pub fn extract_elements_sized<T, F>(
3267        &self,
3268        value: &PyObject,
3269        held: &dyn Fn() -> usize,
3270        func: F,
3271    ) -> PyResult<Vec<T>>
3272    where
3273        F: Fn(PyObjectRef) -> PyResult<T>,
3274    {
3275        self.extract_elements_inner(value, LengthHint::Iterable(held), func)
3276    }
3277
3278    fn extract_elements_inner<T, F>(
3279        &self,
3280        value: &PyObject,
3281        hint: LengthHint<'_>,
3282        func: F,
3283    ) -> PyResult<Vec<T>>
3284    where
3285        F: Fn(PyObjectRef) -> PyResult<T>,
3286    {
3287        // A count known up front is taken in one go. Collecting into a
3288        // `Result` instead would drop it: the adapter that carries the error
3289        // may stop early, so it reports no lower bound and the vector grows a
3290        // step at a time.
3291        fn map_known_len<T, R>(
3292            items: impl ExactSizeIterator<Item = T>,
3293            func: impl Fn(T) -> PyResult<R>,
3294        ) -> PyResult<Vec<R>> {
3295            let mut results = Vec::with_capacity(items.len());
3296            for item in items {
3297                results.push(func(item)?);
3298            }
3299            Ok(results)
3300        }
3301
3302        // Type-specific fast paths corresponding to _list_extend() in CPython
3303        // Objects/listobject.c. Each branch takes an atomic snapshot to avoid
3304        // race conditions from concurrent mutation (no GIL).
3305        let cls = value.class();
3306        let slice = if cls.is(self.ctx.types.tuple_type) {
3307            value.downcast_ref::<PyTuple>().unwrap().as_slice()
3308        } else if cls.is(self.ctx.types.list_type) {
3309            // The list is re-read on every step, the way map_iterable_object()
3310            // does it: func() runs Python, which can mutate or even clear the
3311            // same list, and a borrow held across that call deadlocks it. Its
3312            // length at the start is only how much room to take, not how far
3313            // the loop runs.
3314            let list = value.downcast_ref::<PyList>().unwrap();
3315            let mut results = Vec::with_capacity(list.borrow_vec().len());
3316            let mut i = 0;
3317            loop {
3318                let elem = {
3319                    let elements = list.borrow_vec();
3320                    let Some(elem) = elements.get(i) else {
3321                        break;
3322                    };
3323                    elem.clone()
3324                    // free the lock
3325                };
3326                results.push(func(elem)?);
3327                i += 1;
3328            }
3329            return Ok(results);
3330        } else if cls.is(self.ctx.types.set_type) {
3331            let keys = value.downcast_ref::<PySet>().unwrap().elements();
3332            return map_known_len(keys.into_iter(), func);
3333        } else if cls.is(self.ctx.types.frozenset_type) {
3334            let keys = value.downcast_ref::<PyFrozenSet>().unwrap().elements();
3335            return map_known_len(keys.into_iter(), func);
3336        } else if cls.is(self.ctx.types.dict_type) {
3337            let keys = value.downcast_ref::<PyDict>().unwrap().keys_vec();
3338            return map_known_len(keys.into_iter(), func);
3339        } else if cls.is(self.ctx.types.dict_keys_type) {
3340            let keys = value.downcast_ref::<PyDictKeys>().unwrap().dict.keys_vec();
3341            return map_known_len(keys.into_iter(), func);
3342        } else if cls.is(self.ctx.types.dict_values_type) {
3343            let values = value
3344                .downcast_ref::<PyDictValues>()
3345                .unwrap()
3346                .dict
3347                .values_vec();
3348            return map_known_len(values.into_iter(), func);
3349        } else if cls.is(self.ctx.types.dict_items_type) {
3350            let items = value
3351                .downcast_ref::<PyDictItems>()
3352                .unwrap()
3353                .dict
3354                .items_vec();
3355            return map_known_len(items.into_iter(), |(k, v)| {
3356                func(self.ctx.new_tuple(vec![k, v]).into())
3357            });
3358        } else {
3359            return self.map_py_iter(value, hint, func);
3360        };
3361        map_known_len(slice.iter(), |obj| func(obj.clone()))
3362    }
3363
3364    /// [`Self::map_iterable_object`] for a caller that asks the object it was
3365    /// handed how long it is.
3366    pub fn map_iterable_object_sized<F, R>(
3367        &self,
3368        obj: &PyObject,
3369        f: F,
3370    ) -> PyResult<PyResult<Vec<R>>>
3371    where
3372        F: FnMut(PyObjectRef) -> PyResult<R>,
3373    {
3374        self.map_iterable_object_inner(obj, LengthHint::Iterable(&|| 0), f)
3375    }
3376
3377    pub fn map_iterable_object<F, R>(&self, obj: &PyObject, f: F) -> PyResult<PyResult<Vec<R>>>
3378    where
3379        F: FnMut(PyObjectRef) -> PyResult<R>,
3380    {
3381        self.map_iterable_object_inner(obj, LengthHint::Unasked, f)
3382    }
3383
3384    fn map_iterable_object_inner<F, R>(
3385        &self,
3386        obj: &PyObject,
3387        hint: LengthHint<'_>,
3388        mut f: F,
3389    ) -> PyResult<PyResult<Vec<R>>>
3390    where
3391        F: FnMut(PyObjectRef) -> PyResult<R>,
3392    {
3393        match_class!(match obj {
3394            ref l @ PyList => {
3395                let mut i: usize = 0;
3396                let mut results = Vec::with_capacity(l.borrow_vec().len());
3397                loop {
3398                    let elem = {
3399                        let elements = &*l.borrow_vec();
3400                        if i >= elements.len() {
3401                            results.shrink_to_fit();
3402                            return Ok(Ok(results));
3403                        }
3404                        elements[i].clone()
3405
3406                        // free the lock
3407                    };
3408                    match f(elem) {
3409                        Ok(result) => results.push(result),
3410                        Err(err) => return Ok(Err(err)),
3411                    }
3412                    i += 1;
3413                }
3414            }
3415            ref t @ PyTuple => Ok(t.as_slice().iter().cloned().map(f).collect()),
3416            // TODO: put internal iterable type
3417            obj => {
3418                Ok(self.map_py_iter(obj, hint, f))
3419            }
3420        })
3421    }
3422
3423    fn map_py_iter<F, R>(
3424        &self,
3425        value: &PyObject,
3426        hint: LengthHint<'_>,
3427        mut f: F,
3428    ) -> PyResult<Vec<R>>
3429    where
3430        F: FnMut(PyObjectRef) -> PyResult<R>,
3431    {
3432        let iter = value.to_owned().get_iter(self)?;
3433
3434        // Take the room the iterable asks for up front, for the callers that
3435        // do. Collecting into a `Result` drops the iterator's lower bound --
3436        // the adapter may stop early -- so without this the vector grows a step
3437        // at a time and an iterable claiming more elements than can be held is
3438        // found out by running out of memory rather than by saying so. An error
3439        // the ask answers with is the iterable's own and belongs to the caller
3440        // that made it; `length_hint_opt` already answers `None` for the
3441        // iterable that declines to guess.
3442        //
3443        // Nobody else asks, so what an object would have answered -- slowly, or
3444        // by raising -- costs the rest nothing.
3445        //
3446        // A hint that does not leave room for what is already held is one the
3447        // iterable cannot be telling the truth about, so it is passed over
3448        // rather than refused: if it was honest the loop runs out of memory on
3449        // its own, and if it lied there was nothing wrong to report. What is
3450        // held is counted now rather than before, since asking for the hint
3451        // runs code that can add to it or take from it.
3452        let mut results: Vec<R> = Vec::new();
3453        let mut cap = None;
3454        if let LengthHint::Iterable(held) = hint {
3455            cap = self.length_hint_opt(value.to_owned())?;
3456            if let Some(cap) = cap
3457                && held() <= (isize::MAX as usize) - cap
3458            {
3459                results
3460                    .try_reserve_exact(cap)
3461                    .map_err(|_| self.new_memory_error(""))?;
3462            }
3463        }
3464        for element in PyIterIter::new(self, iter.as_ref(), cap) {
3465            results.push(f(element?)?);
3466        }
3467        results.shrink_to_fit();
3468        Ok(results)
3469    }
3470
3471    pub fn get_attribute_opt<'a>(
3472        &self,
3473        obj: &PyObject,
3474        attr_name: impl AsPyStr<'a>,
3475    ) -> PyResult<Option<PyObjectRef>> {
3476        let attr_name = attr_name.as_pystr(&self.ctx);
3477        let getattro = obj.class().slots().getattro.load().unwrap();
3478        let result = if fn_addr(getattro) == fn_addr(PyBaseObject::getattro as GetattroFunc) {
3479            obj.generic_getattr_opt(attr_name, None, self)
3480        } else {
3481            obj.get_attr_inner(attr_name, self).map(Some)
3482        };
3483        match result {
3484            Ok(attr) => Ok(attr),
3485            Err(e) if e.fast_isinstance(self.ctx.exceptions.attribute_error) => Ok(None),
3486            Err(e) => Err(e),
3487        }
3488    }
3489
3490    pub fn set_attribute_error_context(
3491        &self,
3492        exc: &Py<PyBaseException>,
3493        obj: PyObjectRef,
3494        name: PyStrRef,
3495    ) {
3496        if exc.class().is(self.ctx.exceptions.attribute_error) {
3497            let exc = exc.as_object();
3498            // Check if this exception was already augmented
3499            let already_set = exc.get_attr("name", self).is_ok_and(|v| !self.is_none(&v));
3500            if already_set {
3501                return;
3502            }
3503            exc.set_attr("name", name, self).unwrap();
3504            exc.set_attr("obj", obj, self).unwrap();
3505        }
3506    }
3507
3508    // get_method should be used for internal access to magic methods (by-passing
3509    // the full getattribute look-up.
3510    pub fn get_method_or_type_error<F>(
3511        &self,
3512        obj: PyObjectRef,
3513        method_name: &'static PyStrInterned,
3514        err_msg: F,
3515    ) -> PyResult
3516    where
3517        F: FnOnce() -> String,
3518    {
3519        let method = obj
3520            .class()
3521            .get_attr(method_name)
3522            .ok_or_else(|| self.new_type_error(err_msg()))?;
3523        self.call_if_get_descriptor(&method, obj)
3524    }
3525
3526    // TODO: remove + transfer over to get_special_method
3527    pub(crate) fn get_method(
3528        &self,
3529        obj: PyObjectRef,
3530        method_name: &'static PyStrInterned,
3531    ) -> Option<PyResult> {
3532        let method = obj.get_class_attr(method_name)?;
3533        Some(self.call_if_get_descriptor(&method, obj))
3534    }
3535
3536    pub(crate) fn get_str_method(&self, obj: PyObjectRef, method_name: &str) -> Option<PyResult> {
3537        let method_name = self.ctx.interned_str(method_name)?;
3538        self.get_method(obj, method_name)
3539    }
3540
3541    /// Fast path for the bytecode loop: pending signals, QSBR, scheduled GC,
3542    /// finalization, and stop-the-world.
3543    ///
3544    /// `STOP_BIT` is a process-wide hint set when any interpreter asks a
3545    /// thread to park. Each interpreter's `start_the_world` clears that hint,
3546    /// even if another interpreter still has `stop_requested` threads, so the
3547    /// per-thread flag is checked first. Missing it lets a worker skip
3548    /// `check_signals` and never park, so `stop_the_world` waits forever.
3549    #[inline]
3550    pub(crate) fn eval_breaker_tripped(&self) -> bool {
3551        #[cfg(feature = "threading")]
3552        if thread::stop_requested_for_current_thread() || self.state.gc.collection_ready() {
3553            return true;
3554        }
3555        #[cfg(not(target_arch = "wasm32"))]
3556        {
3557            crate::signal::eval_breaker_pending()
3558        }
3559        #[cfg(target_arch = "wasm32")]
3560        {
3561            false
3562        }
3563    }
3564
3565    #[inline]
3566    /// Checks for triggered signals and calls the appropriate handlers. A no-op on
3567    /// platforms where signals are not supported.
3568    pub fn check_signals(&self) -> PyResult<()> {
3569        #[cfg(feature = "threading")]
3570        if self.state.finalizing.load(Ordering::Acquire)
3571            && stdlib::_thread::get_ident() != self.state.finalizing_thread_ident.load()
3572        {
3573            // `_PyThreadState_MustExit` → `_PyThreadState_HangThread`.
3574            // Do not return SystemExit: that would mark the handle done and
3575            // make `Thread.is_alive()` false for a daemon still forced off
3576            // during finalize.
3577            thread::hang_current_thread(&self.state);
3578        }
3579
3580        // Suspend this thread if stop-the-world is in progress
3581        #[cfg(feature = "threading")]
3582        thread::suspend_if_needed(&self.state);
3583
3584        // Pass a QSBR checkpoint if requested (deferred memory reclamation).
3585        #[cfg(feature = "threading")]
3586        if crate::signal::qsbr_bit_set() && thread::qsbr_break_requested() {
3587            thread::qsbr_checkpoint();
3588        }
3589
3590        #[cfg(not(target_arch = "wasm32"))]
3591        crate::signal::check_signals(self)?;
3592
3593        Ok(())
3594    }
3595
3596    /// Run an automatic collection scheduled by `maybe_collect`, if any.
3597    ///
3598    /// Called only from the bytecode-loop safepoint, where no interpreter
3599    /// locks are held, so the stop-the-world it performs cannot deadlock
3600    /// against a thread blocked on a lock this thread would otherwise hold.
3601    #[cfg(feature = "threading")]
3602    pub(crate) fn run_scheduled_gc(&self) {
3603        if self.state.gc.collection_ready() {
3604            self.state.gc.collect(0);
3605        }
3606    }
3607
3608    /// Push a new exc_info slot (for generator/coroutine resume).
3609    ///
3610    /// `topmost_exception()` skips `None` slots when searching for the
3611    /// visible exception, so pushing `None` can never change what it
3612    /// returns -- the thread-local mirror update (TLS lookup + atomic ref
3613    /// swap) is safe to skip in that common case (e.g. resuming a
3614    /// generator with no saved exception state).
3615    pub(crate) fn push_exception(&self, exc: Option<PyBaseExceptionRef>) {
3616        #[cfg(feature = "threading")]
3617        let may_change_top = exc.is_some();
3618        self.exceptions.borrow_mut().stack.push(exc);
3619        #[cfg(feature = "threading")]
3620        if may_change_top {
3621            thread::update_thread_exception(self.topmost_exception());
3622        }
3623    }
3624
3625    /// Pop the topmost exc_info slot (generator/coroutine yield/return).
3626    ///
3627    /// Symmetric with `push_exception`: popping a `None` slot cannot change
3628    /// what `topmost_exception()` reports (it was already skipped while
3629    /// searching down the stack), so the thread-local mirror update is
3630    /// skipped in that case.
3631    pub(crate) fn pop_exception(&self) -> Option<PyBaseExceptionRef> {
3632        let exc = self
3633            .exceptions
3634            .borrow_mut()
3635            .stack
3636            .pop()
3637            .expect("pop_exception() without nested exc stack");
3638        #[cfg(feature = "threading")]
3639        if exc.is_some() {
3640            thread::update_thread_exception(self.topmost_exception());
3641        }
3642        exc
3643    }
3644
3645    pub fn current_exception(&self) -> Option<PyBaseExceptionRef> {
3646        self.exceptions.borrow().stack.last().cloned().flatten()
3647    }
3648
3649    /// Set the current exc_info slot value (PUSH_EXC_INFO / POP_EXCEPT).
3650    pub fn set_exception(&self, exc: Option<PyBaseExceptionRef>) {
3651        // don't be holding the RefCell guard while __del__ is called
3652        let mut excs = self.exceptions.borrow_mut();
3653        debug_assert!(
3654            !excs.stack.is_empty(),
3655            "set_exception called with empty exception stack"
3656        );
3657        if let Some(top) = excs.stack.last_mut() {
3658            let prev = core::mem::replace(top, exc);
3659            drop(excs);
3660            drop(prev);
3661        } else {
3662            excs.stack.push(exc);
3663            drop(excs);
3664        }
3665        #[cfg(feature = "threading")]
3666        thread::update_thread_exception(self.topmost_exception());
3667    }
3668
3669    /// Restore an exc_info slot value saved by `with_frame`, skipping the
3670    /// store when the slot is unchanged. `saved` is a strong reference taken
3671    /// at save time, so the object it points to cannot have been freed and
3672    /// its address reused while the frame ran; pointer identity therefore
3673    /// proves the slot still holds the same value and both the store and the
3674    /// thread-exception mirror update would be no-ops.
3675    pub(crate) fn restore_exception(&self, saved: Option<PyBaseExceptionRef>) {
3676        let excs = self.exceptions.borrow();
3677        let unchanged = match (excs.stack.last(), &saved) {
3678            (Some(Some(current)), Some(saved)) => current.is(saved),
3679            (Some(None), None) => true,
3680            _ => false,
3681        };
3682        drop(excs);
3683        if !unchanged {
3684            self.set_exception(saved);
3685        }
3686    }
3687
3688    pub fn take_raised_exception(&self) -> Option<PyBaseExceptionRef> {
3689        let mut excs = self.exceptions.borrow_mut();
3690        if let Some(top) = excs.stack.last_mut() {
3691            let exc = top.take();
3692            drop(excs);
3693            #[cfg(feature = "threading")]
3694            thread::update_thread_exception(self.topmost_exception());
3695            exc
3696        } else {
3697            None
3698        }
3699    }
3700
3701    /// `_PyErr_ChainStackItem`: if the current `exc_info` slot is occupied,
3702    /// set that handled exception as `__context__` of `exception`. A vacant
3703    /// current slot must not walk to an outer frame's exception.
3704    pub(crate) fn chain_stack_item(&self, exception: &Py<PyBaseException>) {
3705        if self.current_exception().is_some() {
3706            self.contextualize_exception(exception);
3707        }
3708    }
3709
3710    pub(crate) fn contextualize_exception(&self, exception: &Py<PyBaseException>) {
3711        if let Some(context_exc) = self.topmost_exception()
3712            && !context_exc.is(exception)
3713        {
3714            // Traverse the context chain to find `exception` and break cycles
3715            // Uses Floyd's cycle detection: o moves every step, slow_o every other step
3716            let mut o = context_exc.clone();
3717            let mut slow_o = context_exc.clone();
3718            let mut slow_update_toggle = false;
3719            while let Some(context) = o.__context__() {
3720                if context.is(exception) {
3721                    o.set_context(None);
3722                    break;
3723                }
3724                o = context;
3725                if o.is(&slow_o) {
3726                    // Pre-existing cycle detected - all exceptions on the path were visited
3727                    break;
3728                }
3729                if slow_update_toggle && let Some(slow_context) = slow_o.__context__() {
3730                    slow_o = slow_context;
3731                }
3732                slow_update_toggle = !slow_update_toggle;
3733            }
3734            exception.set_context(Some(context_exc))
3735        }
3736    }
3737
3738    pub(crate) fn topmost_exception(&self) -> Option<PyBaseExceptionRef> {
3739        let excs = self.exceptions.borrow();
3740        excs.stack.iter().rev().find_map(|e| e.clone())
3741    }
3742
3743    pub fn handle_exit_exception(&self, exc: PyBaseExceptionRef) -> u32 {
3744        if exc.fast_isinstance(self.ctx.exceptions.system_exit) {
3745            let code = exc
3746                .as_object()
3747                .get_attr("code", self)
3748                .unwrap_or_else(|_| exc.as_object().to_owned());
3749            let msg = match_class!(match code {
3750                ref i @ PyInt => {
3751                    use num_traits::cast::ToPrimitive;
3752                    // Try u32 first, then i32 (for negative values), else -1 for overflow
3753                    let code = i
3754                        .as_bigint()
3755                        .to_u32()
3756                        .or_else(|| i.as_bigint().to_i32().map(|v| v as u32))
3757                        .unwrap_or(-1i32 as u32);
3758                    return code;
3759                }
3760                code => {
3761                    if self.is_none(&code) {
3762                        return 0;
3763                    }
3764                    code.str(self).ok()
3765                }
3766            });
3767            if let Some(msg) = msg {
3768                // Write using Python's write() to use stderr's error handler (backslashreplace)
3769                if let Ok(stderr) = stdlib::sys::get_stderr(self) {
3770                    let _ = self.call_method(&stderr, "write", (msg,));
3771                    let _ = self.call_method(&stderr, "write", ("\n",));
3772                }
3773            }
3774            1
3775        } else if exc.fast_isinstance(self.ctx.exceptions.keyboard_interrupt) {
3776            self.print_exception(&exc);
3777            cfg_select! {
3778                unix => {
3779                    if crate::host_env::signal::set_sigint_default_onstack().is_ok() {
3780                        self.flush_std();
3781                        crate::host_env::signal::send_sigint_to_self()
3782                            .expect("Expect to be killed.");
3783                    }
3784
3785                    (libc::SIGINT as u32) + 128
3786                }
3787                // STATUS_CONTROL_C_EXIT - same as CPython
3788                windows => 0xC000013A,
3789                _ => 1,
3790            }
3791        } else {
3792            self.print_exception(&exc);
3793            1
3794        }
3795    }
3796
3797    #[doc(hidden)]
3798    pub fn __module_set_attr(
3799        &self,
3800        module: &Py<PyModule>,
3801        attr_name: &'static PyStrInterned,
3802        attr_value: impl Into<PyObjectRef>,
3803    ) -> PyResult<()> {
3804        let val = attr_value.into();
3805        module
3806            .as_object()
3807            .generic_setattr(attr_name, PySetterValue::Assign(val), self)
3808    }
3809
3810    pub fn insert_sys_path(&self, obj: PyObjectRef) -> PyResult<()> {
3811        let sys_path = self.sys_module.get_attr("path", self).unwrap();
3812        self.call_method(&sys_path, "insert", (0, obj))?;
3813        Ok(())
3814    }
3815
3816    pub fn run_module(&self, module: &str) -> PyResult<()> {
3817        let runpy = self.import("runpy", 0)?;
3818        let run_module_as_main = runpy.get_attr("_run_module_as_main", self)?;
3819        run_module_as_main.call((module,), self)?;
3820        Ok(())
3821    }
3822
3823    pub fn fs_encoding(&self) -> &'static PyStrInterned {
3824        identifier!(self, utf_8)
3825    }
3826
3827    pub fn fs_encode_errors(&self) -> &'static PyUtf8StrInterned {
3828        if cfg!(windows) {
3829            identifier_utf8!(self, surrogatepass)
3830        } else {
3831            identifier_utf8!(self, surrogateescape)
3832        }
3833    }
3834
3835    pub fn fsdecode(&self, s: impl Into<OsString>) -> PyStrRef {
3836        match s.into().into_string() {
3837            Ok(s) => self.ctx.new_str(s),
3838            Err(s) => {
3839                let bytes = self.ctx.new_bytes(s.into_encoded_bytes());
3840                let errors = self.fs_encode_errors().to_owned();
3841                let res = self.state.codec_registry.decode_text(
3842                    bytes.into(),
3843                    "utf-8",
3844                    Some(errors),
3845                    self,
3846                );
3847                self.expect_pyresult(res, "fsdecode should be lossless and never fail")
3848            }
3849        }
3850    }
3851
3852    pub fn fsencode<'a>(&self, s: &'a Py<PyStr>) -> PyResult<Cow<'a, OsStr>> {
3853        if cfg!(windows) || s.is_utf8() {
3854            // XXX: this is sketchy on windows; it's not guaranteed that the
3855            //      OsStr encoding will always be compatible with WTF-8.
3856            let s = unsafe { OsStr::from_encoded_bytes_unchecked(s.as_bytes()) };
3857            return Ok(Cow::Borrowed(s));
3858        }
3859        let errors = self.fs_encode_errors().to_owned();
3860        let bytes = self
3861            .state
3862            .codec_registry
3863            .encode_text(s.to_owned(), "utf-8", Some(errors), self)?
3864            .as_bytes()
3865            .to_vec();
3866        // XXX: this is sketchy on windows; it's not guaranteed that the
3867        //      OsStr encoding will always be compatible with WTF-8.
3868        let s = unsafe { OsString::from_encoded_bytes_unchecked(bytes) };
3869        Ok(Cow::Owned(s))
3870    }
3871}
3872
3873impl AsRef<Context> for VirtualMachine {
3874    fn as_ref(&self) -> &Context {
3875        &self.ctx
3876    }
3877}
3878
3879/// Resolve frozen module alias to its original name.
3880/// Returns the original module name if an alias exists, otherwise returns the input name.
3881#[must_use]
3882pub fn resolve_frozen_alias(name: &str) -> &str {
3883    match name {
3884        "_frozen_importlib" => "importlib._bootstrap",
3885        "_frozen_importlib_external" => "importlib._bootstrap_external",
3886        "encodings_ascii" => "encodings.ascii",
3887        "encodings_utf_8" => "encodings.utf_8",
3888        "encodings_latin_1" => "encodings.latin_1",
3889        "__hello_alias__" | "__phello_alias__" | "__phello_alias__.spam" => "__hello__",
3890        "__phello__.__init__" => "<__phello__",
3891        "__phello__.ham.__init__" => "<__phello__.ham",
3892        "__hello_only__" => "",
3893        _ => name,
3894    }
3895}
3896
3897#[cfg(test)]
3898mod tests {
3899    use super::*;
3900
3901    #[test]
3902    fn nested_frozen() {
3903        use rustpython_vm as vm;
3904
3905        vm::Interpreter::builder(Default::default())
3906            .add_frozen_modules(rustpython_vm::py_freeze!(
3907                dir = "../../../../extra_tests/snippets"
3908            ))
3909            .build()
3910            .enter(|vm| {
3911                let scope = vm.new_scope_with_builtins();
3912
3913                let source = "from dir_module.dir_module_inner import value2";
3914                let code_obj = vm
3915                    .compile(source, vm::compiler::Mode::Exec, "<embedded>")
3916                    .map_err(|err| err.into_pyexception(vm, Some(source)))
3917                    .unwrap();
3918
3919                if let Err(e) = vm.run_code_obj(code_obj, scope) {
3920                    vm.print_exception(&e);
3921                    panic!();
3922                }
3923            })
3924    }
3925
3926    #[test]
3927    fn frozen_origname_matches() {
3928        use rustpython_vm as vm;
3929
3930        vm::Interpreter::builder(Default::default())
3931            .build()
3932            .enter(|vm| {
3933                let check = |name, expected| {
3934                    let module = import::import_frozen(vm, name).unwrap();
3935                    let origname: PyStrRef = module
3936                        .get_attr("__origname__", vm)
3937                        .unwrap()
3938                        .try_into_value(vm)
3939                        .unwrap();
3940                    assert_eq!(origname.as_wtf8(), expected);
3941                };
3942
3943                check("_frozen_importlib", "importlib._bootstrap");
3944                check(
3945                    "_frozen_importlib_external",
3946                    "importlib._bootstrap_external",
3947                );
3948            });
3949    }
3950}