Skip to main content

funct/
vm.rs

1//! The engine + reified VM.
2//!
3//! Script frames never use the host call stack: `VmState` is plain owned data
4//! and `step()` executes exactly one instruction, so execution can pause
5//! between any two instructions, be snapshotted (Clone), serialized, and
6//! resumed. Native (Rust) calls are the one atomic unit: a native call —
7//! including any script closures it invokes reentrantly — completes within a
8//! single step.
9
10use crate::ast::{ImportDef, ImportKind, Item};
11use crate::bytecode::{CaptureSrc, Const, FnProto, Instr, Pat};
12use crate::compiler::{compile_program, module_global_name, ProgramCtx};
13use crate::parser::parse;
14use crate::value::shared::{HostBound, Lock, Sh, ShWeak};
15use crate::value::{AtomCell, Closure, Value, Variant, VariantPayload};
16use std::collections::{BTreeMap, HashMap, HashSet};
17use std::fmt;
18use std::path::PathBuf;
19use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
20use std::sync::Arc;
21use std::time::Duration;
22
23#[derive(Debug, Clone, PartialEq)]
24pub struct Fault {
25    pub msg: String,
26    /// "fn_name:line" where the fault was raised, when known
27    pub at: Option<String>,
28}
29
30impl Fault {
31    pub fn new(msg: impl Into<String>) -> Fault {
32        Fault {
33            msg: msg.into(),
34            at: None,
35        }
36    }
37}
38
39impl fmt::Display for Fault {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match &self.at {
42            Some(at) => write!(f, "fault at {}: {}", at, self.msg),
43            None => write!(f, "fault: {}", self.msg),
44        }
45    }
46}
47
48#[derive(Debug)]
49pub enum FunctError {
50    Parse(String),
51    Compile(String),
52    Fault(Fault),
53}
54
55impl fmt::Display for FunctError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            FunctError::Parse(m) => write!(f, "parse error: {}", m),
59            FunctError::Compile(m) => write!(f, "compile error: {}", m),
60            FunctError::Fault(fa) => write!(f, "{}", fa),
61        }
62    }
63}
64
65impl std::error::Error for FunctError {}
66
67#[derive(Clone)]
68pub struct Frame {
69    pub fn_id: u32,
70    /// Snapshot of the proto at call time: hot reload swaps the table, but
71    /// in-flight frames finish on the code they started with (spec §8).
72    pub proto: Sh<FnProto>,
73    pub ip: u32,
74    pub locals: Vec<Value>,
75    pub upvals: Vec<Value>,
76}
77
78#[derive(Clone)]
79pub enum Status {
80    Running,
81    Done(Value),
82    Faulted(Fault),
83}
84
85/// All execution state. Plain data: Clone = snapshot (time travel).
86#[derive(Clone)]
87pub struct VmState {
88    pub frames: Vec<Frame>,
89    pub stack: Vec<Value>,
90    pub status: Status,
91}
92
93impl VmState {
94    pub fn current_line(&self) -> Option<u32> {
95        let f = self.frames.last()?;
96        Some(f.proto.line_at(f.ip as usize))
97    }
98
99    pub fn depth(&self) -> usize {
100        self.frames.len()
101    }
102
103    pub fn is_running(&self) -> bool {
104        matches!(self.status, Status::Running)
105    }
106}
107
108#[derive(Debug, Clone, PartialEq)]
109pub enum StepResult {
110    Running,
111    Done(Value),
112    Faulted(Fault),
113}
114
115#[derive(Debug, Clone, PartialEq)]
116pub enum RunResult {
117    Done(Value),
118    Faulted(Fault),
119    Paused(Cause),
120}
121
122#[derive(Debug, Clone, PartialEq)]
123pub enum Cause {
124    /// The instruction-count (gas) budget hit zero.
125    FuelExhausted,
126    /// The epoch counter reached the deadline (a time budget elapsed, or the
127    /// host bumped the epoch past `set_deadline`).
128    DeadlineReached,
129    Breakpoint(u32),
130    NextLine(u32),
131}
132
133#[derive(Debug, Clone)]
134pub enum StopWhen {
135    /// Run to completion (or fault). The tightest dispatch loop.
136    Never,
137    /// Deterministic gas: pause after this many instructions.
138    Fuel(u64),
139    /// Wall-clock budget: pause once roughly this much time has elapsed. funct
140    /// lazily starts one background ticker thread that advances the epoch; the
141    /// loop pauses with `Cause::DeadlineReached`. Granularity ≈ 1ms.
142    Deadline(Duration),
143    /// Host-driven epoch: pause once the epoch counter reaches the value set
144    /// with `set_deadline`. The host advances the epoch itself (its own timer,
145    /// a frame loop, an OS signal) via the handle from `epoch()`.
146    Epoch,
147    /// Combine an instruction budget and/or a wall-clock budget; whichever
148    /// trips first wins, and the `Cause` says which it was.
149    Budget {
150        fuel: Option<u64>,
151        deadline: Option<Duration>,
152    },
153    Breakpoints(HashSet<u32>),
154    NextLine,
155}
156
157pub(crate) type NativeImpl =
158    Sh<dyn Fn(&mut Funct, Vec<Value>) -> Result<Value, Fault> + Send + Sync>;
159
160/// Bound for host callbacks registered into the engine (see `register_raw`).
161pub trait HostFn: Fn(&mut Funct, Vec<Value>) -> Result<Value, Fault> + HostBound {}
162impl<F: Fn(&mut Funct, Vec<Value>) -> Result<Value, Fault> + HostBound> HostFn for F {}
163
164pub(crate) struct NativeEntry {
165    pub name: String,
166    pub f: NativeImpl,
167}
168
169pub(crate) type Getter = Sh<dyn Fn(&mut Funct, &Value) -> Result<Value, Fault> + Send + Sync>;
170
171struct LoadResult {
172    main: Option<u32>,
173    exports: Vec<(String, u32)>,
174}
175
176/// What a loaded module exposes to importers.
177#[derive(Debug, Clone)]
178pub(crate) enum ModuleExports {
179    /// file module: exported (plain name, global slot) pairs
180    File(Vec<(String, u32)>),
181    /// host module registered via `register_module`: global slot of its record
182    Host(u32),
183}
184
185/// The engine: code table, globals, natives, modules, atom registry.
186pub struct Funct {
187    pub(crate) fns: Vec<Sh<FnProto>>,
188    pub(crate) ctx: ProgramCtx,
189    pub(crate) globals: Vec<Option<Value>>,
190    pub(crate) natives: Vec<NativeEntry>,
191    pub(crate) native_ids: HashMap<String, u32>,
192    /// (type_name, field) -> getter, for Native host values
193    pub(crate) getters: HashMap<(String, String), Getter>,
194    pub(crate) atom_counter: u64,
195    pub(crate) atoms: Vec<ShWeak<AtomCell>>,
196    /// loaded modules by path; loaded once, cached
197    pub(crate) modules: HashMap<String, ModuleExports>,
198    /// directory module paths resolve against (`<root>/<path>.ft`)
199    pub(crate) module_root: PathBuf,
200    /// module-load stack for cycle detection
201    loading: Vec<String>,
202    /// `#[test]` functions seen in top-level (non-module) loads
203    pub(crate) test_fns: Vec<String>,
204    /// Monotonic interrupt counter. `run` compares it against a deadline at
205    /// safe points; the host (or the `Deadline` ticker thread) advances it.
206    /// Shared so a timer thread and the host can bump it concurrently.
207    epoch: Arc<AtomicU64>,
208    /// Epoch value at which `StopWhen::Epoch` pauses. `u64::MAX` = no deadline.
209    epoch_deadline: u64,
210    /// Lazily-started ticker for `StopWhen::Deadline`; advances `epoch`.
211    ticker: Option<Ticker>,
212}
213
214/// Background thread that advances the epoch on a fixed interval, so a
215/// wall-clock `Deadline` can be expressed as an epoch target. Started on first
216/// use and stopped when the engine is dropped.
217struct Ticker {
218    running: Arc<AtomicBool>,
219    tick: Duration,
220    handle: Option<std::thread::JoinHandle<()>>,
221}
222
223impl Drop for Ticker {
224    fn drop(&mut self) {
225        self.running.store(false, Ordering::Relaxed);
226        if let Some(h) = self.handle.take() {
227            let _ = h.join();
228        }
229    }
230}
231
232impl Funct {
233    /// Engine with the prelude (natives + funct-source stdlib) installed.
234    pub fn new() -> Funct {
235        let mut vm = Funct::bare();
236        crate::prelude::install(&mut vm);
237        // everything that exists at this point (natives + prelude stdlib) is
238        // visible inside modules without an import
239        let n = vm.ctx.global_names.len() as u32;
240        vm.ctx.shared.extend(0..n);
241        vm
242    }
243
244    /// Engine with no prelude at all (used by tests/snapshot restore).
245    pub fn bare() -> Funct {
246        Funct {
247            fns: Vec::new(),
248            ctx: ProgramCtx {
249                fn_ids: HashMap::new(),
250                fn_count: 0,
251                global_ids: HashMap::new(),
252                global_names: Vec::new(),
253                shared: HashSet::new(),
254            },
255            globals: Vec::new(),
256            natives: Vec::new(),
257            native_ids: HashMap::new(),
258            getters: HashMap::new(),
259            atom_counter: 0,
260            atoms: Vec::new(),
261            modules: HashMap::new(),
262            module_root: PathBuf::from("."),
263            loading: Vec::new(),
264            test_fns: Vec::new(),
265            epoch: Arc::new(AtomicU64::new(0)),
266            epoch_deadline: u64::MAX,
267            ticker: None,
268        }
269    }
270
271    /// Set the directory module import paths resolve against.
272    pub fn set_module_root(&mut self, root: impl Into<PathBuf>) {
273        self.module_root = root.into();
274    }
275
276    // ---------- epoch / time budgets ----------
277
278    /// A handle to the interrupt counter `run` checks at safe points. The host
279    /// can advance it (`fetch_add`) from any thread — a timer, a frame loop, a
280    /// signal handler — to make `StopWhen::Epoch` pause a long-running call.
281    /// `run` reads it with `Relaxed` ordering, so bumping it never blocks.
282    pub fn epoch(&self) -> Arc<AtomicU64> {
283        self.epoch.clone()
284    }
285
286    /// Current epoch value.
287    pub fn epoch_now(&self) -> u64 {
288        self.epoch.load(Ordering::Relaxed)
289    }
290
291    /// Advance the epoch by one and return the new value. Convenience for hosts
292    /// driving `StopWhen::Epoch` from their own loop.
293    pub fn bump_epoch(&self) -> u64 {
294        self.epoch.fetch_add(1, Ordering::Relaxed) + 1
295    }
296
297    /// Set the epoch value at which `StopWhen::Epoch` pauses. Pass it the result
298    /// of `epoch_now() + n`, then bump the epoch `n` times (or let a timer) to
299    /// trip it.
300    pub fn set_deadline(&mut self, epoch: u64) {
301        self.epoch_deadline = epoch;
302    }
303
304    /// Clear any epoch deadline set with `set_deadline`.
305    pub fn clear_deadline(&mut self) {
306        self.epoch_deadline = u64::MAX;
307    }
308
309    /// Ensure the background ticker (for wall-clock `Deadline`s) is running at
310    /// `tick` granularity, and return the epoch target for `after` from now.
311    fn ticker_target(&mut self, after: Duration) -> u64 {
312        const TICK: Duration = Duration::from_millis(1);
313        if self.ticker.is_none() {
314            let running = Arc::new(AtomicBool::new(true));
315            let epoch = self.epoch.clone();
316            let flag = running.clone();
317            let handle = std::thread::Builder::new()
318                .name("funct-epoch-ticker".into())
319                .spawn(move || {
320                    while flag.load(Ordering::Relaxed) {
321                        std::thread::sleep(TICK);
322                        epoch.fetch_add(1, Ordering::Relaxed);
323                    }
324                })
325                .ok();
326            self.ticker = Some(Ticker {
327                running,
328                tick: TICK,
329                handle,
330            });
331        }
332        let tick = self.ticker.as_ref().map(|t| t.tick).unwrap_or(TICK);
333        // round the duration up to whole ticks (at least 1) so a sub-tick
334        // budget still pauses promptly rather than effectively never.
335        let ticks = after.as_nanos().div_ceil(tick.as_nanos().max(1)).max(1) as u64;
336        self.epoch_now().saturating_add(ticks)
337    }
338
339    // ---------- compiling & running ----------
340
341    /// Compile and immediately run source. Re-defining an existing `fn` name
342    /// hot-swaps it (same FnId, new code).
343    pub fn eval(&mut self, src: &str) -> Result<Value, FunctError> {
344        let main = self.load(src)?;
345        match main {
346            Some(fn_id) => {
347                let mut st = self.state_for(fn_id, vec![])?;
348                match self.run(&mut st, StopWhen::Never) {
349                    RunResult::Done(v) => Ok(v),
350                    RunResult::Faulted(f) => Err(FunctError::Fault(f)),
351                    RunResult::Paused(_) => unreachable!("StopWhen::Never paused"),
352                }
353            }
354            None => Ok(Value::Unit),
355        }
356    }
357
358    /// Compile source and install its items, but run nothing. Returns the
359    /// fn id of the top-level code, if any.
360    pub fn load(&mut self, src: &str) -> Result<Option<u32>, FunctError> {
361        self.load_with_prefix(src, None).map(|r| r.main)
362    }
363
364    fn load_with_prefix(
365        &mut self,
366        src: &str,
367        prefix: Option<&str>,
368    ) -> Result<LoadResult, FunctError> {
369        let prog = parse(src).map_err(FunctError::Parse)?;
370        // imports and extern declarations first, so the bindings exist when
371        // the rest compiles
372        for item in &prog.items {
373            match item {
374                Item::Import(imp) => self.process_import(imp, prefix)?,
375                Item::Extern { name, .. } => self.process_extern(name),
376                Item::ExternLet { name, .. } => {
377                    // declare the slot (shared, like all host-provided
378                    // globals); reading it unset faults loudly already
379                    let g = self.ctx.ensure_global(name);
380                    self.ctx.shared.insert(g);
381                    self.sync_globals();
382                }
383                _ => {}
384            }
385        }
386        let compiled =
387            compile_program(&mut self.ctx, &prog, prefix).map_err(FunctError::Compile)?;
388        for (id, proto) in compiled.protos {
389            let id = id as usize;
390            if self.fns.len() <= id {
391                self.fns.resize_with(id + 1, || {
392                    Sh::new(FnProto {
393                        name: "<hole>".into(),
394                        arity: 0,
395                        num_locals: 0,
396                        num_upvals: 0,
397                        code: vec![],
398                        consts: vec![],
399                        pats: vec![],
400                        lines: vec![],
401                        closure_captures: vec![],
402                    })
403                });
404            }
405            self.fns[id] = Sh::new(proto);
406        }
407        self.sync_globals();
408        for (gslot, fn_id) in compiled.fn_globals {
409            self.globals[gslot as usize] = Some(Value::Closure(Sh::new(Closure {
410                fn_id,
411                upvals: vec![],
412            })));
413        }
414        // tests register for the runner only from top-level files, not from
415        // imported modules (run a module file directly to run its tests)
416        if prefix.is_none() {
417            for t in &compiled.tests {
418                if !self.test_fns.contains(t) {
419                    self.test_fns.push(t.clone());
420                }
421            }
422        }
423        Ok(LoadResult {
424            main: compiled.main,
425            exports: compiled.exports,
426        })
427    }
428
429    /// `extern fn name(...)`: if the host registered a native with this name
430    /// it is already bound — nothing to do. Otherwise install a placeholder
431    /// native that faults loudly when CALLED, so files declaring a host
432    /// interface still load (e.g. for `funct test` over pure logic).
433    fn process_extern(&mut self, name: &str) {
434        if let Some(&g) = self.ctx.global_ids.get(name) {
435            if self
436                .globals
437                .get(g as usize)
438                .map(|v| v.is_some())
439                .unwrap_or(false)
440            {
441                return; // host already provided it
442            }
443        }
444        let fn_name = name.to_string();
445        self.register_raw(name, move |_vm, _args| {
446            Err(Fault::new(format!(
447                "host function `{}` was declared `extern` but this host does not provide it",
448                fn_name
449            )))
450        });
451    }
452
453    /// Names of `#[test]` functions loaded so far (declaration order).
454    pub fn test_names(&self) -> Vec<String> {
455        self.test_fns.clone()
456    }
457
458    // ---------- modules ----------
459
460    /// Load a module (if not already cached) and return its export names.
461    pub fn load_module(&mut self, path: &str) -> Result<Vec<String>, FunctError> {
462        self.ensure_module(path)?;
463        Ok(self.module_export_names(path))
464    }
465
466    /// Re-read and re-evaluate a file module: functions hot-swap (importers
467    /// see new code immediately); module top-level `let`s re-run, so module
468    /// atoms are recreated. Previously imported `let` *values* keep the copy
469    /// they were bound to.
470    pub fn reload_module(&mut self, path: &str) -> Result<Vec<String>, FunctError> {
471        if let Some(ModuleExports::Host(_)) = self.modules.get(path) {
472            return Err(FunctError::Fault(Fault::new(format!(
473                "`{}` is a host module registered from Rust; re-register it instead",
474                path
475            ))));
476        }
477        self.modules.remove(path);
478        self.load_module(path)
479    }
480
481    /// Make a host record (already stored at global `gid`) importable.
482    pub(crate) fn register_host_module(&mut self, name: &str, gid: u32) {
483        self.modules
484            .insert(name.to_string(), ModuleExports::Host(gid));
485    }
486
487    fn module_export_names(&self, path: &str) -> Vec<String> {
488        match self.modules.get(path) {
489            Some(ModuleExports::File(list)) => list.iter().map(|(n, _)| n.clone()).collect(),
490            Some(ModuleExports::Host(gid)) => match self.globals.get(*gid as usize) {
491                Some(Some(Value::Record(r))) => r.keys().cloned().collect(),
492                _ => vec![],
493            },
494            None => vec![],
495        }
496    }
497
498    fn ensure_module(&mut self, path: &str) -> Result<(), FunctError> {
499        if self.modules.contains_key(path) {
500            return Ok(());
501        }
502        if path
503            .split('/')
504            .any(|seg| seg.is_empty() || seg == "." || seg == "..")
505        {
506            return Err(FunctError::Fault(Fault::new(format!(
507                "invalid module path `{}`: segments must be plain names (no `..`, `.` or empty)",
508                path
509            ))));
510        }
511        if let Some(i) = self.loading.iter().position(|p| p == path) {
512            let mut chain: Vec<&str> = self.loading[i..].iter().map(|s| s.as_str()).collect();
513            chain.push(path);
514            return Err(FunctError::Fault(Fault::new(format!(
515                "circular module imports: {}",
516                chain.join(" -> ")
517            ))));
518        }
519        let file = self.module_root.join(format!("{}.ft", path));
520        let src = std::fs::read_to_string(&file).map_err(|e| {
521            FunctError::Fault(Fault::new(format!(
522                "cannot load module `{}`: {} ({})",
523                path,
524                file.display(),
525                e
526            )))
527        })?;
528        self.loading.push(path.to_string());
529        let result = (|| {
530            let loaded = self.load_with_prefix(&src, Some(path))?;
531            // run the module's top-level code now (its lets/side effects)
532            if let Some(fn_id) = loaded.main {
533                let mut st = self.state_for(fn_id, vec![])?;
534                match self.run(&mut st, StopWhen::Never) {
535                    RunResult::Done(_) => {}
536                    RunResult::Faulted(f) => return Err(FunctError::Fault(f)),
537                    RunResult::Paused(_) => unreachable!(),
538                }
539            }
540            Ok(loaded.exports)
541        })();
542        self.loading.pop();
543        let exports = result?;
544        self.modules
545            .insert(path.to_string(), ModuleExports::File(exports));
546        Ok(())
547    }
548
549    /// Resolve one exported name from a loaded module to its current value.
550    fn export_value(&self, path: &str, name: &str) -> Result<Value, FunctError> {
551        let loud_missing = |available: Vec<String>| {
552            FunctError::Fault(Fault::new(format!(
553                "module `{}` has no export `{}` (exports: {})",
554                path,
555                name,
556                if available.is_empty() {
557                    "none".to_string()
558                } else {
559                    available.join(", ")
560                }
561            )))
562        };
563        match self.modules.get(path) {
564            Some(ModuleExports::File(list)) => match list.iter().find(|(n, _)| n == name) {
565                Some((_, gid)) => self
566                    .globals
567                    .get(*gid as usize)
568                    .cloned()
569                    .flatten()
570                    .ok_or_else(|| {
571                        FunctError::Fault(Fault::new(format!(
572                            "export `{}` of module `{}` is uninitialized",
573                            name, path
574                        )))
575                    }),
576                None => Err(loud_missing(list.iter().map(|(n, _)| n.clone()).collect())),
577            },
578            Some(ModuleExports::Host(gid)) => match self.globals.get(*gid as usize) {
579                Some(Some(Value::Record(r))) => r
580                    .get(name)
581                    .cloned()
582                    .ok_or_else(|| loud_missing(r.keys().cloned().collect())),
583                _ => Err(FunctError::Fault(Fault::new(format!(
584                    "host module `{}` record is missing",
585                    path
586                )))),
587            },
588            None => Err(FunctError::Fault(Fault::new(format!(
589                "module `{}` not loaded",
590                path
591            )))),
592        }
593    }
594
595    fn process_import(&mut self, imp: &ImportDef, prefix: Option<&str>) -> Result<(), FunctError> {
596        self.ensure_module(&imp.path)?;
597        match &imp.kind {
598            ImportKind::Named(names) => {
599                for (name, alias) in names {
600                    let v = self.export_value(&imp.path, name)?;
601                    let bind = alias.as_deref().unwrap_or(name);
602                    let full = module_global_name(prefix, bind);
603                    let g = self.ctx.ensure_global(&full);
604                    self.sync_globals();
605                    self.globals[g as usize] = Some(v);
606                }
607            }
608            ImportKind::Qualified(alias) => {
609                let bind = match alias {
610                    Some(a) => a.clone(),
611                    None => {
612                        let last = imp.path.rsplit('/').next().unwrap_or(&imp.path);
613                        let valid = !last.is_empty()
614                            && last
615                                .chars()
616                                .next()
617                                .map(|c| c.is_ascii_lowercase() || c == '_')
618                                .unwrap_or(false)
619                            && last.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
620                        if !valid {
621                            return Err(FunctError::Fault(Fault::new(format!(
622                                "`{}` is not a usable module alias; add one: import \"{}\" as <name>",
623                                last, imp.path
624                            ))));
625                        }
626                        last.to_string()
627                    }
628                };
629                let v = match self.modules.get(&imp.path).cloned() {
630                    Some(ModuleExports::Host(gid)) => self
631                        .globals
632                        .get(gid as usize)
633                        .cloned()
634                        .flatten()
635                        .ok_or_else(|| {
636                            FunctError::Fault(Fault::new(format!(
637                                "host module `{}` record is missing",
638                                imp.path
639                            )))
640                        })?,
641                    Some(ModuleExports::File(list)) => {
642                        // Skip exports with no value yet: an `extern let` the
643                        // host hasn't provided is uninitialized, and a bare
644                        // `import "host"` must still load (the externs' real
645                        // job is declaring the shared globals, a side effect of
646                        // ensure_module above). extern fns always have a
647                        // placeholder value, so they're always included.
648                        let mut map = BTreeMap::new();
649                        for (name, gid) in &list {
650                            if let Some(Some(v)) = self.globals.get(*gid as usize) {
651                                map.insert(name.clone(), v.clone());
652                            }
653                        }
654                        Value::record(map)
655                    }
656                    None => unreachable!("ensure_module just loaded it"),
657                };
658                let full = module_global_name(prefix, &bind);
659                let g = self.ctx.ensure_global(&full);
660                self.sync_globals();
661                self.globals[g as usize] = Some(v);
662            }
663        }
664        Ok(())
665    }
666
667    /// Compile source like `eval`, but return a resumable VmState for its
668    /// top-level code instead of running it.
669    pub fn eval_resumable(&mut self, src: &str) -> Result<VmState, FunctError> {
670        let main = self.load(src)?;
671        match main {
672            Some(fn_id) => self.state_for(fn_id, vec![]),
673            None => Ok(VmState {
674                frames: vec![],
675                stack: vec![],
676                status: Status::Done(Value::Unit),
677            }),
678        }
679    }
680
681    pub(crate) fn sync_globals(&mut self) {
682        if self.globals.len() < self.ctx.global_names.len() {
683            self.globals.resize(self.ctx.global_names.len(), None);
684        }
685    }
686
687    fn state_for(&mut self, fn_id: u32, args: Vec<Value>) -> Result<VmState, FunctError> {
688        let proto = self
689            .fns
690            .get(fn_id as usize)
691            .cloned()
692            .ok_or_else(|| FunctError::Fault(Fault::new(format!("unknown fn id {}", fn_id))))?;
693        let frame = make_frame(fn_id, proto, args).map_err(FunctError::Fault)?;
694        Ok(VmState {
695            frames: vec![frame],
696            stack: vec![],
697            status: Status::Running,
698        })
699    }
700
701    pub fn global(&self, name: &str) -> Option<Value> {
702        let id = *self.ctx.global_ids.get(name)?;
703        self.globals.get(id as usize)?.clone()
704    }
705
706    /// Set (or create) a global binding from the host — e.g. injecting
707    /// `canvas_w` before calling a handler. Host-provided globals are
708    /// `shared`: visible inside modules without an import, like natives.
709    pub fn set_global(&mut self, name: &str, v: Value) {
710        let g = self.ctx.ensure_global(name);
711        self.ctx.shared.insert(g);
712        self.sync_globals();
713        self.globals[g as usize] = Some(v);
714    }
715
716    /// Create a resumable state that calls global `name` with `args`.
717    pub fn start(&mut self, name: &str, args: Vec<Value>) -> Result<VmState, FunctError> {
718        let f = self
719            .global(name)
720            .ok_or_else(|| FunctError::Fault(Fault::new(format!("unknown function `{}`", name))))?;
721        self.start_value(&f, args)
722    }
723
724    pub fn start_value(&mut self, f: &Value, args: Vec<Value>) -> Result<VmState, FunctError> {
725        match f {
726            Value::Closure(c) => {
727                let proto = self.fns[c.fn_id as usize].clone();
728                let mut frame = make_frame(c.fn_id, proto, args).map_err(FunctError::Fault)?;
729                frame.upvals = c.upvals.clone();
730                Ok(VmState {
731                    frames: vec![frame],
732                    stack: vec![],
733                    status: Status::Running,
734                })
735            }
736            Value::NativeFn(id) => {
737                // no script frames; run it now and produce a Done state
738                let f = self.natives[*id as usize].f.clone();
739                match f(self, args) {
740                    Ok(v) => Ok(VmState {
741                        frames: vec![],
742                        stack: vec![],
743                        status: Status::Done(v),
744                    }),
745                    Err(e) => Err(FunctError::Fault(e)),
746                }
747            }
748            other => Err(FunctError::Fault(Fault::new(format!(
749                "value of type {} is not callable",
750                other.type_name()
751            )))),
752        }
753    }
754
755    /// Call a script function to completion (used by host code and natives).
756    pub fn call_value(&mut self, f: &Value, args: Vec<Value>) -> Result<Value, Fault> {
757        let mut st = self.start_value(f, args).map_err(|e| match e {
758            FunctError::Fault(f) => f,
759            other => Fault::new(other.to_string()),
760        })?;
761        match self.run(&mut st, StopWhen::Never) {
762            RunResult::Done(v) => Ok(v),
763            RunResult::Faulted(f) => Err(f),
764            RunResult::Paused(_) => unreachable!(),
765        }
766    }
767
768    /// Call a global by name to completion.
769    pub fn call(&mut self, name: &str, args: Vec<Value>) -> Result<Value, FunctError> {
770        let f = self
771            .global(name)
772            .ok_or_else(|| FunctError::Fault(Fault::new(format!("unknown function `{}`", name))))?;
773        self.call_value(&f, args).map_err(FunctError::Fault)
774    }
775
776    pub fn run(&mut self, st: &mut VmState, stop: StopWhen) -> RunResult {
777        // Fast path: no breakpoints / single-stepping → a tight dispatch loop
778        // with none of the per-instruction line tracking the debug modes need.
779        // This is what `eval`, host calls, and ordinary execution use.
780        match &stop {
781            StopWhen::Never => return self.run_fast(st, None),
782            StopWhen::Fuel(n) => return self.run_fast(st, Some(*n)),
783            StopWhen::Epoch => {
784                let dl = self.epoch_deadline;
785                return self.run_with_deadline(st, None, dl);
786            }
787            StopWhen::Deadline(d) => {
788                let dl = self.ticker_target(*d);
789                return self.run_with_deadline(st, None, dl);
790            }
791            StopWhen::Budget { fuel, deadline } => {
792                return match deadline {
793                    // no time limit → reuse the untouched gas-only fast path
794                    None => self.run_fast(st, *fuel),
795                    Some(d) => {
796                        let dl = self.ticker_target(*d);
797                        self.run_with_deadline(st, *fuel, dl)
798                    }
799                };
800            }
801            _ => {}
802        }
803        let mut fuel: Option<u64> = None;
804        // for breakpoints: only trigger when we *enter* the line, so resuming
805        // from a breakpoint doesn't immediately re-trigger it
806        let mut last_line = st.current_line().unwrap_or(0);
807        let start_line = st.current_line().unwrap_or(0);
808        let start_depth = st.depth();
809        loop {
810            match &st.status {
811                Status::Done(v) => return RunResult::Done(v.clone()),
812                Status::Faulted(f) => return RunResult::Faulted(f.clone()),
813                Status::Running => {}
814            }
815            if let Some(0) = fuel {
816                return RunResult::Paused(Cause::FuelExhausted);
817            }
818            if let Some(line) = st.current_line() {
819                match &stop {
820                    StopWhen::Breakpoints(bps) => {
821                        if line != last_line && bps.contains(&line) {
822                            return RunResult::Paused(Cause::Breakpoint(line));
823                        }
824                    }
825                    StopWhen::NextLine => {
826                        if line != start_line && st.depth() <= start_depth {
827                            return RunResult::Paused(Cause::NextLine(line));
828                        }
829                    }
830                    _ => {}
831                }
832                last_line = line;
833            }
834            match self.step(st) {
835                StepResult::Running => {}
836                StepResult::Done(v) => return RunResult::Done(v),
837                StepResult::Faulted(f) => return RunResult::Faulted(f),
838            }
839            if let Some(n) = fuel.as_mut() {
840                *n -= 1;
841            }
842        }
843    }
844
845    /// Tight execution loop for the common case (no breakpoints / stepping):
846    /// calls `step_inner` directly — no per-instruction line lookup, no extra
847    /// `step()` dispatch layer. `fuel` = Some(budget) caps instruction count.
848    fn run_fast(&mut self, st: &mut VmState, mut fuel: Option<u64>) -> RunResult {
849        loop {
850            // cheap tag check: a native tail-call on the last frame finishes by
851            // setting status (rather than returning a value), so catch it here
852            match &st.status {
853                Status::Done(v) => return RunResult::Done(v.clone()),
854                Status::Faulted(f) => return RunResult::Faulted(f.clone()),
855                Status::Running => {}
856            }
857            if let Some(0) = fuel {
858                return RunResult::Paused(Cause::FuelExhausted);
859            }
860            match self.step_inner(st) {
861                Ok(None) => {}
862                Ok(Some(v)) => {
863                    st.status = Status::Done(v.clone());
864                    return RunResult::Done(v);
865                }
866                Err(mut fault) => {
867                    if fault.at.is_none() {
868                        if let Some(f) = st.frames.last() {
869                            let ip = (f.ip as usize).saturating_sub(1);
870                            fault.at = Some(format!("{}:{}", f.proto.name, f.proto.line_at(ip)));
871                        }
872                    }
873                    st.status = Status::Faulted(fault.clone());
874                    return RunResult::Faulted(fault);
875                }
876            }
877            if let Some(n) = fuel.as_mut() {
878                *n -= 1;
879            }
880        }
881    }
882
883    /// Like `run_fast`, but also pauses with `Cause::DeadlineReached` once the
884    /// epoch counter reaches `deadline`. The epoch is sampled every
885    /// `EPOCH_POLL` instructions (not every one) so the atomic load stays off
886    /// the hot per-instruction path; budget granularity is therefore ≈ the poll
887    /// interval, which is far finer than any useful wall-clock budget.
888    fn run_with_deadline(
889        &mut self,
890        st: &mut VmState,
891        mut fuel: Option<u64>,
892        deadline: u64,
893    ) -> RunResult {
894        const EPOCH_POLL: u32 = 256;
895        let mut since_poll: u32 = 0;
896        loop {
897            match &st.status {
898                Status::Done(v) => return RunResult::Done(v.clone()),
899                Status::Faulted(f) => return RunResult::Faulted(f.clone()),
900                Status::Running => {}
901            }
902            if let Some(0) = fuel {
903                return RunResult::Paused(Cause::FuelExhausted);
904            }
905            since_poll += 1;
906            if since_poll >= EPOCH_POLL {
907                since_poll = 0;
908                if self.epoch.load(Ordering::Relaxed) >= deadline {
909                    return RunResult::Paused(Cause::DeadlineReached);
910                }
911            }
912            match self.step_inner(st) {
913                Ok(None) => {}
914                Ok(Some(v)) => {
915                    st.status = Status::Done(v.clone());
916                    return RunResult::Done(v);
917                }
918                Err(mut fault) => {
919                    if fault.at.is_none() {
920                        if let Some(f) = st.frames.last() {
921                            let ip = (f.ip as usize).saturating_sub(1);
922                            fault.at = Some(format!("{}:{}", f.proto.name, f.proto.line_at(ip)));
923                        }
924                    }
925                    st.status = Status::Faulted(fault.clone());
926                    return RunResult::Faulted(fault);
927                }
928            }
929            if let Some(n) = fuel.as_mut() {
930                *n -= 1;
931            }
932        }
933    }
934
935    // ---------- the step loop ----------
936
937    /// Execute exactly one instruction.
938    pub fn step(&mut self, st: &mut VmState) -> StepResult {
939        match &st.status {
940            Status::Done(v) => return StepResult::Done(v.clone()),
941            Status::Faulted(f) => return StepResult::Faulted(f.clone()),
942            Status::Running => {}
943        }
944        match self.step_inner(st) {
945            Ok(Some(v)) => {
946                st.status = Status::Done(v.clone());
947                StepResult::Done(v)
948            }
949            Ok(None) => StepResult::Running,
950            Err(mut fault) => {
951                if fault.at.is_none() {
952                    if let Some(f) = st.frames.last() {
953                        let ip = (f.ip as usize).saturating_sub(1);
954                        fault.at = Some(format!("{}:{}", f.proto.name, f.proto.line_at(ip)));
955                    }
956                }
957                st.status = Status::Faulted(fault.clone());
958                StepResult::Faulted(fault)
959            }
960        }
961    }
962
963    fn step_inner(&mut self, st: &mut VmState) -> Result<Option<Value>, Fault> {
964        let frame = st
965            .frames
966            .last_mut()
967            .ok_or_else(|| Fault::new("vm has no frames (already finished?)"))?;
968        let ip = frame.ip as usize;
969        // `Instr` is `Copy`, so this is a trivial register/stack copy, not a
970        // heap-touching clone — the hottest line in the interpreter.
971        let instr = *frame.proto.code.get(ip).ok_or_else(|| {
972            Fault::new(format!("ip {} out of bounds in {}", ip, frame.proto.name))
973        })?;
974        frame.ip += 1;
975
976        macro_rules! pop {
977            () => {
978                st.stack
979                    .pop()
980                    .ok_or_else(|| Fault::new("operand stack underflow"))?
981            };
982        }
983
984        match instr {
985            Instr::Nop => {}
986            Instr::Const(k) => {
987                let frame = st.frames.last().unwrap();
988                let v = match &frame.proto.consts[k as usize] {
989                    Const::Int(i) => Value::Int(*i),
990                    Const::Float(f) => Value::Float(*f),
991                    Const::Str(s) => Value::str(s.clone()),
992                    c => return Err(Fault::new(format!("cannot load const {:?}", c))),
993                };
994                st.stack.push(v);
995            }
996            Instr::Unit => st.stack.push(Value::Unit),
997            Instr::True => st.stack.push(Value::Bool(true)),
998            Instr::False => st.stack.push(Value::Bool(false)),
999            Instr::LoadLocal(n) => {
1000                let v = st.frames.last().unwrap().locals[n as usize].clone();
1001                st.stack.push(v);
1002            }
1003            Instr::StoreLocal(n) => {
1004                let v = pop!();
1005                st.frames.last_mut().unwrap().locals[n as usize] = v;
1006            }
1007            Instr::LoadUpval(n) => {
1008                let v = st.frames.last().unwrap().upvals[n as usize].clone();
1009                st.stack.push(v);
1010            }
1011            Instr::LoadGlobal(g) => {
1012                let v = self
1013                    .globals
1014                    .get(g as usize)
1015                    .cloned()
1016                    .flatten()
1017                    .ok_or_else(|| {
1018                        let name = self
1019                            .ctx
1020                            .global_names
1021                            .get(g as usize)
1022                            .cloned()
1023                            .unwrap_or_else(|| format!("#{}", g));
1024                        Fault::new(format!("global `{}` used before it was initialized", name))
1025                    })?;
1026                st.stack.push(v);
1027            }
1028            Instr::StoreGlobal(g) => {
1029                let v = pop!();
1030                self.sync_globals();
1031                if (g as usize) >= self.globals.len() {
1032                    self.globals.resize(g as usize + 1, None);
1033                }
1034                self.globals[g as usize] = Some(v);
1035            }
1036            Instr::NewCell => {
1037                let v = pop!();
1038                st.stack.push(Value::Cell(Sh::new(Lock::new(v))));
1039            }
1040            Instr::CellGet => {
1041                let c = pop!();
1042                match c {
1043                    Value::Cell(c) => st.stack.push(c.read().clone()),
1044                    other => return Err(Fault::new(format!("CellGet on {}", other.type_name()))),
1045                }
1046            }
1047            Instr::CellSet => {
1048                let v = pop!();
1049                let c = pop!();
1050                match c {
1051                    Value::Cell(c) => *c.write() = v,
1052                    other => return Err(Fault::new(format!("CellSet on {}", other.type_name()))),
1053                }
1054            }
1055            Instr::MakeList(n) => {
1056                let items = pop_n(&mut st.stack, n as usize)?;
1057                st.stack.push(Value::list(items));
1058            }
1059            Instr::MakeTuple(n) => {
1060                let items = pop_n(&mut st.stack, n as usize)?;
1061                st.stack.push(Value::tuple(items));
1062            }
1063            Instr::MakeRecord(names_k) => {
1064                let names = self.names_const(st, names_k)?;
1065                let vals = pop_n(&mut st.stack, names.len())?;
1066                let mut map = BTreeMap::new();
1067                for (n, v) in names.into_iter().zip(vals) {
1068                    map.insert(n, v);
1069                }
1070                st.stack.push(Value::record(map));
1071            }
1072            Instr::RecordUpdate(names_k) => {
1073                let names = self.names_const(st, names_k)?;
1074                let vals = pop_n(&mut st.stack, names.len())?;
1075                let base = pop!();
1076                let mut map = match base {
1077                    Value::Record(r) => r.clone(),
1078                    other => {
1079                        return Err(Fault::new(format!(
1080                            "record update `{{ ..base }}` needs a record, got {}",
1081                            other.type_name()
1082                        )))
1083                    }
1084                };
1085                for (n, v) in names.into_iter().zip(vals) {
1086                    map.insert(n, v);
1087                }
1088                st.stack.push(Value::Record(map));
1089            }
1090            Instr::GetField(name_k) => {
1091                let name = self.name_const(st, name_k)?;
1092                let recv = pop!();
1093                let v = self.get_field(&recv, &name)?;
1094                st.stack.push(v);
1095            }
1096            Instr::Index => {
1097                let idx = pop!();
1098                let recv = pop!();
1099                st.stack.push(index_value(&recv, &idx)?);
1100            }
1101            Instr::MakeVariantUnit { tag } => {
1102                let tag = self.name_const(st, tag)?;
1103                st.stack.push(Value::Variant(Sh::new(Variant {
1104                    tag,
1105                    payload: VariantPayload::Unit,
1106                })));
1107            }
1108            Instr::MakeVariantPos { tag, count } => {
1109                let tag = self.name_const(st, tag)?;
1110                let items = pop_n(&mut st.stack, count as usize)?;
1111                st.stack.push(Value::Variant(Sh::new(Variant {
1112                    tag,
1113                    payload: VariantPayload::Positional(items),
1114                })));
1115            }
1116            Instr::MakeVariantNamed { tag, names } => {
1117                let tag = self.name_const(st, tag)?;
1118                let names = self.names_const(st, names)?;
1119                let vals = pop_n(&mut st.stack, names.len())?;
1120                let mut map = BTreeMap::new();
1121                for (n, v) in names.into_iter().zip(vals) {
1122                    map.insert(n, v);
1123                }
1124                st.stack.push(Value::Variant(Sh::new(Variant {
1125                    tag,
1126                    payload: VariantPayload::Named(map),
1127                })));
1128            }
1129            Instr::Add => bin_op(&mut st.stack, BinKind::Add)?,
1130            Instr::Sub => bin_op(&mut st.stack, BinKind::Sub)?,
1131            Instr::Mul => bin_op(&mut st.stack, BinKind::Mul)?,
1132            Instr::Div => bin_op(&mut st.stack, BinKind::Div)?,
1133            Instr::Mod => bin_op(&mut st.stack, BinKind::Mod)?,
1134            Instr::Pow => bin_op(&mut st.stack, BinKind::Pow)?,
1135            Instr::Eq => {
1136                let b = pop!();
1137                let a = pop!();
1138                st.stack.push(Value::Bool(a == b));
1139            }
1140            Instr::Ne => {
1141                let b = pop!();
1142                let a = pop!();
1143                st.stack.push(Value::Bool(a != b));
1144            }
1145            Instr::Lt => cmp_op(&mut st.stack, |o| o == std::cmp::Ordering::Less)?,
1146            Instr::Le => cmp_op(&mut st.stack, |o| o != std::cmp::Ordering::Greater)?,
1147            Instr::Gt => cmp_op(&mut st.stack, |o| o == std::cmp::Ordering::Greater)?,
1148            Instr::Ge => cmp_op(&mut st.stack, |o| o != std::cmp::Ordering::Less)?,
1149            Instr::Neg => {
1150                let v = pop!();
1151                st.stack.push(match v {
1152                    Value::Int(i) => Value::Int(
1153                        i.checked_neg()
1154                            .ok_or_else(|| Fault::new("integer overflow in negation"))?,
1155                    ),
1156                    Value::Float(f) => Value::Float(-f),
1157                    other => {
1158                        return Err(Fault::new(format!("cannot negate {}", other.type_name())))
1159                    }
1160                });
1161            }
1162            Instr::Not => {
1163                let v = pop!();
1164                match v {
1165                    Value::Bool(b) => st.stack.push(Value::Bool(!b)),
1166                    other => {
1167                        return Err(Fault::new(format!(
1168                            "`not` needs a Bool, got {}",
1169                            other.type_name()
1170                        )))
1171                    }
1172                }
1173            }
1174            Instr::MakeRange { inclusive } => {
1175                let hi = pop!();
1176                let lo = pop!();
1177                match (lo, hi) {
1178                    (Value::Int(a), Value::Int(b)) => st.stack.push(Value::Range(a, b, inclusive)),
1179                    (a, b) => {
1180                        return Err(Fault::new(format!(
1181                            "range bounds must be Int, got {} and {}",
1182                            a.type_name(),
1183                            b.type_name()
1184                        )))
1185                    }
1186                }
1187            }
1188            Instr::Jump(t) => st.frames.last_mut().unwrap().ip = t,
1189            Instr::JumpIfFalse(t) => {
1190                let v = pop!();
1191                match v {
1192                    Value::Bool(false) => st.frames.last_mut().unwrap().ip = t,
1193                    Value::Bool(true) => {}
1194                    other => {
1195                        return Err(Fault::new(format!(
1196                            "condition must be a Bool, got {}",
1197                            other.type_name()
1198                        )))
1199                    }
1200                }
1201            }
1202            Instr::JumpIfFalsePeek(t) => match st.stack.last() {
1203                Some(Value::Bool(false)) => st.frames.last_mut().unwrap().ip = t,
1204                Some(Value::Bool(true)) => {}
1205                Some(other) => {
1206                    return Err(Fault::new(format!(
1207                        "`and`/`or` operands must be Bool, got {}",
1208                        other.type_name()
1209                    )))
1210                }
1211                None => return Err(Fault::new("operand stack underflow")),
1212            },
1213            Instr::JumpIfTruePeek(t) => match st.stack.last() {
1214                Some(Value::Bool(true)) => st.frames.last_mut().unwrap().ip = t,
1215                Some(Value::Bool(false)) => {}
1216                Some(other) => {
1217                    return Err(Fault::new(format!(
1218                        "`and`/`or` operands must be Bool, got {}",
1219                        other.type_name()
1220                    )))
1221                }
1222                None => return Err(Fault::new("operand stack underflow")),
1223            },
1224            Instr::MatchPat { pat, fail } => {
1225                let subject = st
1226                    .stack
1227                    .last()
1228                    .ok_or_else(|| Fault::new("operand stack underflow"))?
1229                    .clone();
1230                let frame = st.frames.last_mut().unwrap();
1231                let pat = frame.proto.pats[pat as usize].clone();
1232                if !match_pat(&pat, &subject, &mut frame.locals) {
1233                    frame.ip = fail;
1234                }
1235            }
1236            Instr::MakeClosure { fn_id, captures } => {
1237                let frame = st.frames.last().unwrap();
1238                let caps = &frame.proto.closure_captures[captures as usize];
1239                let upvals: Vec<Value> = caps
1240                    .iter()
1241                    .map(|c| match c {
1242                        CaptureSrc::Local(s) => frame.locals[*s as usize].clone(),
1243                        CaptureSrc::Upval(i) => frame.upvals[*i as usize].clone(),
1244                    })
1245                    .collect();
1246                st.stack
1247                    .push(Value::Closure(Sh::new(Closure { fn_id, upvals })));
1248            }
1249            Instr::Call(argc) => {
1250                let args = pop_n(&mut st.stack, argc as usize)?;
1251                let callee = pop!();
1252                self.do_call(st, callee, args, false)?;
1253            }
1254            Instr::TailCall(argc) => {
1255                let args = pop_n(&mut st.stack, argc as usize)?;
1256                let callee = pop!();
1257                self.do_call(st, callee, args, true)?;
1258            }
1259            Instr::Invoke { name, global, argc } => {
1260                let name = self.name_const(st, name)?;
1261                let args = pop_n(&mut st.stack, argc as usize)?;
1262                let recv = pop!();
1263                // UFCS: a record field named `name` wins (spec §4.1)
1264                let field_callable = match &recv {
1265                    Value::Record(r) => r.get(&name).cloned(),
1266                    _ => None,
1267                };
1268                match field_callable {
1269                    Some(f @ (Value::Closure(_) | Value::NativeFn(_))) => {
1270                        self.do_call(st, f, args, false)?;
1271                    }
1272                    Some(other) => {
1273                        return Err(Fault::new(format!(
1274                            "field `{}` is not callable (it is {})",
1275                            name,
1276                            other.type_name()
1277                        )))
1278                    }
1279                    None => {
1280                        // compile-time resolved slot (module-aware), then a
1281                        // plain runtime lookup for late-defined globals
1282                        let f = global
1283                            .and_then(|g| self.globals.get(g as usize).cloned().flatten())
1284                            .or_else(|| self.global(&name))
1285                            .ok_or_else(|| {
1286                                Fault::new(format!(
1287                                    "no function `{}` for method call on {}",
1288                                    name,
1289                                    recv.type_name()
1290                                ))
1291                            })?;
1292                        let mut full_args = Vec::with_capacity(args.len() + 1);
1293                        full_args.push(recv);
1294                        full_args.extend(args);
1295                        self.do_call(st, f, full_args, false)?;
1296                    }
1297                }
1298            }
1299            Instr::Ret => {
1300                let ret = pop!();
1301                st.frames.pop();
1302                if st.frames.is_empty() {
1303                    return Ok(Some(ret));
1304                }
1305                st.stack.push(ret);
1306            }
1307            Instr::Deref => {
1308                let v = pop!();
1309                match v {
1310                    Value::Atom(a) => st.stack.push(a.value.read().clone()),
1311                    other => {
1312                        return Err(Fault::new(format!(
1313                            "`@` deref needs an Atom, got {}",
1314                            other.type_name()
1315                        )))
1316                    }
1317                }
1318            }
1319            Instr::Try => {
1320                let v = pop!();
1321                match &v {
1322                    Value::Variant(var) => match (var.tag.as_str(), &var.payload) {
1323                        ("Ok", VariantPayload::Positional(p))
1324                        | ("Some", VariantPayload::Positional(p))
1325                            if p.len() == 1 =>
1326                        {
1327                            st.stack.push(p[0].clone());
1328                        }
1329                        ("Err", _) | ("None", _) => {
1330                            // return the carrier from the enclosing function
1331                            st.frames.pop();
1332                            if st.frames.is_empty() {
1333                                return Ok(Some(v));
1334                            }
1335                            st.stack.push(v);
1336                        }
1337                        _ => {
1338                            return Err(Fault::new(format!(
1339                                "`?` needs Ok/Err/Some/None, got {}",
1340                                v.type_name()
1341                            )))
1342                        }
1343                    },
1344                    other => {
1345                        return Err(Fault::new(format!(
1346                            "`?` needs a Result/Option, got {}",
1347                            other.type_name()
1348                        )))
1349                    }
1350                }
1351            }
1352            Instr::Fault(msg_k) => {
1353                let msg = self.name_const(st, msg_k)?;
1354                return Err(Fault::new(msg));
1355            }
1356            Instr::Pop => {
1357                pop!();
1358            }
1359            Instr::Dup => {
1360                let v = st
1361                    .stack
1362                    .last()
1363                    .cloned()
1364                    .ok_or_else(|| Fault::new("stack underflow"))?;
1365                st.stack.push(v);
1366            }
1367            Instr::IterNext { iter, idx, end } => {
1368                let frame = st.frames.last_mut().unwrap();
1369                let i = match &frame.locals[idx as usize] {
1370                    Value::Int(i) => *i,
1371                    other => {
1372                        return Err(Fault::new(format!(
1373                            "loop index corrupted: {}",
1374                            other.type_name()
1375                        )))
1376                    }
1377                };
1378                let item: Option<Value> = match &frame.locals[iter as usize] {
1379                    Value::List(items) | Value::Tuple(items) => items.get(i as usize).cloned(),
1380                    Value::Range(a, b, inc) => {
1381                        let v = a + i;
1382                        let ok = if *inc { v <= *b } else { v < *b };
1383                        if ok {
1384                            Some(Value::Int(v))
1385                        } else {
1386                            None
1387                        }
1388                    }
1389                    Value::Str(s) => s.chars().nth(i as usize).map(|c| Value::str(c.to_string())),
1390                    other => {
1391                        return Err(Fault::new(format!(
1392                            "cannot iterate over {}",
1393                            other.type_name()
1394                        )))
1395                    }
1396                };
1397                match item {
1398                    Some(v) => {
1399                        frame.locals[idx as usize] = Value::Int(i + 1);
1400                        st.stack.push(v);
1401                    }
1402                    None => frame.ip = end,
1403                }
1404            }
1405        }
1406        Ok(None)
1407    }
1408
1409    fn do_call(
1410        &mut self,
1411        st: &mut VmState,
1412        callee: Value,
1413        args: Vec<Value>,
1414        tail: bool,
1415    ) -> Result<(), Fault> {
1416        match callee {
1417            Value::Closure(c) => {
1418                let proto = self
1419                    .fns
1420                    .get(c.fn_id as usize)
1421                    .cloned()
1422                    .ok_or_else(|| Fault::new(format!("unknown fn id {}", c.fn_id)))?;
1423                let mut frame = make_frame(c.fn_id, proto, args)?;
1424                frame.upvals = c.upvals.clone();
1425                if tail {
1426                    st.frames.pop();
1427                }
1428                st.frames.push(frame);
1429                Ok(())
1430            }
1431            Value::NativeFn(id) => {
1432                let f = self
1433                    .natives
1434                    .get(id as usize)
1435                    .ok_or_else(|| Fault::new(format!("unknown native fn id {}", id)))?
1436                    .f
1437                    .clone();
1438                let result = f(self, args)?;
1439                if tail {
1440                    // behave like `return result`
1441                    st.frames.pop();
1442                    if st.frames.is_empty() {
1443                        st.status = Status::Done(result);
1444                        return Ok(());
1445                    }
1446                }
1447                st.stack.push(result);
1448                Ok(())
1449            }
1450            other => Err(Fault::new(format!(
1451                "value of type {} is not callable",
1452                other.type_name()
1453            ))),
1454        }
1455    }
1456
1457    pub(crate) fn get_field(&mut self, recv: &Value, name: &str) -> Result<Value, Fault> {
1458        match recv {
1459            Value::Record(r) => r
1460                .get(name)
1461                .cloned()
1462                .ok_or_else(|| Fault::new(format!("record has no field `{}`", name))),
1463            Value::Variant(v) => match &v.payload {
1464                VariantPayload::Named(fields) => fields.get(name).cloned().ok_or_else(|| {
1465                    Fault::new(format!("variant {} has no field `{}`", v.tag, name))
1466                }),
1467                _ => Err(Fault::new(format!("variant {} has no named fields", v.tag))),
1468            },
1469            Value::Atom(a) if name == "value" => Ok(a.value.read().clone()),
1470            Value::Native(n) => {
1471                let key = (n.type_name.to_string(), name.to_string());
1472                match self.getters.get(&key).cloned() {
1473                    Some(g) => g(self, recv),
1474                    None => Err(Fault::new(format!(
1475                        "native type {} has no registered field `{}`",
1476                        n.type_name, name
1477                    ))),
1478                }
1479            }
1480            other => Err(Fault::new(format!("{} has no fields", other.type_name()))),
1481        }
1482    }
1483
1484    fn name_const(&self, st: &VmState, k: u32) -> Result<String, Fault> {
1485        let frame = st.frames.last().ok_or_else(|| Fault::new("no frame"))?;
1486        match &frame.proto.consts[k as usize] {
1487            Const::Name(s) | Const::Str(s) => Ok(s.clone()),
1488            c => Err(Fault::new(format!("expected name const, got {:?}", c))),
1489        }
1490    }
1491
1492    fn names_const(&self, st: &VmState, k: u32) -> Result<Vec<String>, Fault> {
1493        let frame = st.frames.last().ok_or_else(|| Fault::new("no frame"))?;
1494        match &frame.proto.consts[k as usize] {
1495            Const::Names(s) => Ok(s.clone()),
1496            c => Err(Fault::new(format!("expected names const, got {:?}", c))),
1497        }
1498    }
1499
1500    // ---------- atoms ----------
1501
1502    pub fn make_atom(&mut self, v: Value) -> Value {
1503        let id = self.atom_counter;
1504        self.atom_counter += 1;
1505        let cell = Sh::new(AtomCell {
1506            id,
1507            value: Lock::new(v),
1508            watchers: Lock::new(Vec::new()),
1509        });
1510        self.atoms.push(Sh::downgrade(&cell));
1511        Value::Atom(cell)
1512    }
1513
1514    pub(crate) fn fire_watchers(
1515        &mut self,
1516        atom: &Sh<AtomCell>,
1517        old: Value,
1518        new: Value,
1519    ) -> Result<(), Fault> {
1520        let watchers: Vec<Value> = atom
1521            .watchers
1522            .read()
1523            .iter()
1524            .map(|(_, f)| f.clone())
1525            .collect();
1526        for w in watchers {
1527            self.call_value(&w, vec![old.clone(), new.clone()])?;
1528        }
1529        Ok(())
1530    }
1531
1532    pub fn live_atoms(&mut self) -> Vec<Sh<AtomCell>> {
1533        self.atoms.retain(|w| w.strong_count() > 0);
1534        self.atoms.iter().filter_map(|w| w.upgrade()).collect()
1535    }
1536}
1537
1538impl Default for Funct {
1539    fn default() -> Self {
1540        Funct::new()
1541    }
1542}
1543
1544fn make_frame(fn_id: u32, proto: Sh<FnProto>, args: Vec<Value>) -> Result<Frame, Fault> {
1545    if args.len() != proto.arity as usize {
1546        return Err(Fault::new(format!(
1547            "{} expects {} argument(s), got {}",
1548            proto.name,
1549            proto.arity,
1550            args.len()
1551        )));
1552    }
1553    let mut locals = args;
1554    locals.resize(proto.num_locals as usize, Value::Unit);
1555    Ok(Frame {
1556        fn_id,
1557        proto,
1558        ip: 0,
1559        locals,
1560        upvals: vec![],
1561    })
1562}
1563
1564fn pop_n(stack: &mut Vec<Value>, n: usize) -> Result<Vec<Value>, Fault> {
1565    if stack.len() < n {
1566        return Err(Fault::new("operand stack underflow"));
1567    }
1568    Ok(stack.split_off(stack.len() - n))
1569}
1570
1571enum BinKind {
1572    Add,
1573    Sub,
1574    Mul,
1575    Div,
1576    Mod,
1577    Pow,
1578}
1579
1580fn bin_op(stack: &mut Vec<Value>, kind: BinKind) -> Result<(), Fault> {
1581    let b = stack
1582        .pop()
1583        .ok_or_else(|| Fault::new("operand stack underflow"))?;
1584    let a = stack
1585        .pop()
1586        .ok_or_else(|| Fault::new("operand stack underflow"))?;
1587    use Value::*;
1588    let v = match (&kind, &a, &b) {
1589        // string concat
1590        (BinKind::Add, Str(x), Str(y)) => Value::str(format!("{}{}", x, y)),
1591        // list concat
1592        (BinKind::Add, List(x), List(y)) => {
1593            let mut items = (**x).clone();
1594            items.extend(y.iter().cloned());
1595            Value::list_v(items)
1596        }
1597        (_, Int(x), Int(y)) => match kind {
1598            BinKind::Add => Int(x
1599                .checked_add(*y)
1600                .ok_or_else(|| Fault::new("integer overflow in +"))?),
1601            BinKind::Sub => Int(x
1602                .checked_sub(*y)
1603                .ok_or_else(|| Fault::new("integer overflow in -"))?),
1604            BinKind::Mul => Int(x
1605                .checked_mul(*y)
1606                .ok_or_else(|| Fault::new("integer overflow in *"))?),
1607            BinKind::Div => {
1608                if *y == 0 {
1609                    return Err(Fault::new("division by zero"));
1610                }
1611                Int(x / y)
1612            }
1613            BinKind::Mod => {
1614                if *y == 0 {
1615                    return Err(Fault::new("modulo by zero"));
1616                }
1617                Int(x % y)
1618            }
1619            BinKind::Pow => {
1620                if *y >= 0 {
1621                    let e: u32 = (*y)
1622                        .try_into()
1623                        .map_err(|_| Fault::new("exponent too large"))?;
1624                    Int(x
1625                        .checked_pow(e)
1626                        .ok_or_else(|| Fault::new("integer overflow in **"))?)
1627                } else {
1628                    Float((*x as f64).powi(*y as i32))
1629                }
1630            }
1631        },
1632        (_, a, b) => {
1633            let (x, y) = match (as_f64(a), as_f64(b)) {
1634                (Some(x), Some(y)) => (x, y),
1635                _ => {
1636                    let op = match kind {
1637                        BinKind::Add => "+",
1638                        BinKind::Sub => "-",
1639                        BinKind::Mul => "*",
1640                        BinKind::Div => "/",
1641                        BinKind::Mod => "%",
1642                        BinKind::Pow => "**",
1643                    };
1644                    return Err(Fault::new(format!(
1645                        "cannot apply `{}` to {} and {}",
1646                        op,
1647                        a.type_name(),
1648                        b.type_name()
1649                    )));
1650                }
1651            };
1652            match kind {
1653                BinKind::Add => Float(x + y),
1654                BinKind::Sub => Float(x - y),
1655                BinKind::Mul => Float(x * y),
1656                BinKind::Div => {
1657                    if y == 0.0 {
1658                        return Err(Fault::new("division by zero"));
1659                    }
1660                    Float(x / y)
1661                }
1662                BinKind::Mod => {
1663                    if y == 0.0 {
1664                        return Err(Fault::new("modulo by zero"));
1665                    }
1666                    Float(x % y)
1667                }
1668                BinKind::Pow => Float(x.powf(y)),
1669            }
1670        }
1671    };
1672    stack.push(v);
1673    Ok(())
1674}
1675
1676fn as_f64(v: &Value) -> Option<f64> {
1677    match v {
1678        Value::Int(i) => Some(*i as f64),
1679        Value::Float(f) => Some(*f),
1680        _ => None,
1681    }
1682}
1683
1684fn cmp_op(stack: &mut Vec<Value>, test: fn(std::cmp::Ordering) -> bool) -> Result<(), Fault> {
1685    let b = stack
1686        .pop()
1687        .ok_or_else(|| Fault::new("operand stack underflow"))?;
1688    let a = stack
1689        .pop()
1690        .ok_or_else(|| Fault::new("operand stack underflow"))?;
1691    let ord = match (&a, &b) {
1692        (Value::Str(x), Value::Str(y)) => x.cmp(y),
1693        (x, y) => match (as_f64(x), as_f64(y)) {
1694            (Some(x), Some(y)) => x
1695                .partial_cmp(&y)
1696                .ok_or_else(|| Fault::new("cannot compare NaN"))?,
1697            _ => {
1698                return Err(Fault::new(format!(
1699                    "cannot compare {} and {}",
1700                    a.type_name(),
1701                    b.type_name()
1702                )))
1703            }
1704        },
1705    };
1706    stack.push(Value::Bool(test(ord)));
1707    Ok(())
1708}
1709
1710fn index_value(recv: &Value, idx: &Value) -> Result<Value, Fault> {
1711    match (recv, idx) {
1712        (Value::List(items), Value::Int(i)) | (Value::Tuple(items), Value::Int(i)) => {
1713            let i = *i;
1714            if i < 0 || i as usize >= items.len() {
1715                return Err(Fault::new(format!(
1716                    "index {} out of bounds (length {})",
1717                    i,
1718                    items.len()
1719                )));
1720            }
1721            Ok(items[i as usize].clone())
1722        }
1723        (Value::Record(r), Value::Str(k)) => r
1724            .get(&**k)
1725            .cloned()
1726            .ok_or_else(|| Fault::new(format!("record has no field `{}`", k))),
1727        (Value::Str(s), Value::Int(i)) => {
1728            let c = s
1729                .chars()
1730                .nth(*i as usize)
1731                .ok_or_else(|| Fault::new(format!("string index {} out of bounds", i)))?;
1732            Ok(Value::str(c.to_string()))
1733        }
1734        (r, i) => Err(Fault::new(format!(
1735            "cannot index {} with {}",
1736            r.type_name(),
1737            i.type_name()
1738        ))),
1739    }
1740}
1741
1742/// Try to match `pat` against `v`, writing bindings into `locals`.
1743pub fn match_pat(pat: &Pat, v: &Value, locals: &mut Vec<Value>) -> bool {
1744    match pat {
1745        Pat::Wildcard => true,
1746        Pat::Bind(slot) => {
1747            locals[*slot as usize] = v.clone();
1748            true
1749        }
1750        Pat::LitInt(i) => v == &Value::Int(*i),
1751        Pat::LitFloat(f) => v == &Value::Float(*f),
1752        Pat::LitStr(s) => matches!(v, Value::Str(x) if &**x == s.as_str()),
1753        Pat::LitBool(b) => v == &Value::Bool(*b),
1754        Pat::LitUnit => matches!(v, Value::Unit),
1755        Pat::VariantPos { tag, items } => match v {
1756            Value::Variant(var) if var.tag == *tag => match &var.payload {
1757                VariantPayload::Unit => items.is_empty(),
1758                VariantPayload::Positional(vals) => {
1759                    vals.len() == items.len()
1760                        && items.iter().zip(vals).all(|(p, x)| match_pat(p, x, locals))
1761                }
1762                VariantPayload::Named(_) => false,
1763            },
1764            _ => false,
1765        },
1766        Pat::VariantNamed { tag, fields, rest } => match v {
1767            Value::Variant(var) if var.tag == *tag => match &var.payload {
1768                VariantPayload::Named(vals) => match_fields(fields, *rest, vals, locals),
1769                _ => false,
1770            },
1771            _ => false,
1772        },
1773        Pat::Record { fields, rest } => match v {
1774            Value::Record(vals) => match_fields(fields, *rest, vals, locals),
1775            _ => false,
1776        },
1777        Pat::Tuple(items) => match v {
1778            Value::Tuple(vals) => {
1779                vals.len() == items.len()
1780                    && items
1781                        .iter()
1782                        .zip(vals.iter())
1783                        .all(|(p, x)| match_pat(p, x, locals))
1784            }
1785            _ => false,
1786        },
1787        Pat::List { items, rest } => match v {
1788            Value::List(vals) => match rest {
1789                None => {
1790                    vals.len() == items.len()
1791                        && items
1792                            .iter()
1793                            .zip(vals.iter())
1794                            .all(|(p, x)| match_pat(p, x, locals))
1795                }
1796                Some(rest_bind) => {
1797                    if vals.len() < items.len() {
1798                        return false;
1799                    }
1800                    if !items
1801                        .iter()
1802                        .zip(vals.iter())
1803                        .all(|(p, x)| match_pat(p, x, locals))
1804                    {
1805                        return false;
1806                    }
1807                    if let Some(slot) = rest_bind {
1808                        let rest_items: Vec<Value> =
1809                            vals.iter().skip(items.len()).cloned().collect();
1810                        locals[*slot as usize] = Value::list(rest_items);
1811                    }
1812                    true
1813                }
1814            },
1815            _ => false,
1816        },
1817        Pat::Range { lo, hi, inclusive } => match v {
1818            Value::Int(i) => {
1819                if *inclusive {
1820                    i >= lo && i <= hi
1821                } else {
1822                    i >= lo && i < hi
1823                }
1824            }
1825            _ => false,
1826        },
1827        Pat::Or(alts) => alts.iter().any(|p| match_pat(p, v, locals)),
1828        Pat::As(inner, slot) => {
1829            if match_pat(inner, v, locals) {
1830                locals[*slot as usize] = v.clone();
1831                true
1832            } else {
1833                false
1834            }
1835        }
1836    }
1837}
1838
1839/// Field lookup shared by record patterns (`FMap`) and named-variant patterns
1840/// (still a plain `BTreeMap`), so the matcher works on both.
1841trait FieldMap {
1842    fn flen(&self) -> usize;
1843    fn fget(&self, k: &str) -> Option<&Value>;
1844}
1845impl FieldMap for BTreeMap<String, Value> {
1846    fn flen(&self) -> usize {
1847        self.len()
1848    }
1849    fn fget(&self, k: &str) -> Option<&Value> {
1850        self.get(k)
1851    }
1852}
1853impl FieldMap for crate::value::FMap {
1854    fn flen(&self) -> usize {
1855        self.len()
1856    }
1857    fn fget(&self, k: &str) -> Option<&Value> {
1858        self.get(k)
1859    }
1860}
1861
1862fn match_fields(
1863    fields: &[(String, Pat)],
1864    rest: bool,
1865    vals: &impl FieldMap,
1866    locals: &mut Vec<Value>,
1867) -> bool {
1868    if !rest && vals.flen() != fields.len() {
1869        return false;
1870    }
1871    for (name, p) in fields {
1872        match vals.fget(name) {
1873            Some(x) => {
1874                if !match_pat(p, x, locals) {
1875                    return false;
1876                }
1877            }
1878            None => return false,
1879        }
1880    }
1881    true
1882}