Skip to main content

stet_core/
save_stack.rs

1// stet - A PostScript Interpreter
2// Copyright (c) 2026 Scott Bowman
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Save/restore stack for PostScript VM persistence.
6//!
7//! Implements copy-on-write save/restore: `save` records a level, mutations
8//! create COW copies, and `restore` swaps offsets to revert changes.
9
10use crate::graphics_state::{GraphicsState, GstateEntry};
11use crate::object::EntityId;
12
13/// Which store type a save record refers to.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum StoreType {
16    String,
17    Array,
18    Dict,
19}
20
21/// Records one COW copy made during a save level.
22#[derive(Debug, Clone)]
23pub struct SaveRecord {
24    /// The original entity that was COW-copied.
25    pub src: EntityId,
26    /// The backup entity holding the pre-mutation data.
27    pub copy: EntityId,
28    /// Which store the entities belong to.
29    pub store_type: StoreType,
30}
31
32/// High-water marks of **local** VM at the moment of a `save`.
33///
34/// `restore` truncates each local store back to these marks, which is what
35/// makes the memory a save level allocated actually available again. Global VM
36/// is never affected by save/restore, so it has no marks here.
37#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
38pub struct VmMarks {
39    pub string_data: usize,
40    pub string_entities: usize,
41    pub array_data: usize,
42    pub array_entities: usize,
43    pub dict_slots: usize,
44    pub dict_entities: usize,
45}
46
47/// Interpreter state captured by `save` and reinstated by `restore`.
48///
49/// Grouped into a struct rather than passed positionally: every field is
50/// state the interpreter has to rewind, and the set grows as more of the
51/// interpreter learns to participate in save/restore.
52pub struct SaveSnapshot {
53    /// `d_stack` length at save time (for restore validation).
54    pub d_stack_depth: usize,
55    /// Packing mode (`setpacking`/`currentpacking`).
56    pub packing_mode: bool,
57    /// VM allocation mode (`setglobal`/`currentglobal`).
58    pub vm_alloc_mode: bool,
59    /// Binary object format (`setobjectformat`/`currentobjectformat`).
60    pub object_format: i32,
61    /// Graphics state and graphics state stack.
62    pub gstate: GraphicsState,
63    pub gstate_stack: Vec<GstateEntry>,
64    /// `Context::gstate_store` length at save time.
65    ///
66    /// `gstate_store` backs `PsValue::Gstate`, and a `gstate` object is a
67    /// composite in local VM, so `restore` has to reclaim the ones created
68    /// after the save. Without this the store would grow monotonically and
69    /// each stale slot would keep naming entities the restore released.
70    pub gstate_store_len: usize,
71    /// Local-VM high-water marks, for reclamation on restore.
72    pub marks: VmMarks,
73}
74
75/// One save level's state.
76pub struct SaveLevel {
77    /// Numeric level (1-based, 0 = no save active).
78    pub level: u16,
79    /// Unique save id for invalidation tracking.
80    pub save_id: u32,
81    /// COW records accumulated during this save level.
82    pub records: Vec<SaveRecord>,
83    /// Whether this save level is still valid (becomes false on restore).
84    pub valid: bool,
85    /// Snapshot of d_stack length at save time (for restore validation).
86    pub d_stack_depth: usize,
87    /// Saved packing mode (`setpacking`/`currentpacking`).
88    pub packing_mode: bool,
89    /// Saved VM allocation mode (`setglobal`/`currentglobal`).
90    pub vm_alloc_mode: bool,
91    /// Saved binary object format (`setobjectformat`/`currentobjectformat`).
92    pub object_format: i32,
93    /// Saved graphics state and graphics state stack.
94    pub gstate: GraphicsState,
95    pub gstate_stack: Vec<GstateEntry>,
96    /// `Context::gstate_store` length at save time.
97    ///
98    /// `gstate_store` backs `PsValue::Gstate`, and a `gstate` object is a
99    /// composite in local VM, so `restore` has to reclaim the ones created
100    /// after the save. Without this the store would grow monotonically and
101    /// each stale slot would keep naming entities the restore released.
102    pub gstate_store_len: usize,
103    /// Local-VM high-water marks, for reclamation on restore.
104    pub marks: VmMarks,
105}
106
107/// The save/restore stack.
108pub struct SaveStack {
109    levels: Vec<SaveLevel>,
110    next_save_id: u32,
111}
112
113impl SaveStack {
114    /// Create an empty save stack.
115    pub fn new() -> Self {
116        Self {
117            levels: Vec::new(),
118            next_save_id: 1,
119        }
120    }
121
122    /// Push a new save level. Returns `(level, save_id)`.
123    pub fn save(&mut self, snapshot: SaveSnapshot) -> (u16, u32) {
124        let SaveSnapshot {
125            d_stack_depth,
126            packing_mode,
127            vm_alloc_mode,
128            object_format,
129            gstate,
130            gstate_stack,
131            gstate_store_len,
132            marks,
133        } = snapshot;
134        let level = (self.levels.len() + 1) as u16;
135        let save_id = self.next_save_id;
136        self.next_save_id += 1;
137        self.levels.push(SaveLevel {
138            level,
139            save_id,
140            records: Vec::new(),
141            valid: true,
142            d_stack_depth,
143            packing_mode,
144            vm_alloc_mode,
145            object_format,
146            gstate,
147            gstate_stack,
148            gstate_store_len,
149            marks,
150        });
151        (level, save_id)
152    }
153
154    /// Add a COW record to the current save level.
155    pub fn add_record(&mut self, record: SaveRecord) {
156        if let Some(level) = self.levels.last_mut() {
157            level.records.push(record);
158        }
159    }
160
161    /// Pop the topmost save level, returning its records for restore processing.
162    /// Returns `None` if the stack is empty.
163    pub fn restore(&mut self) -> Option<SaveLevel> {
164        self.levels.pop()
165    }
166
167    /// Pop all save levels from `save_id` upward (inclusive), returning them
168    /// in stack order (target level first, newest level last).
169    /// Per PLRM, `restore` can target any valid save — not just the topmost.
170    /// All newer saves are invalidated and their COW records are also returned
171    /// so they can be undone.
172    pub fn restore_to(&mut self, save_id: u32) -> Option<Vec<SaveLevel>> {
173        let idx = self.levels.iter().position(|l| l.save_id == save_id)?;
174        let popped: Vec<SaveLevel> = self.levels.drain(idx..).collect();
175        Some(popped)
176    }
177
178    /// Current save level (0 if no save active).
179    pub fn current_level(&self) -> u16 {
180        self.levels.last().map(|l| l.level).unwrap_or(0)
181    }
182
183    /// Save ID of the most recent save (0 if no save active).
184    /// Used for entity creation tracking (invalidrestore).
185    pub fn last_save_id(&self) -> u32 {
186        self.levels.last().map(|l| l.save_id).unwrap_or(0)
187    }
188
189    /// Check if a save_id is valid (exists and not invalidated).
190    pub fn is_valid(&self, save_id: u32) -> bool {
191        self.levels.iter().any(|l| l.save_id == save_id && l.valid)
192    }
193
194    /// Number of active save levels.
195    pub fn depth(&self) -> usize {
196        self.levels.len()
197    }
198
199    /// Read-only access to the levels (for validation checks).
200    pub fn levels_ref(&self) -> &[SaveLevel] {
201        &self.levels
202    }
203
204    /// Invalidate all save levels newer than the given save_id.
205    pub fn invalidate_newer(&mut self, save_id: u32) {
206        let mut found = false;
207        for level in &mut self.levels {
208            if found {
209                level.valid = false;
210            }
211            if level.save_id == save_id {
212                found = true;
213            }
214        }
215    }
216}
217
218impl Default for SaveStack {
219    fn default() -> Self {
220        Self::new()
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn test_save_and_depth() {
230        let mut ss = SaveStack::new();
231        assert_eq!(ss.depth(), 0);
232        assert_eq!(ss.current_level(), 0);
233
234        let (level, id) = ss.save(SaveSnapshot {
235            d_stack_depth: 3,
236            packing_mode: false,
237            vm_alloc_mode: false,
238            object_format: 0,
239            gstate: GraphicsState::new(),
240            gstate_stack: Vec::new(),
241            gstate_store_len: 0,
242            marks: VmMarks::default(),
243        });
244        assert_eq!(level, 1);
245        assert_eq!(id, 1);
246        assert_eq!(ss.depth(), 1);
247        assert_eq!(ss.current_level(), 1);
248    }
249
250    #[test]
251    fn test_nested_save() {
252        let mut ss = SaveStack::new();
253        let (l1, _) = ss.save(SaveSnapshot {
254            d_stack_depth: 3,
255            packing_mode: false,
256            vm_alloc_mode: false,
257            object_format: 0,
258            gstate: GraphicsState::new(),
259            gstate_stack: Vec::new(),
260            gstate_store_len: 0,
261            marks: VmMarks::default(),
262        });
263        let (l2, _) = ss.save(SaveSnapshot {
264            d_stack_depth: 3,
265            packing_mode: false,
266            vm_alloc_mode: false,
267            object_format: 0,
268            gstate: GraphicsState::new(),
269            gstate_stack: Vec::new(),
270            gstate_store_len: 0,
271            marks: VmMarks::default(),
272        });
273        assert_eq!(l1, 1);
274        assert_eq!(l2, 2);
275        assert_eq!(ss.depth(), 2);
276        assert_eq!(ss.current_level(), 2);
277    }
278
279    #[test]
280    fn test_restore() {
281        let mut ss = SaveStack::new();
282        let (_, id1) = ss.save(SaveSnapshot {
283            d_stack_depth: 3,
284            packing_mode: false,
285            vm_alloc_mode: false,
286            object_format: 0,
287            gstate: GraphicsState::new(),
288            gstate_stack: Vec::new(),
289            gstate_store_len: 0,
290            marks: VmMarks::default(),
291        });
292        ss.add_record(SaveRecord {
293            src: EntityId(0),
294            copy: EntityId(1),
295            store_type: StoreType::String,
296        });
297
298        let level = ss.restore().unwrap();
299        assert_eq!(level.save_id, id1);
300        assert_eq!(level.records.len(), 1);
301        assert_eq!(ss.depth(), 0);
302    }
303
304    #[test]
305    fn test_is_valid() {
306        let mut ss = SaveStack::new();
307        let (_, id1) = ss.save(SaveSnapshot {
308            d_stack_depth: 3,
309            packing_mode: false,
310            vm_alloc_mode: false,
311            object_format: 0,
312            gstate: GraphicsState::new(),
313            gstate_stack: Vec::new(),
314            gstate_store_len: 0,
315            marks: VmMarks::default(),
316        });
317        assert!(ss.is_valid(id1));
318        ss.restore();
319        assert!(!ss.is_valid(id1));
320    }
321
322    #[test]
323    fn test_invalidate_newer() {
324        let mut ss = SaveStack::new();
325        let (_, id1) = ss.save(SaveSnapshot {
326            d_stack_depth: 3,
327            packing_mode: false,
328            vm_alloc_mode: false,
329            object_format: 0,
330            gstate: GraphicsState::new(),
331            gstate_stack: Vec::new(),
332            gstate_store_len: 0,
333            marks: VmMarks::default(),
334        });
335        let (_, id2) = ss.save(SaveSnapshot {
336            d_stack_depth: 3,
337            packing_mode: false,
338            vm_alloc_mode: false,
339            object_format: 0,
340            gstate: GraphicsState::new(),
341            gstate_stack: Vec::new(),
342            gstate_store_len: 0,
343            marks: VmMarks::default(),
344        });
345        let (_, id3) = ss.save(SaveSnapshot {
346            d_stack_depth: 3,
347            packing_mode: false,
348            vm_alloc_mode: false,
349            object_format: 0,
350            gstate: GraphicsState::new(),
351            gstate_stack: Vec::new(),
352            gstate_store_len: 0,
353            marks: VmMarks::default(),
354        });
355
356        ss.invalidate_newer(id1);
357        assert!(ss.is_valid(id1));
358        assert!(!ss.is_valid(id2));
359        assert!(!ss.is_valid(id3));
360    }
361
362    #[test]
363    fn test_add_record_to_current() {
364        let mut ss = SaveStack::new();
365        ss.save(SaveSnapshot {
366            d_stack_depth: 3,
367            packing_mode: false,
368            vm_alloc_mode: false,
369            object_format: 0,
370            gstate: GraphicsState::new(),
371            gstate_stack: Vec::new(),
372            gstate_store_len: 0,
373            marks: VmMarks::default(),
374        });
375        ss.add_record(SaveRecord {
376            src: EntityId(0),
377            copy: EntityId(1),
378            store_type: StoreType::Array,
379        });
380        ss.add_record(SaveRecord {
381            src: EntityId(2),
382            copy: EntityId(3),
383            store_type: StoreType::Dict,
384        });
385
386        let level = ss.restore().unwrap();
387        assert_eq!(level.records.len(), 2);
388    }
389
390    #[test]
391    fn test_restore_empty() {
392        let mut ss = SaveStack::new();
393        assert!(ss.restore().is_none());
394    }
395
396    #[test]
397    fn test_d_stack_depth_snapshot() {
398        let mut ss = SaveStack::new();
399        ss.save(SaveSnapshot {
400            d_stack_depth: 5,
401            packing_mode: false,
402            vm_alloc_mode: false,
403            object_format: 0,
404            gstate: GraphicsState::new(),
405            gstate_stack: Vec::new(),
406            gstate_store_len: 0,
407            marks: VmMarks::default(),
408        });
409        let level = ss.restore().unwrap();
410        assert_eq!(level.d_stack_depth, 5);
411    }
412
413    #[test]
414    fn test_unique_save_ids() {
415        let mut ss = SaveStack::new();
416        let (_, id1) = ss.save(SaveSnapshot {
417            d_stack_depth: 3,
418            packing_mode: false,
419            vm_alloc_mode: false,
420            object_format: 0,
421            gstate: GraphicsState::new(),
422            gstate_stack: Vec::new(),
423            gstate_store_len: 0,
424            marks: VmMarks::default(),
425        });
426        let (_, id2) = ss.save(SaveSnapshot {
427            d_stack_depth: 3,
428            packing_mode: false,
429            vm_alloc_mode: false,
430            object_format: 0,
431            gstate: GraphicsState::new(),
432            gstate_stack: Vec::new(),
433            gstate_store_len: 0,
434            marks: VmMarks::default(),
435        });
436        ss.restore();
437        let (_, id3) = ss.save(SaveSnapshot {
438            d_stack_depth: 3,
439            packing_mode: false,
440            vm_alloc_mode: false,
441            object_format: 0,
442            gstate: GraphicsState::new(),
443            gstate_stack: Vec::new(),
444            gstate_store_len: 0,
445            marks: VmMarks::default(),
446        });
447        assert_ne!(id1, id2);
448        assert_ne!(id2, id3);
449        assert_ne!(id1, id3);
450    }
451
452    #[test]
453    fn test_save_level_numbers() {
454        let mut ss = SaveStack::new();
455        let (l1, _) = ss.save(SaveSnapshot {
456            d_stack_depth: 3,
457            packing_mode: false,
458            vm_alloc_mode: false,
459            object_format: 0,
460            gstate: GraphicsState::new(),
461            gstate_stack: Vec::new(),
462            gstate_store_len: 0,
463            marks: VmMarks::default(),
464        });
465        let (l2, _) = ss.save(SaveSnapshot {
466            d_stack_depth: 3,
467            packing_mode: false,
468            vm_alloc_mode: false,
469            object_format: 0,
470            gstate: GraphicsState::new(),
471            gstate_stack: Vec::new(),
472            gstate_store_len: 0,
473            marks: VmMarks::default(),
474        });
475        ss.restore();
476        // After restoring level 2, next save should be level 2 again
477        let (l3, _) = ss.save(SaveSnapshot {
478            d_stack_depth: 3,
479            packing_mode: false,
480            vm_alloc_mode: false,
481            object_format: 0,
482            gstate: GraphicsState::new(),
483            gstate_stack: Vec::new(),
484            gstate_store_len: 0,
485            marks: VmMarks::default(),
486        });
487        assert_eq!(l1, 1);
488        assert_eq!(l2, 2);
489        assert_eq!(l3, 2);
490    }
491}