zsh/extensions/vm_pool.rs
1//! Per-thread pool of recyclable fusevm `VM`s — a Rust-only optimization
2//! that stops zshrs from rebuilding a VM (and re-registering its entire
3//! builtin table) on every function call, command substitution, pipeline
4//! stage, and subshell.
5//!
6//! # Why
7//!
8//! Handing a compiled `Chunk` to a [`fusevm::VM`] used to mean
9//! `VM::new(chunk)` + `register_builtins(&mut vm)` at each of ~15 execution
10//! sites. `register_builtins` installs the ~hundreds of `fn`-pointer
11//! handlers that make up the shell builtin table — identical for every VM,
12//! so redoing it per run was pure waste (the #2 hot spot after option
13//! lookups in a function-call profile). Worse, throwing the VM away each
14//! call meant the tracing JIT never stayed warm, so hot numeric loops never
15//! got compiled.
16//!
17//! [`fusevm::VM::reset`] clears execution state (stack, frames, globals, ip,
18//! status) but PRESERVES the `builtin_table`, shell host, and JIT wiring. So
19//! a VM built once can be recycled for any later chunk with only a `reset` —
20//! no re-registration — and its JIT trace state accumulates across runs.
21//!
22//! # Usage
23//!
24//! [`acquire`] returns a [`PooledVm`] RAII guard that derefs to the VM. It
25//! pops a recycled VM (already registered — just `reset`) or builds and
26//! registers a fresh one. On drop the VM returns to the pool. Runs nest
27//! (a function body spawns a command substitution, …), so multiple guards
28//! can be live at once; the pool grows to the maximum nesting depth.
29//! Thread-local because a fusevm `VM` is not `Sync`.
30
31use std::cell::RefCell;
32use std::ops::{Deref, DerefMut};
33
34thread_local! {
35 /// Released, ready-to-reuse VMs for this thread. Each retains its
36 /// registered builtin table and host from first construction.
37 static POOL: RefCell<Vec<fusevm::VM>> = const { RefCell::new(Vec::new()) };
38}
39
40/// Cap on retained VMs per thread. Deep recursion can hold many guards at
41/// once; we only bound what we *keep*, so a pathological one-off recursion
42/// doesn't pin a large fleet forever. Excess returns are dropped.
43const MAX_POOLED: usize = 64;
44
45/// RAII handle to a pooled VM. Derefs to [`fusevm::VM`]; returns the VM to
46/// the pool on drop.
47pub struct PooledVm {
48 vm: Option<fusevm::VM>,
49}
50
51/// Acquire a call-ready VM for `chunk`. Recycles a pooled VM (builtins
52/// already registered — just `reset`) or builds and registers a fresh one.
53pub fn acquire(chunk: fusevm::Chunk) -> PooledVm {
54 let vm = match POOL.with(|p| p.borrow_mut().pop()) {
55 Some(mut recycled) => {
56 recycled.reset(chunk);
57 recycled
58 }
59 None => {
60 let mut fresh = fusevm::VM::new(chunk);
61 crate::fusevm_bridge::register_builtins(&mut fresh);
62 fresh
63 }
64 };
65 PooledVm { vm: Some(vm) }
66}
67
68impl Deref for PooledVm {
69 type Target = fusevm::VM;
70 #[inline]
71 fn deref(&self) -> &fusevm::VM {
72 // Present for the whole lifetime; only `take`n in Drop.
73 self.vm.as_ref().expect("PooledVm used after drop")
74 }
75}
76
77impl DerefMut for PooledVm {
78 #[inline]
79 fn deref_mut(&mut self) -> &mut fusevm::VM {
80 self.vm.as_mut().expect("PooledVm used after drop")
81 }
82}
83
84impl Drop for PooledVm {
85 fn drop(&mut self) {
86 if let Some(vm) = self.vm.take() {
87 POOL.with(|p| {
88 let mut pool = p.borrow_mut();
89 if pool.len() < MAX_POOLED {
90 pool.push(vm);
91 }
92 });
93 }
94 }
95}