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