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