Skip to main content

shape_vm/executor/vm_impl/
init.rs

1use super::super::*;
2use shape_value::NativeKind;
3
4impl VirtualMachine {
5    pub fn new(config: VMConfig) -> Self {
6        let debugger = if config.debug_mode {
7            Some(VMDebugger::new())
8        } else {
9            None
10        };
11
12        let gc = GarbageCollector::new(config.gc_config.clone());
13
14        // Initialize builtin schema IDs (overwritten from loaded bytecode registry
15        // in `load_program`).
16        let (registry, builtin_schemas) =
17            shape_runtime::type_schema::TypeSchemaRegistry::with_stdlib_types_and_builtin_ids();
18
19        let mut program = BytecodeProgram::new();
20        program.type_schema_registry = registry;
21
22        let mut vm = Self {
23            config,
24            program,
25            ip: 0,
26            // ADR-006 §2.7.7 / Q9: typed VM stack is `Vec<u64>` data plus
27            // parallel `Vec<NativeKind>` kind track. Slots above `sp` are
28            // pre-allocated dead space; their kind is `Bool` by convention
29            // (Drop is a no-op for Bool, so dead bits never leak refcount).
30            stack: vec![0u64; crate::constants::DEFAULT_STACK_CAPACITY],
31            kinds: vec![NativeKind::Bool; crate::constants::DEFAULT_STACK_CAPACITY],
32            sp: 0,
33            // ADR-006 §2.7.8 / Q10: module-binding storage carries a
34            // parallel `NativeKind` track in lockstep with the raw bits.
35            // Both vecs start empty; the resize-pad helper grows them
36            // together (see `VirtualMachine::module_binding_pad_to_kinded`).
37            module_bindings: Vec::new(),
38            module_binding_kinds: Vec::new(),
39            shared_module_bindings: std::collections::HashSet::new(),
40            call_stack: Vec::with_capacity(crate::constants::DEFAULT_CALL_STACK_CAPACITY),
41            loop_stack: Vec::new(),
42            timeframe_stack: Vec::new(),
43            debugger,
44            gc,
45            instruction_count: 0,
46            exception_handlers: Vec::new(),
47            builtin_schemas,
48            last_error_line: None,
49            last_error_file: None,
50            last_uncaught_exception: None,
51            module_init_done: false,
52            output_buffer: None,
53            module_registry: shape_runtime::module_exports::ModuleExportRegistry::new(),
54            module_fn_table: Vec::new(),
55            function_name_index: HashMap::new(),
56            extension_methods: HashMap::new(),
57            merged_schema_cache: HashMap::new(),
58            interrupt: Arc::new(AtomicU8::new(0)),
59            future_id_counter: 0,
60            async_scope_stack: Vec::new(),
61            task_scheduler: task_scheduler::TaskScheduler::new(),
62            foreign_fn_handles: Vec::new(),
63            function_hashes: Vec::new(),
64            function_hash_raw: Vec::new(),
65            function_id_by_hash: HashMap::new(),
66            function_entry_points: Vec::new(),
67            program_entry_ip: 0,
68            resource_usage: None,
69            time_travel: None,
70            #[cfg(feature = "gc")]
71            gc_heap: None,
72            #[cfg(feature = "jit")]
73            jit_compiled: false,
74            #[cfg(feature = "jit")]
75            jit_dispatch_table: std::collections::HashMap::new(),
76            tier_manager: None,
77            pending_resume: None,
78            pending_frame_resume: None,
79            metrics: None,
80            feedback_vectors: Vec::new(),
81            megamorphic_cache: crate::megamorphic_cache::MegamorphicCache::new(),
82            shape_table: shape_value::ShapeTableHandle::new(),
83        };
84
85        // VM-native stdlib modules are always available, independent of
86        // user-installed extension plugins.
87        // VM-side modules (state, transport, remote) live in shape-vm.
88        vm.register_stdlib_module(state_builtins::create_state_module());
89        vm.register_stdlib_module(create_transport_module_exports());
90        vm.register_stdlib_module(create_remote_module_exports());
91        // shape-runtime canonical registry covers all non-VM modules.
92        for module in shape_runtime::stdlib::all_stdlib_modules() {
93            vm.register_stdlib_module(module);
94        }
95
96        // Initialise metrics collector when requested.
97        if vm.config.metrics_enabled {
98            vm.metrics = Some(crate::metrics::VmMetrics::new());
99        }
100
101        // Auto-initialise the tracing GC heap when requested.
102        #[cfg(feature = "gc")]
103        if vm.config.use_tracing_gc {
104            vm.init_gc_heap();
105        }
106
107        vm
108    }
109
110    /// Attach resource limits to this VM. The dispatch loop will enforce them.
111    pub fn with_resource_limits(mut self, limits: crate::resource_limits::ResourceLimits) -> Self {
112        let mut usage = crate::resource_limits::ResourceUsage::new(limits);
113        usage.start();
114        self.resource_usage = Some(usage);
115        self
116    }
117
118    /// Initialize the GC heap for this VM instance (gc feature only).
119    ///
120    /// Sets up the GcHeap and registers it as the thread-local heap so
121    /// ValueWord::heap_box() and ValueSlot::from_heap() can allocate through it.
122    /// Also configures the GC threshold from the VM's GCConfig.
123    #[cfg(feature = "gc")]
124    pub fn init_gc_heap(&mut self) {
125        let heap = shape_gc::GcHeap::new();
126        self.gc_heap = Some(heap);
127        // Set thread-local GC heap pointer AFTER the move into self.gc_heap
128        // so the pointer remains valid for the VM's lifetime.
129        if let Some(ref mut heap) = self.gc_heap {
130            unsafe { shape_gc::set_thread_gc_heap(heap as *mut _) };
131        }
132    }
133
134    /// Set the interrupt flag (shared with Ctrl+C handler).
135    pub fn set_interrupt(&mut self, flag: Arc<AtomicU8>) {
136        self.interrupt = flag;
137    }
138
139    /// Enable time-travel debugging with the given capture mode and history limit.
140    pub fn enable_time_travel(&mut self, mode: time_travel::CaptureMode, max_entries: usize) {
141        self.time_travel = Some(time_travel::TimeTravel::new(mode, max_entries));
142    }
143
144    /// Disable time-travel debugging and discard history.
145    pub fn disable_time_travel(&mut self) {
146        self.time_travel = None;
147    }
148
149    /// Mark this VM as having been JIT-compiled selectively.
150    ///
151    /// Call this after using `shape_jit::JITCompiler::compile_program_selective`
152    /// externally to JIT-compile functions that benefit from native execution.
153    /// The caller is responsible for performing the compilation via `shape-jit`
154    /// (which depends on `shape-vm`, so the dependency flows one way).
155    ///
156    /// # Example (in a crate that depends on both `shape-vm` and `shape-jit`):
157    ///
158    /// ```ignore
159    /// let mut compiler = shape_jit::JITCompiler::new()?;
160    /// let (_jitted_fn, _table) = compiler.compile_program_selective("main", vm.program())?;
161    /// vm.set_jit_compiled();
162    /// ```
163    #[cfg(feature = "jit")]
164    pub fn set_jit_compiled(&mut self) {
165        self.jit_compiled = true;
166    }
167
168    /// Returns whether selective JIT compilation has been applied to this VM.
169    #[cfg(feature = "jit")]
170    pub fn is_jit_compiled(&self) -> bool {
171        self.jit_compiled
172    }
173
174    /// Register a JIT-compiled function in the dispatch table.
175    ///
176    /// After registration, calls to this function_id will attempt JIT dispatch
177    /// before falling back to bytecode interpretation.
178    #[cfg(feature = "jit")]
179    pub fn register_jit_function(&mut self, function_id: u16, ptr: JitFnPtr) {
180        self.jit_dispatch_table.insert(function_id, ptr);
181        self.jit_compiled = true;
182    }
183
184    /// Get the JIT dispatch table for inspection or external use.
185    #[cfg(feature = "jit")]
186    pub fn jit_dispatch_table(&self) -> &std::collections::HashMap<u16, JitFnPtr> {
187        &self.jit_dispatch_table
188    }
189
190    /// Enable tiered compilation for this VM.
191    ///
192    /// Must be called after `load_program()` so the function count is known.
193    /// The caller is responsible for spawning a background compilation thread
194    /// that reads from the request channel and sends results back.
195    ///
196    /// Returns `(request_rx, result_tx)` that the background thread should use.
197    pub fn enable_tiered_compilation(
198        &mut self,
199    ) -> (
200        std::sync::mpsc::Receiver<crate::tier::CompilationRequest>,
201        std::sync::mpsc::Sender<crate::tier::CompilationResult>,
202    ) {
203        let function_count = self.program.functions.len();
204        let mut mgr = crate::tier::TierManager::new(function_count, true);
205
206        let (req_tx, req_rx) = std::sync::mpsc::channel();
207        let (res_tx, res_rx) = std::sync::mpsc::channel();
208        mgr.set_channels(req_tx, res_rx);
209
210        self.tier_manager = Some(mgr);
211        (req_rx, res_tx)
212    }
213
214    /// Get a reference to the tier manager, if tiered compilation is enabled.
215    pub fn tier_manager(&self) -> Option<&crate::tier::TierManager> {
216        self.tier_manager.as_ref()
217    }
218
219    /// Poll the tier manager for completed background JIT compilations.
220    ///
221    /// Completed compilations are applied by `TierManager::poll_completions()`,
222    /// which updates its internal `native_code_table`. The JIT dispatch fast
223    /// path in `op_call` reads from `tier_mgr.get_native_code()`.
224    ///
225    /// Called every 1024 instructions from the dispatch loop (same cadence as
226    /// interrupt and GC safepoint checks).
227    pub(crate) fn poll_tier_completions(&mut self) {
228        if let Some(ref mut tier_mgr) = self.tier_manager {
229            // poll_completions() reads from the compilation_rx channel and
230            // updates native_code_table internally.
231            let completions = tier_mgr.poll_completions();
232
233            // Record tier transition events in metrics if enabled.
234            if let Some(ref mut metrics) = self.metrics {
235                for result in &completions {
236                    if result.native_code.is_some() {
237                        let from_tier = match result.compiled_tier {
238                            crate::tier::Tier::BaselineJit => 0,   // was Interpreted
239                            crate::tier::Tier::OptimizingJit => 1, // was BaselineJit
240                            crate::tier::Tier::Interpreted => continue,
241                        };
242                        let to_tier = match result.compiled_tier {
243                            crate::tier::Tier::BaselineJit => 1,
244                            crate::tier::Tier::OptimizingJit => 2,
245                            crate::tier::Tier::Interpreted => continue,
246                        };
247                        metrics.record_tier_event(crate::metrics::TierEvent {
248                            function_id: result.function_id,
249                            from_tier,
250                            to_tier,
251                            call_count: tier_mgr.get_call_count(result.function_id),
252                            timestamp_us: metrics.elapsed_us(),
253                        });
254                    }
255                }
256            }
257        }
258    }
259
260    /// Get or create a feedback vector for the current function.
261    /// Returns None if tiered compilation is disabled.
262    #[inline]
263    pub(crate) fn current_feedback_vector(
264        &mut self,
265    ) -> Option<&mut crate::feedback::FeedbackVector> {
266        let func_id = self.call_stack.last()?.function_id? as usize;
267        if func_id >= self.feedback_vectors.len() {
268            return None;
269        }
270        if self.feedback_vectors[func_id].is_none() {
271            if self.tier_manager.is_none() {
272                return None;
273            }
274            self.feedback_vectors[func_id] =
275                Some(crate::feedback::FeedbackVector::new(func_id as u16));
276        }
277        self.feedback_vectors[func_id].as_mut()
278    }
279
280    /// Access the feedback vectors (for JIT compilation).
281    pub fn feedback_vectors(&self) -> &[Option<crate::feedback::FeedbackVector>] {
282        &self.feedback_vectors
283    }
284
285    /// Get a reference to the loaded program (for external JIT compilation).
286    pub fn program(&self) -> &BytecodeProgram {
287        &self.program
288    }
289
290    /// Get a reference to the time-travel debugger, if enabled.
291    pub fn time_travel(&self) -> Option<&time_travel::TimeTravel> {
292        self.time_travel.as_ref()
293    }
294
295    /// Get a mutable reference to the time-travel debugger, if enabled.
296    pub fn time_travel_mut(&mut self) -> Option<&mut time_travel::TimeTravel> {
297        self.time_travel.as_mut()
298    }
299
300    /// Get a reference to the extension module registry.
301    pub fn module_registry(&self) -> &shape_runtime::module_exports::ModuleExportRegistry {
302        &self.module_registry
303    }
304
305    /// Generate a unique future ID for spawned async tasks
306    pub(crate) fn next_future_id(&mut self) -> u64 {
307        self.future_id_counter += 1;
308        self.future_id_counter
309    }
310
311    /// Get function ID for fast repeated calls (avoids name lookup in hot loops)
312    pub fn get_function_id(&self, name: &str) -> Option<u16> {
313        self.program
314            .functions
315            .iter()
316            .position(|f| f.name == name)
317            .map(|id| id as u16)
318    }
319}