Skip to main content

aver/vm/execute/
mod.rs

1mod boundary;
2mod dispatch;
3mod host;
4mod ops;
5
6#[cfg(test)]
7mod tests;
8
9use super::runtime::VmRuntime;
10use super::types::{CallFrame, CodeStore, VmError};
11use super::{VmProfileReport, profile::VmProfileState};
12use crate::nan_value::{Arena, NanValue};
13
14/// The Aver bytecode virtual machine.
15pub struct VM {
16    stack: Vec<NanValue>,
17    frames: Vec<CallFrame>,
18    globals: Vec<NanValue>,
19    code: CodeStore,
20    pub arena: Arena,
21    runtime: VmRuntime,
22    profile: Option<VmProfileState>,
23    /// Last executing (fn_id, ip) — updated at top of dispatch loop for error reporting.
24    error_fn_id: u32,
25    error_ip: u32,
26    /// Cooperative cancellation flag — set by sibling threads on error.
27    cancelled: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
28    /// Mutable scratch buffers for the deforestation lowering's `__buf_*`
29    /// intrinsics (0.15 Traversal). Slots are `Option<String>` so finalize
30    /// can take ownership and leave a tombstone; `BUFFER_NEW` reuses freed
31    /// slots before extending. The pool lives on the host heap, opaque to
32    /// the arena GC — buffer handles travel as `Int(idx)` NanValues.
33    buffer_pool: Vec<Option<String>>,
34}
35
36enum ReturnControl {
37    Done(NanValue),
38    Resume {
39        result: NanValue,
40        fn_id: u32,
41        ip: usize,
42        bp: usize,
43    },
44}
45
46impl VM {
47    pub fn new(code: CodeStore, globals: Vec<NanValue>, arena: Arena) -> Self {
48        VM {
49            stack: Vec::with_capacity(1024),
50            frames: Vec::with_capacity(64),
51            globals,
52            code,
53            arena,
54            runtime: VmRuntime::new(),
55            profile: None,
56            error_fn_id: 0,
57            error_ip: 0,
58            cancelled: None,
59            buffer_pool: Vec::new(),
60        }
61    }
62
63    pub fn start_profiling(&mut self) {
64        self.profile = Some(VmProfileState::new(self.code.functions.len()));
65    }
66
67    pub fn clear_profile(&mut self) {
68        self.profile = None;
69    }
70
71    pub fn profile_report(&self) -> Option<VmProfileReport> {
72        self.profile
73            .as_ref()
74            .map(|profile| profile.report(&self.code))
75    }
76
77    pub fn profile_top_bigrams(&self, n: usize) -> Vec<((u8, u8), u64)> {
78        self.profile
79            .as_ref()
80            .map(|p| p.top_bigrams(n))
81            .unwrap_or_default()
82    }
83
84    /// Set CLI arguments for Args.get().
85    pub fn set_cli_args(&mut self, args: Vec<String>) {
86        self.runtime.set_cli_args(args);
87    }
88
89    pub fn set_silent_console(&mut self, silent: bool) {
90        self.runtime.set_silent_console(silent);
91    }
92
93    /// Set the runtime policy loaded from `aver.toml`.
94    pub fn set_runtime_policy(&mut self, config: crate::config::ProjectConfig) {
95        self.runtime.set_runtime_policy(config);
96    }
97
98    /// Start recording effectful calls.
99    pub fn start_recording(&mut self) {
100        self.runtime.start_recording();
101    }
102
103    /// Cap the recorder at `cap` events. Useful for browser record
104    /// runs where a game with no quit path would otherwise hang the
105    /// wasm main thread. `None` (default) = unlimited, matching CLI.
106    pub fn set_record_cap(&mut self, cap: Option<usize>) {
107        self.runtime.set_record_cap(cap);
108    }
109
110    /// Start replaying from recorded effects.
111    pub fn start_replay(
112        &mut self,
113        effects: Vec<crate::replay::session::EffectRecord>,
114        validate_args: bool,
115    ) {
116        self.runtime.start_replay(effects, validate_args);
117    }
118
119    pub fn set_allowed_effects(&mut self, effects: Vec<u32>) {
120        self.runtime.set_allowed_effects(effects);
121    }
122
123    /// Oracle v1: install the oracle-stub map for a verify-law case.
124    /// Maps classified effect method names (e.g. `"Random.int"`) to the
125    /// fn_id of an Aver stub function with signature
126    /// `(BranchPath, Int, orig_args...) -> T`. Counter resets to 0.
127    pub fn install_oracle_stubs(&mut self, stubs: std::collections::HashMap<String, u32>) {
128        self.runtime.install_oracle_stubs(stubs);
129    }
130
131    /// Clear the oracle-stub map and reset the counter. Always call this
132    /// at the end of the verify-law case so the next case (or normal
133    /// evaluation) doesn't see stale substitutions.
134    pub fn clear_oracle_stubs(&mut self) {
135        self.runtime.clear_oracle_stubs();
136    }
137
138    /// Resolve an Aver top-level function name to its VM fn_id. Used by
139    /// the verify runner when wiring stubs from a `given` clause.
140    pub fn find_fn_id(&self, name: &str) -> Option<u32> {
141        self.code.find(name)
142    }
143
144    /// Oracle v1: start collecting classified-effect emissions into a
145    /// per-case trace buffer. Call before evaluating a verify-trace
146    /// case's LHS; pair with `take_trace_events` after.
147    pub fn start_trace_collection(&mut self) {
148        self.runtime.start_trace_collection();
149    }
150
151    /// Oracle v1: set (or clear) the root fn_id used by the helper-
152    /// boundary filter — only emissions whose immediate caller fn_id
153    /// matches the root count towards `.trace.*` projections. Pass
154    /// `None` (or don't call it) to disable filtering, so every
155    /// classified effect lands in the trace.
156    pub fn set_trace_root_fn_id(&mut self, fn_id: Option<u32>) {
157        self.runtime.set_trace_root_fn_id(fn_id);
158    }
159
160    /// Oracle v1: stop collection without consuming the buffer.
161    pub fn stop_trace_collection(&mut self) {
162        self.runtime.stop_trace_collection();
163    }
164
165    /// Oracle v1: take the collected trace events, stopping collection
166    /// and clearing the buffer. The returned list is what
167    /// `fn.trace.contains(...)` / `.event(k)` / `.length()` operate on.
168    pub fn take_trace_events(&mut self) -> Vec<crate::value::Value> {
169        let events = self.runtime.take_trace_events();
170        self.runtime.stop_trace_collection();
171        events
172    }
173
174    /// Oracle v1: take both events and structural coordinates
175    /// together. Used by tree-navigation projections like
176    /// `.trace.group(N).event(k)` — the coords identify which
177    /// `!`/`?!` group each event came from in source order.
178    pub fn take_trace_events_with_coords(
179        &mut self,
180    ) -> (
181        Vec<crate::value::Value>,
182        Vec<crate::vm::runtime::TraceCoord>,
183    ) {
184        let out = self.runtime.take_trace_events_with_coords();
185        self.runtime.stop_trace_collection();
186        out
187    }
188
189    pub fn set_cancelled(&mut self, flag: std::sync::Arc<std::sync::atomic::AtomicBool>) {
190        self.cancelled = Some(flag);
191    }
192
193    /// Check if this VM has been cancelled by a sibling branch.
194    fn is_cancelled(&self) -> bool {
195        self.cancelled
196            .as_ref()
197            .is_some_and(|f| f.load(std::sync::atomic::Ordering::Relaxed))
198    }
199
200    pub fn recorded_effects(&self) -> &[crate::replay::session::EffectRecord] {
201        self.runtime.recorded_effects()
202    }
203
204    pub fn replay_progress(&self) -> (usize, usize) {
205        self.runtime.replay_progress()
206    }
207
208    pub fn args_diff_count(&self) -> usize {
209        self.runtime.args_diff_count()
210    }
211
212    pub fn ensure_replay_consumed(&self) -> Result<(), VmError> {
213        self.runtime.ensure_replay_consumed()
214    }
215
216    pub fn run(&mut self) -> Result<NanValue, VmError> {
217        self.run_top_level()?;
218        // If there is no `main` function, finish silently (as expected).
219        let has_main = self
220            .code
221            .symbols
222            .find("main")
223            .and_then(|sid| self.code.symbols.resolve_function(sid))
224            .or_else(|| self.code.find("main"))
225            .is_some();
226        if has_main {
227            self.run_named_function("main", &[])
228        } else {
229            Ok(NanValue::UNIT)
230        }
231    }
232
233    pub fn run_top_level(&mut self) -> Result<(), VmError> {
234        if let Some(top_id) = self.code.find("__top_level__") {
235            let _ = self.call_function(top_id, &[])?;
236        }
237        Ok(())
238    }
239
240    pub fn run_named_function(
241        &mut self,
242        name: &str,
243        args: &[NanValue],
244    ) -> Result<NanValue, VmError> {
245        let fn_id = self
246            .code
247            .symbols
248            .find(name)
249            .and_then(|symbol_id| self.code.symbols.resolve_function(symbol_id))
250            .or_else(|| self.code.find(name))
251            .ok_or_else(|| VmError::runtime(format!("function '{}' not found", name)))?;
252        self.runtime
253            .set_allowed_effects(self.code.get(fn_id).effects.clone());
254        self.call_function(fn_id, args)
255    }
256
257    pub fn call_function(&mut self, fn_id: u32, args: &[NanValue]) -> Result<NanValue, VmError> {
258        let chunk = self.code.get(fn_id);
259        let caller_depth = self.frames.len();
260        let arena_mark = self.arena.young_len() as u32;
261        let yard_mark = self.arena.yard_len() as u32;
262        let handoff_mark = self.arena.handoff_len() as u32;
263        let bp = self.stack.len() as u32;
264        for arg in args {
265            self.stack.push(*arg);
266        }
267        for _ in args.len()..(chunk.local_count as usize) {
268            self.stack.push(NanValue::UNIT);
269        }
270        self.frames.push(CallFrame {
271            fn_id,
272            ip: 0,
273            bp,
274            local_count: chunk.local_count,
275            arena_mark,
276            yard_base: yard_mark,
277            yard_mark,
278            handoff_mark,
279            globals_dirty: false,
280            yard_dirty: false,
281            handoff_dirty: false,
282            thin: chunk.thin,
283            parent_thin: chunk.parent_thin,
284        });
285        if let Some(profile) = self.profile.as_mut() {
286            profile.record_function_entry(chunk, fn_id);
287        }
288        self.execute_until(caller_depth).map_err(|err| {
289            // Cold path: resolve source location from line_table.
290            let loc = self
291                .code
292                .resolve_source_location(self.error_fn_id, self.error_ip);
293            err.with_location(loc.map(|(file, line)| super::types::VmSourceLoc {
294                file: file.to_string(),
295                line,
296                fn_name: self.code.get(self.error_fn_id).name.clone(),
297            }))
298        })
299    }
300}