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    pub fn set_cancelled(&mut self, flag: std::sync::Arc<std::sync::atomic::AtomicBool>) {
117        self.cancelled = Some(flag);
118    }
119
120    /// Check if this VM has been cancelled by a sibling branch.
121    fn is_cancelled(&self) -> bool {
122        self.cancelled
123            .as_ref()
124            .is_some_and(|f| f.load(std::sync::atomic::Ordering::Relaxed))
125    }
126
127    pub fn recorded_effects(&self) -> &[crate::replay::session::EffectRecord] {
128        self.runtime.recorded_effects()
129    }
130
131    pub fn replay_progress(&self) -> (usize, usize) {
132        self.runtime.replay_progress()
133    }
134
135    pub fn args_diff_count(&self) -> usize {
136        self.runtime.args_diff_count()
137    }
138
139    pub fn ensure_replay_consumed(&self) -> Result<(), VmError> {
140        self.runtime.ensure_replay_consumed()
141    }
142
143    pub fn run(&mut self) -> Result<NanValue, VmError> {
144        self.run_top_level()?;
145        // If there is no `main` function, finish silently (as expected).
146        let has_main = self
147            .code
148            .symbols
149            .find("main")
150            .and_then(|sid| self.code.symbols.resolve_function(sid))
151            .or_else(|| self.code.find("main"))
152            .is_some();
153        if has_main {
154            self.run_named_function("main", &[])
155        } else {
156            Ok(NanValue::UNIT)
157        }
158    }
159
160    pub fn run_top_level(&mut self) -> Result<(), VmError> {
161        if let Some(top_id) = self.code.find("__top_level__") {
162            let _ = self.call_function(top_id, &[])?;
163        }
164        Ok(())
165    }
166
167    pub fn run_named_function(
168        &mut self,
169        name: &str,
170        args: &[NanValue],
171    ) -> Result<NanValue, VmError> {
172        let fn_id = self
173            .code
174            .symbols
175            .find(name)
176            .and_then(|symbol_id| self.code.symbols.resolve_function(symbol_id))
177            .or_else(|| self.code.find(name))
178            .ok_or_else(|| VmError::runtime(format!("function '{}' not found", name)))?;
179        self.runtime
180            .set_allowed_effects(self.code.get(fn_id).effects.clone());
181        self.call_function(fn_id, args)
182    }
183
184    pub fn call_function(&mut self, fn_id: u32, args: &[NanValue]) -> Result<NanValue, VmError> {
185        let chunk = self.code.get(fn_id);
186        let caller_depth = self.frames.len();
187        let arena_mark = self.arena.young_len() as u32;
188        let yard_mark = self.arena.yard_len() as u32;
189        let handoff_mark = self.arena.handoff_len() as u32;
190        let bp = self.stack.len() as u32;
191        for arg in args {
192            self.stack.push(*arg);
193        }
194        for _ in args.len()..(chunk.local_count as usize) {
195            self.stack.push(NanValue::UNIT);
196        }
197        self.frames.push(CallFrame {
198            fn_id,
199            ip: 0,
200            bp,
201            local_count: chunk.local_count,
202            arena_mark,
203            yard_base: yard_mark,
204            yard_mark,
205            handoff_mark,
206            globals_dirty: false,
207            yard_dirty: false,
208            handoff_dirty: false,
209            thin: chunk.thin,
210            parent_thin: chunk.parent_thin,
211        });
212        if let Some(profile) = self.profile.as_mut() {
213            profile.record_function_entry(chunk, fn_id);
214        }
215        self.execute_until(caller_depth).map_err(|err| {
216            // Cold path: resolve source location from line_table.
217            let loc = self
218                .code
219                .resolve_source_location(self.error_fn_id, self.error_ip);
220            err.with_location(loc.map(|(file, line)| super::types::VmSourceLoc {
221                file: file.to_string(),
222                line,
223                fn_name: self.code.get(self.error_fn_id).name.clone(),
224            }))
225        })
226    }
227}