Skip to main content

zapcode_core/
snapshot.rs

1use std::collections::{HashMap, HashSet};
2
3use serde::{Deserialize, Serialize};
4
5use crate::compiler::CompiledProgram;
6use crate::error::{Result, ZapcodeError};
7use crate::sandbox::ResourceLimits;
8use crate::value::Value;
9use crate::vm::{CallFrame, Continuation, TryInfo, Vm, VmState};
10
11/// Internal serializable representation of VM state at a suspension point.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13struct VmSnapshot {
14    program: CompiledProgram,
15    stack: Vec<Value>,
16    frames: Vec<CallFrame>,
17    /// User-defined globals only — builtins are re-registered on resume.
18    globals: Vec<(String, Value)>,
19    try_stack: Vec<TryInfo>,
20    continuations: Vec<Continuation>,
21    stdout: String,
22    limits: ResourceLimits,
23    external_functions: Vec<String>,
24}
25
26/// A snapshot of VM state at a suspension point.
27/// Can be serialized to bytes and resumed later in any process.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ZapcodeSnapshot {
30    data: Vec<u8>,
31}
32
33impl ZapcodeSnapshot {
34    /// Capture the current VM state as a snapshot.
35    pub(crate) fn capture(vm: &Vm) -> Result<Self> {
36        // Filter out builtin globals — they'll be re-registered on resume.
37        let builtin_names: HashSet<&str> = Vm::BUILTIN_GLOBAL_NAMES.iter().copied().collect();
38        let user_globals: Vec<(String, Value)> = vm
39            .globals
40            .iter()
41            .filter(|(k, _)| !builtin_names.contains(k.as_str()))
42            .map(|(k, v)| (k.clone(), v.clone()))
43            .collect();
44
45        let snapshot = VmSnapshot {
46            program: vm.program.clone(),
47            stack: vm.stack.clone(),
48            frames: vm.frames.clone(),
49            globals: user_globals,
50            try_stack: vm.try_stack.clone(),
51            continuations: vm.continuations.clone(),
52            stdout: vm.stdout.clone(),
53            limits: vm.limits.clone(),
54            external_functions: vm.external_functions.iter().cloned().collect(),
55        };
56
57        let data = postcard::to_allocvec(&snapshot)
58            .map_err(|e| ZapcodeError::SnapshotError(format!("capture failed: {}", e)))?;
59
60        Ok(Self { data })
61    }
62
63    /// Serialize the snapshot to bytes for storage / transport.
64    pub fn dump(&self) -> Result<Vec<u8>> {
65        postcard::to_allocvec(self)
66            .map_err(|e| ZapcodeError::SnapshotError(format!("dump failed: {}", e)))
67    }
68
69    /// Deserialize a snapshot from bytes.
70    pub fn load(bytes: &[u8]) -> Result<Self> {
71        postcard::from_bytes(bytes)
72            .map_err(|e| ZapcodeError::SnapshotError(format!("load failed: {}", e)))
73    }
74
75    /// Resume execution with a return value from the external function.
76    /// Returns a `VmState` which may be `Complete` or another `Suspended`.
77    pub fn resume(self, return_value: Value) -> Result<VmState> {
78        let vm_snap: VmSnapshot = postcard::from_bytes(&self.data)
79            .map_err(|e| ZapcodeError::SnapshotError(format!("resume decode failed: {}", e)))?;
80
81        let user_globals: HashMap<String, Value> = vm_snap.globals.into_iter().collect();
82        let ext_set: HashSet<String> = vm_snap.external_functions.into_iter().collect();
83
84        let mut vm = Vm::from_snapshot(
85            vm_snap.program,
86            vm_snap.stack,
87            vm_snap.frames,
88            user_globals,
89            vm_snap.try_stack,
90            vm_snap.continuations,
91            vm_snap.stdout,
92            vm_snap.limits,
93            ext_set,
94        );
95
96        // Push the return value onto the stack — this is the result the
97        // `CallExternal` instruction was waiting for.
98        vm.stack.push(return_value);
99
100        vm.resume_execution()
101    }
102}