kcl_lib/execution/
memory.rs

1//! Representation of KCL memory.
2//!
3//! Stores `KclValue`s by name using dynamic scoping. Memory does not support addresses or references,
4//! so all values must be self-contained. Memory is essentially a map from `String`s to `KclValue`s.
5//! `KclValue`s are entirely opaque to this module. Memory is global and there should be only
6//! one per execution. It has no explicit support for caching between executions.
7//!
8//! Memory is mostly immutable (since KCL does not support mutation or reassignment). However, tags
9//! may change as code is executed and that mutates memory. Therefore to some extent,
10//! ProgramMemory supports mutability and does not rely on KCL's (mostly) immutable nature.
11//!
12//! ProgramMemory is observably monotonic, i.e., it only grows and even when we pop a stack frame,
13//! the frame is retained unless we can prove it is unreferenced. We remove some values which we
14//! know cannot be referenced, but we should in the future do better garbage collection (of values
15//! and envs).
16//!
17//! ## Concepts
18//!
19//! There are three main moving parts for ProgramMemory: environments, epochs, and stacks. I'll
20//! cover environments (and the call stack) first as if epochs didn't exist, then describe epochs.
21//!
22//! An environment is a set of bindings (i.e., a map from names to values). Environments handle
23//! both scoping and context switching. A new lexical scope means a new environment. Nesting of scopes
24//! means that environments form a tree, which is represented by parent pointers in the environments.
25//!
26//! Example:
27//!
28//! ```norun
29//! a = 10
30//!
31//! fn foo() {
32//!   b = a
33//!   a = 0
34//! }
35//! ```
36//!
37//! The body of `foo` has an environment whose parent is the enclosing scope. Variables in the inner
38//! scope can hide those in the outer scope (meaning `a` can be redefined in `foo`). Variables in the
39//! outer scope are visible from the inner scope. Note that `b` and the new `a` are not visible
40//! outside of `foo`.
41//!
42//! Nesting of environments is independent of the call stack. E.g., when `foo` is called, we push a
43//! new stack frame (which is an environment). The caller's env is on the stack and is not referenced
44//! by the new environment (i.e., variables in the caller's env are not visible from the callee).
45//!
46//! Note, however, that if a function is called from it's enclosing scope, then the outer env will
47//! be on the call stack and be the parent of the current env. Calling from a different scope will
48//! mean the call stack and parent env do not correspond.
49//!
50//! We use a new call stack for each module. When interpreting a module we start a new call stack
51//! with a new environment (though see below about std). Names imported from one module into another
52//! point into the envs from the exporting module's call stack (though once the module has been
53//! interpreted, those envs won't be on it's call stack any longer). A call stack is represented by
54//! a `Stack` object which references the global `ProgramMemory` object. Environments are stored in
55//! the global memory and the call stack is a stack of references. (See below on concurrent access
56//! using `Stack`s).
57//!
58//! When a function declaration is interpreted we create a value in memory (in the env in which it
59//! is declared) which contains the function's AST and a reference to the env where it is declared.
60//! When the function is called, a new environment is created with the saved reference as its parent
61//! and used for interpreting the function body. The return value is saved into this env. When the
62//! function returns the callee env is popped to resume execution in the caller's env.
63//!
64//! Now consider extending the above example:
65//!
66//! Example:
67//!
68//! ```norun
69//! a = 10
70//!
71//! fn foo() {
72//!   b = a
73//!   a = 0
74//! }
75//!
76//! c = 2
77//! ```
78//!
79//! `c` should not be visible inside foo and if `a` is modified after the declaration of `foo`, then
80//! the earlier value should be the one visible in `foo`, even if `foo` is called after (lexically or
81//! temporally) the definition of `c`. (Note that although KCL does not permit mutation, objects
82//! can change due to the way tags are implemented).
83//!
84//! To make this work, we have the concept of an epoch. An epoch is a simple, global, monotonic counter
85//! which is incremented at any significant moment in execution (we use the term snapshot). When a
86//! value is saved in memory we also save the epoch at which it was stored.
87//!
88//! When we save a reference to an enclosing scope we take a snapshot and save that epoch as part of
89//! the reference. When we call a function, we use the epoch when it was defined to look up variables,
90//! ignoring any variables which have a creation time later than the saved epoch.
91//!
92//! Because the callee could create new variables (with a creation time of the current epoch) which
93//! the callee should be able to read, we can't simply check the epoch with the callees (and we'd need
94//! to maintain a stack of callee epochs for further calls, etc.). Instead a stack frame consists of
95//! a reference to an environment and an epoch at which reads should take place. When we call a function
96//! this creates a new env using the current epoch, and it's parent env (which is the enclosing scope
97//! of the function declaration) includes the epoch at which the function was declared.
98//!
99//! So far, this handles variables created after a function is declared, but does not handle mutation.
100//! Mutation must be handled internally in values, see for example `TagIdentifier`. It is suggested
101//! that objects rely on epochs for this. Since epochs are linked to the stack frame, only objects in
102//! the current stack frame should be mutated.
103//!
104//! ### Std
105//!
106//! The standard library is implicitly imported into every module (unless it explicitly opts out).
107//! So that these implicitly imported names can be overridden, we want to import these names into a
108//! scope outside the implicitly importing module. Furthermore, for efficiency we'd like to share
109//! these imported names between all modules (because std is large and every module imports all
110//! those names). This is safe to do because everything in std is fully immutable.
111//!
112//! To make this work, every env has the std import (prelude) env as its root ancestor. So when an
113//! env is marked as a root env, it may still have the prelude env as its parent.
114//!
115//! ## Implementation
116//!
117//! All environments are kept by the ProgramMemory, their ordering is not important and does not
118//! correspond to anything in the program or execution.
119//!
120//! Pushing and popping stack frames is straightforward. Most get/set/update operations don't touch
121//! the call stack other than the current env (updating tags on function return is the exception).
122//!
123//! ## Invariants
124//!
125//! There's obviously a bunch of invariants in this design, some are kinda obvious, some are limited
126//! in scope and are documented inline, here are some others:
127//!
128//! - We only ever write into the current env, never into any parent envs (though we can read from
129//!   both).
130//! - We only ever write (or mutate) at the most recent epoch, never at an older one.
131//! - The env ref saved with a function decl is always to an historic epoch, never to the current one.
132//! - Since KCL does not have submodules and decls are not visible outside of a nested scope, all
133//!   references to variables in other modules must be in the root scope of a module.
134//!
135//! ## Concurrency and thread-safety
136//!
137//! `ProgramMemory` is a global singleton (technically one per program execution, if we handled multiple
138//! projects in a single interpreter process we'd need multiple `ProgramMemory`s, but that is currently
139//! not possible). `ProgramMemory` could be moved between threads, but there shouldn't be any need
140//! to do so. It can safely be referenced and accessed from multiple threads, but there are rules for
141//! doing so.
142//!
143//! `ProgramMemory` is mostly accessed via a `Stack` object, avoid accessing `ProgramMemory` directly
144//! where possible. `Stack`s can safely be moved to other threads and can access `ProgramMemory`
145//! from a different thread. There can be multiple `Stack`s on different threads or the same thread
146//! (either operating sequentially or using async tasks).
147//!
148//! The key requirement for users is that names from a `Stack` should never be exposed until the
149//! `Stack` itself is no longer needed. I.e., when interpreting a module, you would use a new `Stack`
150//! for the module and no other module can reference anything in the module until interpretation of
151//! it is complete (and the `Stack` object has been dropped).
152//!
153//! Using most of the `Stack` API is easy - you don't need to worry about thread safety and can treat
154//! it just like a self-contained object (though see the docs on `restore_env` and `squash_env` if
155//! you use that method). You shouldn't need to use `ProgramMemory` for much, other
156//! than creating new `Stack`s which is always safe (doesn't mutate `ProgramMemory`). After interpreting
157//! std, you'll need to call `set_std` and for this you must have a unique reference to `ProgramMemory`,
158//! but if you don't we'll just panic, not cause a safety issue. `get_from` and `find_all_in_env`
159//! take an owner parameter and follow the thread-safety invariants below.
160//!
161//! The rest of this section describes the implementation and thread-safety invariants, you should
162//! only need to understand it if you're modifying this file (or want to call a few, rarely used
163//! functions).
164//!
165//! The memory system is a lock-free, mostly wait-free structure. Safety is guaranteed by a few
166//! invariants which are maintained (mostly) internally. There are two areas of mutability which
167//! we need to think about: modifying, updating, or deleting items in memory, and adding or deleting
168//! environments. Other areas of mutation are maintaining the call stacks which is always trivially
169//! thread-local and collecting stats which is trivially atomic.
170//!
171//! A key invariant for modifying memory items is that each env is either uniquely owned by a single
172//! `Stack` (when it is active, i.e., part of a call stack) or is read-only (once interpretation of
173//! the scope backed by the env is complete and the env is no longer on any call stack). Being on a
174//! call stack means the env is owned by that `Stack`. Since the envs are all kept by the `ProgramMemory`
175//! singleton (so that env refs work), we can't rely on Rust ownership to enforce this. Instead, each
176//! `Stack` has an id (ordering of which is irrelevant) and each env has an owner id - if this is 0,
177//! the env is read-only, if not it is owned by the stack with that id. An env can be read or written
178//! by it's owning stack, or if read-only can be read by anyone but never written.
179//!
180//! We check this dynamically, but the checks are assertions and should never fail. The safety invariant
181//! is ensured by construction - memory in a `Stack` should not be referenced from another `Stack`,
182//! memory should only be referenced once interpretation related to it is finished. This is actually
183//! a stronger requirement than is strictly necessary but it is easy to reason about. To be precise,
184//! it is safe to reference a name in an env once it has been popped from a stack and as long as it
185//! doesn't again become active.
186//!
187//! Accessing an env is safe because they are stored on the heap and cannot be moved, even if the
188//! env storage is reorganised (which should only be due to reallocation, we can't move envs within
189//! storage since their indices must be kept consistent).
190//!
191//! Adding or removing an env from storage is protected by a 'lock' field in `ProgramMemory`. Modification
192//! of the env storage must only happen when holding this lock (use `with_envs`). `with_envs` uses a
193//! simple spin lock to wait (the only non-wait-free action) so don't hold the lock for long (currently
194//! the only time this might happen is if the env storage re-sizes and thus reallocates). Reading an
195//! env does not require any lock - an env can never be moved, access to the env must be either
196//! read-only or unique, and (importantly) modifying the environments cannot remove an env unless it
197//! is guaranteed there are no references to the env.
198//!
199//! Edge case: what if an env transitions ownership state at the same time as the env storage is
200//! modified? This shouldn't be a technical issue, because the owner field of an env is only used to
201//! check safety, it is not ever used for any decision. In any case, modifying the env storage is
202//! must be safe if the env is in either state, so even if the transition happens at the same time
203//! as the storage modification, it is ok.
204
205use std::{
206    cell::UnsafeCell,
207    fmt,
208    pin::Pin,
209    sync::{
210        Arc,
211        atomic::{AtomicBool, AtomicUsize, Ordering},
212    },
213};
214
215use anyhow::Result;
216use env::Environment;
217use indexmap::IndexMap;
218use serde::{Deserialize, Serialize};
219
220use crate::{
221    SourceRange,
222    errors::{KclError, KclErrorDetails},
223    execution::KclValue,
224};
225
226/// The distinguished name of the return value of a function.
227pub(crate) const RETURN_NAME: &str = "__return";
228/// Low-budget namespacing for types and modules.
229pub(crate) const TYPE_PREFIX: &str = "__ty_";
230pub(crate) const MODULE_PREFIX: &str = "__mod_";
231
232/// KCL memory. There should be only one ProgramMemory for the interpretation of a program (
233/// including other modules). Multiple interpretation runs should have fresh instances.
234///
235/// See module docs.
236#[derive(Debug)]
237pub(crate) struct ProgramMemory {
238    // Environments are boxed so they will never be moved if the `Vec` reallocates. We use `Pin`
239    // to help guarantee that.
240    environments: UnsafeCell<Vec<Pin<Box<Environment>>>>,
241    /// Memory for the std prelude.
242    std: Option<EnvironmentRef>,
243    /// Statistics about the memory, should not be used for anything other than meta-info.
244    pub(crate) stats: MemoryStats,
245    next_stack_id: AtomicUsize,
246    epoch: AtomicUsize,
247    write_lock: AtomicBool,
248}
249
250unsafe impl Sync for ProgramMemory {}
251
252#[derive(Debug, Clone)]
253pub(crate) struct Stack {
254    pub(crate) memory: Arc<ProgramMemory>,
255    id: usize,
256    /// Invariant: current_env.1.is_none()
257    current_env: EnvironmentRef,
258    /// Invariant: forall er in call_stack: er.1.is_none()
259    call_stack: Vec<EnvironmentRef>,
260}
261
262// Intended for debugging. Do not rely on this output in any way!
263impl fmt::Display for ProgramMemory {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        let envs: Vec<String> = self
266            .envs()
267            .iter()
268            .enumerate()
269            .map(|(i, env)| format!("{i}: {env}"))
270            .collect();
271        write!(
272            f,
273            "ProgramMemory (next stack: {})\nenvs:\n{}",
274            self.next_stack_id.load(Ordering::Relaxed),
275            envs.join("\n")
276        )
277    }
278}
279
280// Intended for debugging. Do not rely on this output in any way!
281impl fmt::Display for Stack {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        let stack: Vec<String> = self
284            .call_stack
285            .iter()
286            .chain(Some(&self.current_env))
287            .map(|e| format!("EnvRef({}, {})", e.0, e.1))
288            .collect();
289        write!(f, "Stack {}\nstack frames:\n{}", self.id, stack.join("\n"))
290    }
291}
292
293impl ProgramMemory {
294    #[allow(clippy::new_without_default)]
295    pub fn new() -> Arc<Self> {
296        Arc::new(Self {
297            // Massively over-allocate here to try and avoid reallocating later.
298            environments: UnsafeCell::new(Vec::with_capacity(512)),
299            std: None,
300            stats: MemoryStats::default(),
301            next_stack_id: AtomicUsize::new(1),
302            epoch: AtomicUsize::new(1),
303            write_lock: AtomicBool::new(false),
304        })
305    }
306
307    /// Clone this ProgramMemory.
308    ///
309    /// This is deliberately not a `Clone` impl or called just `clone` since it requires the write
310    /// lock on the memory and so as to be totally unambiguous with cloning an `Arc` of the memory
311    /// (which you should usually prefer).
312    ///
313    /// This is a long-running operation and holds the write lock, which is bad. Callers must ensure
314    /// that no other task will need to use `self` while this runs.
315    fn deep_clone(&self) -> Self {
316        self.with_envs(|envs| Self {
317            environments: UnsafeCell::new(envs.clone()),
318            std: self.std,
319            stats: MemoryStats::default(),
320            next_stack_id: AtomicUsize::new(self.next_stack_id.load(Ordering::Relaxed)),
321            epoch: AtomicUsize::new(self.epoch.load(Ordering::Relaxed)),
322            write_lock: AtomicBool::new(false),
323        })
324    }
325
326    /// Create a new stack object referencing this `ProgramMemory`.
327    pub fn new_stack(self: Arc<Self>) -> Stack {
328        let id = self.next_stack_id.fetch_add(1, Ordering::Relaxed);
329        assert!(id > 0);
330        Stack {
331            id,
332            memory: self,
333            current_env: EnvironmentRef::dummy(),
334            call_stack: Vec::new(),
335        }
336    }
337
338    /// Set the env var used for the standard library prelude.
339    ///
340    /// Precondition: `self` must be uniquely owned.
341    pub fn set_std(self: &mut Arc<Self>, std: EnvironmentRef) {
342        Arc::get_mut(self).unwrap().std = Some(std);
343    }
344
345    /// Whether this memory still needs to be initialised with its standard library prelude.
346    pub fn requires_std(&self) -> bool {
347        self.std.is_none()
348    }
349
350    /// Get a value from a specific environment of the memory at a specific point in time.
351    pub fn get_from(
352        &self,
353        var: &str,
354        mut env_ref: EnvironmentRef,
355        source_range: SourceRange,
356        owner: usize,
357    ) -> Result<&KclValue, KclError> {
358        loop {
359            let env = self.get_env(env_ref.index());
360            env_ref = match env.get(var, env_ref.1, owner) {
361                Ok(item) => return Ok(item),
362                Err(Some(parent)) => parent,
363                Err(None) => break,
364            };
365        }
366
367        let name = var.trim_start_matches(TYPE_PREFIX).trim_start_matches(MODULE_PREFIX);
368
369        Err(KclError::new_undefined_value(
370            KclErrorDetails::new(format!("`{name}` is not defined"), vec![source_range]),
371            Some(name.to_owned()),
372        ))
373    }
374
375    /// Iterate over all key/value pairs in the specified environment which satisfy the provided
376    /// predicate.
377    fn find_all_in_env<'a>(
378        &'a self,
379        env: EnvironmentRef,
380        pred: impl Fn(&KclValue) -> bool + 'a,
381        owner: usize,
382    ) -> impl Iterator<Item = (&'a String, &'a KclValue)> {
383        assert!(!env.skip_env());
384        self.get_env(env.index()).find_all_by(pred, owner)
385    }
386
387    fn envs(&self) -> &[Pin<Box<Environment>>] {
388        unsafe { self.environments.get().as_ref().unwrap() }
389    }
390
391    #[track_caller]
392    fn get_env(&self, index: usize) -> &Environment {
393        unsafe { &self.environments.get().as_ref().unwrap()[index] }
394    }
395
396    /// Mutable access to the environments. Prefer using higher-level methods if possible.
397    ///
398    /// Uses a spin lock to wait for write access, so `f` must not be even slightly long-running.
399    fn with_envs<T>(&self, f: impl FnOnce(&mut Vec<Pin<Box<Environment>>>) -> T) -> T {
400        // Spin lock
401        while self.write_lock.swap(true, Ordering::AcqRel) {
402            // Atomics wrap on overflow, so no chance of panicking here.
403            self.stats.lock_waits.fetch_add(1, Ordering::Relaxed);
404            std::hint::spin_loop();
405        }
406
407        let envs = unsafe { self.environments.get().as_mut().unwrap() };
408        let result = f(envs);
409
410        let locked = self.write_lock.fetch_not(Ordering::AcqRel);
411        assert!(locked);
412
413        result
414    }
415
416    /// Create a new environment, add it to the list of envs, and return it's ref.
417    fn new_env(&self, parent: Option<EnvironmentRef>, is_root_env: bool, owner: usize) -> EnvironmentRef {
418        assert!(owner > 0);
419        self.stats.env_count.fetch_add(1, Ordering::Relaxed);
420
421        let new_env = Environment::new(parent, is_root_env, owner);
422        self.with_envs(|envs| {
423            let result = EnvironmentRef(envs.len(), usize::MAX);
424            // Note this might reallocate, which would hold the `with_envs` spin lock for way too long
425            // so somehow we should make sure we don't do that (though honestly the chance of that
426            // happening while another thread is waiting for the lock is pretty small).
427            envs.push(Box::pin(new_env));
428            result
429        })
430    }
431
432    /// Handle tidying up an env when it has been popped from the call stack.
433    ///
434    /// If the env must be preserved, it is. If not, then it will be removed or compacted.
435    fn pop_env(&self, old: EnvironmentRef, owner: usize) {
436        // If the env can't be referenced delete all it's bindings.
437        self.get_env(old.index()).compact(owner);
438
439        if self.get_env(old.index()).is_empty() {
440            self.with_envs(|envs| {
441                if old.index() == envs.len() - 1 {
442                    // We can pop the env from the vec.
443                    self.stats.env_gcs.fetch_add(1, Ordering::Relaxed);
444                    envs.pop();
445                } else {
446                    // The env is empty, but we can't pop it. Just leave it around (it can't be
447                    // referenced).
448                    self.stats.skipped_env_gcs.fetch_add(1, Ordering::Relaxed);
449                    envs[old.index()].read_only();
450                }
451            });
452        } else {
453            // Env is non-empty, so preserve it.
454            self.stats.preserved_envs.fetch_add(1, Ordering::Relaxed);
455            self.get_env(old.index()).read_only();
456        }
457    }
458
459    fn take_env(&self, old: EnvironmentRef) -> Pin<Box<Environment>> {
460        self.with_envs(|envs| {
461            if old.index() == envs.len() - 1 {
462                // We can pop the env from the vec.
463                self.stats.env_gcs.fetch_add(1, Ordering::Relaxed);
464                envs.pop().unwrap()
465            } else {
466                // We can't pop because the env is not at the end of the vec and we must maintain
467                // the indices. Replace the env with an empty one. It can no longer be referenced
468                // so we don't care about it.
469                self.stats.skipped_env_gcs.fetch_add(1, Ordering::Relaxed);
470                std::mem::replace(&mut envs[old.index()], Box::pin(Environment::new(None, false, 0)))
471            }
472        })
473    }
474
475    /// Get a value from memory without checking for ownership of the env.
476    ///
477    /// This is not safe to use in general and should only be used if you have unique access to
478    /// the `self` which is generally only true during testing.
479    #[cfg(test)]
480    pub fn get_from_unchecked(&self, var: &str, mut env_ref: EnvironmentRef) -> Result<&KclValue, KclError> {
481        loop {
482            let env = self.get_env(env_ref.index());
483            env_ref = match env.get_unchecked(var, env_ref.1) {
484                Ok(item) => return Ok(item),
485                Err(Some(parent)) => parent,
486                Err(None) => break,
487            };
488        }
489
490        Err(KclError::new_undefined_value(
491            KclErrorDetails::new(format!("`{var}` is not defined"), vec![]),
492            Some(var.to_owned()),
493        ))
494    }
495}
496
497impl Stack {
498    /// Clone this `Stack` and the underlying `ProgramMemory`.
499    ///
500    /// This is a long-running operation and holds the write lock, which is bad. Callers must ensure
501    /// that no other task will need to use the `ProgramMemory` while this runs.
502    pub fn deep_clone(&self) -> Stack {
503        let mem = self.memory.deep_clone();
504        let mut stack = self.clone();
505        stack.memory = Arc::new(mem);
506        stack
507    }
508
509    #[cfg(test)]
510    /// If you're using ProgramMemory directly for testing it must be initialized first.
511    pub fn new_for_tests() -> Stack {
512        let mut stack = ProgramMemory::new().new_stack();
513        stack.push_new_root_env(false);
514        stack.memory.set_std(stack.current_env);
515        stack
516    }
517
518    /// Get the current (globally most recent) epoch.
519    pub fn current_epoch(&self) -> usize {
520        self.memory.epoch.load(Ordering::Relaxed)
521    }
522
523    /// Push a new (standard KCL) stack frame on to the call stack.
524    ///
525    /// `parent` is the environment where the function being called is declared (not the caller's
526    /// environment, which is probably `self.current_env`).
527    pub fn push_new_env_for_call(&mut self, parent: EnvironmentRef) {
528        let env_ref = self.memory.new_env(Some(parent), false, self.id);
529        self.call_stack.push(self.current_env);
530        self.current_env = env_ref;
531    }
532
533    /// Push a stack frame for an inline scope.
534    ///
535    /// This should be used for blocks but is currently only used for mock execution.
536    pub fn push_new_env_for_scope(&mut self) {
537        // We want to use the current env as the parent.
538        // We need to snapshot in case there is a function decl in the new scope.
539        let snapshot = self.snapshot();
540        self.push_new_env_for_call(snapshot);
541    }
542
543    /// Push a new stack frame on to the call stack with no connection to a parent environment.
544    ///
545    /// Suitable for executing a separate module.
546    /// Precondition: include_prelude -> !self.memory.requires_std()
547    pub fn push_new_root_env(&mut self, include_prelude: bool) {
548        let parent = include_prelude.then(|| self.memory.std.unwrap());
549        let env_ref = self.memory.new_env(parent, true, self.id);
550        self.call_stack.push(self.current_env);
551        self.current_env = env_ref;
552    }
553
554    /// Push a previously used environment on to the call stack.
555    ///
556    /// SAFETY: the env must not be being used by another `Stack` since we'll move the env from
557    /// read-only to owned.
558    pub fn restore_env(&mut self, env: EnvironmentRef) {
559        self.call_stack.push(self.current_env);
560        self.memory.get_env(env.index()).restore_owner(self.id);
561        self.current_env = env;
562    }
563
564    /// Pop a frame from the call stack and return a reference to the popped environment. The popped
565    /// environment is preserved if it may be referenced (so the returned reference will remain valid).
566    ///
567    /// The popped environment may be retained completely (if it may be referenced by a function decl
568    /// or import) or retained but its contents deleted or completely discarded.
569    pub fn pop_env(&mut self) -> EnvironmentRef {
570        let old = self.current_env;
571        self.current_env = self.call_stack.pop().unwrap();
572
573        if !old.skip_env() {
574            self.memory.pop_env(old, self.id);
575        }
576
577        old
578    }
579
580    /// Pop a frame from the call stack and return a reference to the popped environment. The popped
581    /// environment is always preserved.
582    pub fn pop_and_preserve_env(&mut self) -> EnvironmentRef {
583        let old = self.current_env;
584        self.current_env = self.call_stack.pop().unwrap();
585        if !old.skip_env() {
586            self.memory.get_env(old.index()).read_only();
587        }
588        old
589    }
590
591    /// Merges the specified environment with the current environment, rewriting any environment refs
592    /// taking snapshots into account. Deletes (if possible) or clears the squashed environment.
593    ///
594    /// Precondition: the caller must have unique access to the env pointed to by `old` and there must be
595    /// no extant references to it. If violated there may be dangling references to the old env once
596    /// it is removed from storage.
597    pub fn squash_env(&mut self, old: EnvironmentRef) {
598        assert!(!old.skip_env());
599        if self.current_env.skip_env() {
600            return;
601        }
602
603        let mut old_env = self.memory.take_env(old);
604        if old_env.is_empty() {
605            return;
606        }
607
608        // Make a new scope so we override variables properly.
609        self.push_new_env_for_scope();
610        // Move the variables in the popped env into the current env.
611        let env = self.memory.get_env(self.current_env.index());
612        for (k, (e, v)) in old_env.as_mut().take_bindings() {
613            env.insert(k, e, v.map_env_ref(old.0, self.current_env.0), self.id);
614        }
615    }
616
617    /// Snapshot the current state of the memory.
618    pub fn snapshot(&mut self) -> EnvironmentRef {
619        self.memory.stats.epoch_count.fetch_add(1, Ordering::Relaxed);
620
621        let env = self.memory.get_env(self.current_env.index());
622        env.mark_as_refed();
623
624        let prev_epoch = self.memory.epoch.fetch_add(1, Ordering::Relaxed);
625        EnvironmentRef(self.current_env.0, prev_epoch)
626    }
627
628    /// Add a value to the program memory (in the current scope). The value must not already exist.
629    pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> {
630        let env = self.memory.get_env(self.current_env.index());
631        if env.contains_key(&key) {
632            return Err(KclError::new_value_already_defined(KclErrorDetails::new(
633                format!("Cannot redefine `{key}`"),
634                vec![source_range],
635            )));
636        }
637
638        self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
639
640        env.insert(key, self.memory.epoch.load(Ordering::Relaxed), value, self.id);
641
642        Ok(())
643    }
644
645    /// Update a variable in memory. `key` must exist in memory. If it doesn't, this function will panic
646    /// in debug builds and do nothing in release builds.
647    pub fn update(&mut self, key: &str, f: impl Fn(&mut KclValue, usize)) {
648        self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
649        self.memory.get_env(self.current_env.index()).update(
650            key,
651            f,
652            self.memory.epoch.load(Ordering::Relaxed),
653            self.id,
654        );
655    }
656
657    /// Get a value from the program memory.
658    /// Return Err if not found.
659    pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&KclValue, KclError> {
660        self.memory.get_from(var, self.current_env, source_range, self.id)
661    }
662
663    /// Whether the current frame of the stack contains a variable with the given name.
664    pub fn cur_frame_contains(&self, var: &str) -> bool {
665        let env = self.memory.get_env(self.current_env.index());
666        env.contains_key(var)
667    }
668
669    /// Get a key from the first stack frame on the call stack.
670    pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<(usize, &KclValue), KclError> {
671        if !self.current_env.skip_env() {
672            return Ok((self.current_env.1, self.get(key, source_range)?));
673        }
674
675        for env in self.call_stack.iter().rev() {
676            if !env.skip_env() {
677                return Ok((env.1, self.memory.get_from(key, *env, source_range, self.id)?));
678            }
679        }
680
681        unreachable!("No frames on the stack?");
682    }
683
684    /// Iterate over all keys in the current environment which satisfy the provided predicate.
685    pub fn find_keys_in_current_env<'a>(
686        &'a self,
687        pred: impl Fn(&KclValue) -> bool + 'a,
688    ) -> impl Iterator<Item = &'a String> {
689        self.memory
690            .find_all_in_env(self.current_env, pred, self.id)
691            .map(|(k, _)| k)
692    }
693
694    /// Iterate over all key/value pairs in the current environment. `env` must
695    /// either be read-only or owned by `self`.
696    pub fn find_all_in_current_env(&self) -> impl Iterator<Item = (&String, &KclValue)> {
697        self.find_all_in_env(self.current_env)
698    }
699
700    /// Iterate over all key/value pairs in the specified environment. `env`
701    /// must either be read-only or owned by `self`.
702    pub fn find_all_in_env(&self, env: EnvironmentRef) -> impl Iterator<Item = (&String, &KclValue)> {
703        self.memory.find_all_in_env(env, |_| true, self.id)
704    }
705
706    /// Walk all values accessible from any environment in the call stack.
707    ///
708    /// This may include duplicate values or different versions of a value known by the same key,
709    /// since an environment may be accessible via multiple paths.
710    pub fn walk_call_stack(&self) -> impl Iterator<Item = &KclValue> {
711        let mut cur_env = self.current_env;
712        let mut stack_index = self.call_stack.len();
713        while cur_env.skip_env() {
714            stack_index -= 1;
715            cur_env = self.call_stack[stack_index];
716        }
717
718        let mut result = CallStackIterator {
719            cur_env,
720            cur_values: None,
721            stack_index,
722            stack: self,
723        };
724        result.init_iter();
725        result
726    }
727}
728
729// See walk_call_stack.
730struct CallStackIterator<'a> {
731    stack: &'a Stack,
732    cur_env: EnvironmentRef,
733    cur_values: Option<Box<dyn Iterator<Item = &'a KclValue> + 'a>>,
734    stack_index: usize,
735}
736
737impl CallStackIterator<'_> {
738    fn init_iter(&mut self) {
739        self.cur_values = Some(self.stack.memory.get_env(self.cur_env.index()).values(self.cur_env.1));
740    }
741}
742
743impl<'a> Iterator for CallStackIterator<'a> {
744    type Item = &'a KclValue;
745
746    fn next(&mut self) -> Option<Self::Item> {
747        self.cur_values.as_ref()?;
748
749        // Loop over each frame in the call stack.
750        'outer: loop {
751            // Loop over each environment in the tree of scopes of which the current stack frame is a leaf.
752            loop {
753                // `unwrap` is OK since we check for None at the start of the function, and if we update
754                // cur_values then it must be to Some(..).
755                let next = self.cur_values.as_mut().unwrap().next();
756                if next.is_some() {
757                    return next;
758                }
759
760                if let Some(env_ref) = self.stack.memory.get_env(self.cur_env.index()).parent() {
761                    self.cur_env = env_ref;
762                    self.init_iter();
763                } else {
764                    break;
765                }
766            }
767
768            if self.stack_index > 0 {
769                // Loop to skip any non-KCL stack frames.
770                loop {
771                    self.stack_index -= 1;
772                    let env_ref = self.stack.call_stack[self.stack_index];
773
774                    if !env_ref.skip_env() {
775                        self.cur_env = env_ref;
776                        self.init_iter();
777                        break;
778                    } else if self.stack_index == 0 {
779                        break 'outer;
780                    }
781                }
782            } else {
783                break;
784            }
785        }
786
787        self.cur_values = None;
788        None
789    }
790}
791
792#[cfg(test)]
793impl PartialEq for Stack {
794    fn eq(&self, other: &Self) -> bool {
795        let vars: Vec<_> = self.find_keys_in_current_env(|_| true).collect();
796        let vars_other: Vec<_> = other.find_keys_in_current_env(|_| true).collect();
797        if vars != vars_other {
798            return false;
799        }
800
801        vars.iter()
802            .all(|k| self.get(k, SourceRange::default()).unwrap() == other.get(k, SourceRange::default()).unwrap())
803    }
804}
805
806/// An index pointing to an environment at a point in time.
807///
808/// The first field indexes an environment, the second field is an epoch. An epoch of 0 is indicates
809/// a dummy, error, or placeholder env ref, an epoch of `usize::MAX` represents the current most
810/// recent epoch.
811#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Hash, Eq, ts_rs::TS)]
812pub struct EnvironmentRef(usize, usize);
813
814impl EnvironmentRef {
815    pub fn dummy() -> Self {
816        Self(usize::MAX, 0)
817    }
818
819    fn is_regular(&self) -> bool {
820        self.0 < usize::MAX && self.1 > 0
821    }
822
823    fn index(&self) -> usize {
824        self.0
825    }
826
827    fn skip_env(&self) -> bool {
828        self.0 == usize::MAX
829    }
830
831    pub fn replace_env(&mut self, old: usize, new: usize) {
832        if self.0 == old {
833            self.0 = new;
834        }
835    }
836}
837
838// TODO keep per-stack stats to avoid so many atomic updates
839#[derive(Debug, Default)]
840pub(crate) struct MemoryStats {
841    // Total number of environments created.
842    env_count: AtomicUsize,
843    // Total number of epochs.
844    epoch_count: AtomicUsize,
845    // Total number of values inserted or updated.
846    mutation_count: AtomicUsize,
847    // The number of envs we delete when popped from the call stack.
848    env_gcs: AtomicUsize,
849    // The number of empty envs we can't delete when popped from the call stack.
850    skipped_env_gcs: AtomicUsize,
851    // The number of envs we can't delete when popped from the call stack because they may be referenced.
852    preserved_envs: AtomicUsize,
853    // The number of iterations waiting for a spin lock.
854    lock_waits: AtomicUsize,
855}
856
857// Use a sub-module to protect access to `Environment::bindings` and prevent unexpected mutatation
858// of stored values.
859mod env {
860    use std::marker::PhantomPinned;
861
862    use super::*;
863
864    #[derive(Debug)]
865    pub(super) struct Environment {
866        bindings: UnsafeCell<IndexMap<String, (usize, KclValue)>>,
867        // An outer scope, if one exists.
868        parent: Option<EnvironmentRef>,
869        might_be_refed: AtomicBool,
870        // The id of the `Stack` if this `Environment` is on a call stack. If this is >0 then it may
871        // only be read or written by that `Stack`; if 0 then the env is read-only.
872        owner: AtomicUsize,
873        // Ensure Environment: !Unpin
874        _unpin: PhantomPinned,
875    }
876
877    impl Clone for Environment {
878        fn clone(&self) -> Self {
879            assert!(self.owner.load(Ordering::Acquire) == 0);
880            Self {
881                bindings: UnsafeCell::new(self.get_bindings().clone()),
882                parent: self.parent,
883                might_be_refed: AtomicBool::new(self.might_be_refed.load(Ordering::Acquire)),
884                owner: AtomicUsize::new(0),
885                _unpin: PhantomPinned,
886            }
887        }
888    }
889
890    impl fmt::Display for Environment {
891        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
892            let parent = self
893                .parent
894                .map(|e| format!("EnvRef({}, {})", e.0, e.1))
895                .unwrap_or("_".to_owned());
896            let data: Vec<String> = self
897                .get_bindings()
898                .iter()
899                .map(|(k, v)| format!("{k}: {}@{}", v.1.human_friendly_type(), v.0))
900                .collect();
901            write!(
902                f,
903                "Env {{\n  parent: {parent},\n  owner: {},\n  ref'ed?: {},\n  bindings:\n    {}\n}}",
904                self.owner.load(Ordering::Relaxed),
905                self.might_be_refed.load(Ordering::Relaxed),
906                data.join("\n    "),
907            )
908        }
909    }
910
911    impl Environment {
912        /// Create a new environment, parent points to it's surrounding lexical scope or the std
913        /// env if it's a root scope.
914        pub(super) fn new(parent: Option<EnvironmentRef>, might_be_refed: bool, owner: usize) -> Self {
915            assert!(parent.map(|p| p.is_regular()).unwrap_or(true));
916            Self {
917                bindings: UnsafeCell::new(IndexMap::new()),
918                parent,
919                might_be_refed: AtomicBool::new(might_be_refed),
920                owner: AtomicUsize::new(owner),
921                _unpin: PhantomPinned,
922            }
923        }
924
925        /// Mark this env as read-only (see module docs).
926        pub(super) fn read_only(&self) {
927            self.owner.store(0, Ordering::Release);
928        }
929
930        /// Mark this env as owned (see module docs).
931        pub(super) fn restore_owner(&self, owner: usize) {
932            self.owner.store(owner, Ordering::Release);
933        }
934
935        /// Mark this environment as possibly having external references.
936        pub(super) fn mark_as_refed(&self) {
937            self.might_be_refed.store(true, Ordering::Release);
938        }
939
940        // SAFETY: either the owner of the env is on the Rust stack or the env is read-only.
941        fn get_bindings(&self) -> &IndexMap<String, (usize, KclValue)> {
942            unsafe { self.bindings.get().as_ref().unwrap() }
943        }
944
945        // SAFETY do not call this function while a previous mutable reference is live
946        #[allow(clippy::mut_from_ref)]
947        fn get_mut_bindings(&self, owner: usize) -> &mut IndexMap<String, (usize, KclValue)> {
948            assert!(owner > 0 && self.owner.load(Ordering::Acquire) == owner);
949            unsafe { self.bindings.get().as_mut().unwrap() }
950        }
951
952        // True if the env is empty and has no external references.
953        pub(super) fn is_empty(&self) -> bool {
954            self.get_bindings().is_empty() && !self.might_be_refed.load(Ordering::Acquire)
955        }
956
957        /// Possibly compress this environment by deleting the memory.
958        ///
959        /// This method will return without changing anything if the environment may be referenced
960        /// (this is a pretty conservative approximation, but if you keep an EnvironmentRef around
961        /// in a new way it might be incorrect).
962        ///
963        /// See module docs for more details.
964        pub(super) fn compact(&self, owner: usize) {
965            // Don't compress if there might be a closure or import referencing us.
966            if self.might_be_refed.load(Ordering::Acquire) {
967                return;
968            }
969
970            *self.get_mut_bindings(owner) = IndexMap::new();
971        }
972
973        pub(super) fn get(&self, key: &str, epoch: usize, owner: usize) -> Result<&KclValue, Option<EnvironmentRef>> {
974            let env_owner = self.owner.load(Ordering::Acquire);
975            assert!(env_owner == 0 || env_owner == owner);
976
977            self.get_unchecked(key, epoch)
978        }
979
980        /// Get a value from memory without checking the env's ownership invariant. Prefer to use `get`.
981        pub(super) fn get_unchecked(&self, key: &str, epoch: usize) -> Result<&KclValue, Option<EnvironmentRef>> {
982            self.get_bindings()
983                .get(key)
984                .and_then(|(e, v)| if *e <= epoch { Some(v) } else { None })
985                .ok_or(self.parent)
986        }
987
988        pub(super) fn update(&self, key: &str, f: impl Fn(&mut KclValue, usize), epoch: usize, owner: usize) {
989            let Some((_, value)) = self.get_mut_bindings(owner).get_mut(key) else {
990                debug_assert!(false, "Missing memory entry for {key}");
991                return;
992            };
993
994            f(value, epoch);
995        }
996
997        pub(super) fn parent(&self) -> Option<EnvironmentRef> {
998            self.parent
999        }
1000
1001        /// Iterate over all values in the environment at the specified epoch.
1002        pub(super) fn values<'a>(&'a self, epoch: usize) -> Box<dyn Iterator<Item = &'a KclValue> + 'a> {
1003            Box::new(
1004                self.get_bindings()
1005                    .values()
1006                    .filter_map(move |(e, v)| (*e <= epoch).then_some(v)),
1007            )
1008        }
1009
1010        /// Pure insert, panics if `key` is already in this environment.
1011        ///
1012        /// Precondition: !self.contains_key(key)
1013        pub(super) fn insert(&self, key: String, epoch: usize, value: KclValue, owner: usize) {
1014            debug_assert!(!self.get_bindings().contains_key(&key));
1015            self.get_mut_bindings(owner).insert(key, (epoch, value));
1016        }
1017
1018        /// Is the key currently contained in this environment.
1019        pub(super) fn contains_key(&self, key: &str) -> bool {
1020            self.get_bindings().contains_key(key)
1021        }
1022
1023        /// Iterate over all key/value pairs currently in this environment where the value satisfies
1024        /// the providied predicate (`f`).
1025        pub(super) fn find_all_by<'a>(
1026            &'a self,
1027            f: impl Fn(&KclValue) -> bool + 'a,
1028            owner: usize,
1029        ) -> impl Iterator<Item = (&'a String, &'a KclValue)> {
1030            let env_owner = self.owner.load(Ordering::Acquire);
1031            assert!(env_owner == 0 || env_owner == owner);
1032
1033            self.get_bindings()
1034                .iter()
1035                .filter_map(move |(k, (_, v))| f(v).then_some((k, v)))
1036        }
1037
1038        /// Take all bindings from the environment.
1039        pub(super) fn take_bindings(self: Pin<&mut Self>) -> impl Iterator<Item = (String, (usize, KclValue))> + use<> {
1040            // SAFETY: caller must have unique access since self is mut. We're not moving or invalidating `self`.
1041            let bindings = std::mem::take(unsafe { self.bindings.get().as_mut().unwrap() });
1042            bindings.into_iter()
1043        }
1044    }
1045}
1046
1047#[cfg(test)]
1048mod test {
1049    use super::*;
1050    use crate::execution::{kcl_value::FunctionSource, types::NumericType};
1051
1052    fn sr() -> SourceRange {
1053        SourceRange::default()
1054    }
1055
1056    fn val(value: i64) -> KclValue {
1057        KclValue::Number {
1058            value: value as f64,
1059            ty: NumericType::count(),
1060            meta: Vec::new(),
1061        }
1062    }
1063
1064    #[track_caller]
1065    fn assert_get(mem: &Stack, key: &str, n: i64) {
1066        match mem.get(key, sr()).unwrap() {
1067            KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
1068            _ => unreachable!(),
1069        }
1070    }
1071
1072    #[track_caller]
1073    fn assert_get_from(mem: &Stack, key: &str, n: i64, snapshot: EnvironmentRef) {
1074        match mem.memory.get_from_unchecked(key, snapshot).unwrap() {
1075            KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
1076            _ => unreachable!(),
1077        }
1078    }
1079
1080    #[test]
1081    fn mem_smoke() {
1082        // Follows test_pattern_transform_function_cannot_access_future_definitions
1083
1084        let mem = &mut Stack::new_for_tests();
1085        let transform = mem.snapshot();
1086        mem.add("transform".to_owned(), val(1), sr()).unwrap();
1087        let layer = mem.snapshot();
1088        mem.add("layer".to_owned(), val(1), sr()).unwrap();
1089        mem.add("x".to_owned(), val(1), sr()).unwrap();
1090
1091        mem.push_new_env_for_call(layer);
1092        mem.pop_env();
1093
1094        mem.push_new_env_for_call(transform);
1095        mem.get("x", sr()).unwrap_err();
1096        mem.pop_env();
1097    }
1098
1099    #[test]
1100    fn simple_snapshot() {
1101        let mem = &mut Stack::new_for_tests();
1102        mem.add("a".to_owned(), val(1), sr()).unwrap();
1103        assert_get(mem, "a", 1);
1104        mem.add("a".to_owned(), val(2), sr()).unwrap_err();
1105        assert_get(mem, "a", 1);
1106        mem.get("b", sr()).unwrap_err();
1107
1108        let sn = mem.snapshot();
1109        mem.add("a".to_owned(), val(2), sr()).unwrap_err();
1110        assert_get(mem, "a", 1);
1111        mem.add("b".to_owned(), val(3), sr()).unwrap();
1112        assert_get(mem, "b", 3);
1113        mem.memory.get_from_unchecked("b", sn).unwrap_err();
1114    }
1115
1116    #[test]
1117    fn multiple_snapshot() {
1118        let mem = &mut Stack::new_for_tests();
1119        mem.add("a".to_owned(), val(1), sr()).unwrap();
1120
1121        let sn1 = mem.snapshot();
1122        mem.add("b".to_owned(), val(3), sr()).unwrap();
1123
1124        let sn2 = mem.snapshot();
1125        mem.add("a".to_owned(), val(4), sr()).unwrap_err();
1126        mem.add("b".to_owned(), val(5), sr()).unwrap_err();
1127        mem.add("c".to_owned(), val(6), sr()).unwrap();
1128        assert_get(mem, "a", 1);
1129        assert_get(mem, "b", 3);
1130        assert_get(mem, "c", 6);
1131        assert_get_from(mem, "a", 1, sn1);
1132        mem.memory.get_from_unchecked("b", sn1).unwrap_err();
1133        mem.memory.get_from_unchecked("c", sn1).unwrap_err();
1134        assert_get_from(mem, "a", 1, sn2);
1135        assert_get_from(mem, "b", 3, sn2);
1136        mem.memory.get_from_unchecked("c", sn2).unwrap_err();
1137    }
1138
1139    #[test]
1140    fn simple_call_env() {
1141        let mem = &mut Stack::new_for_tests();
1142        mem.add("a".to_owned(), val(1), sr()).unwrap();
1143        mem.add("b".to_owned(), val(3), sr()).unwrap();
1144
1145        mem.push_new_env_for_call(mem.current_env);
1146        assert_get(mem, "b", 3);
1147        mem.add("b".to_owned(), val(4), sr()).unwrap();
1148        mem.add("c".to_owned(), val(5), sr()).unwrap();
1149        assert_get(mem, "b", 4);
1150        assert_get(mem, "c", 5);
1151        // Preserve the callee stack frame
1152        mem.snapshot();
1153
1154        let callee = mem.pop_env();
1155        assert_get(mem, "b", 3);
1156        mem.get("c", sr()).unwrap_err();
1157
1158        // callee stack frame is preserved
1159        assert_get_from(mem, "b", 4, callee);
1160        assert_get_from(mem, "c", 5, callee);
1161    }
1162
1163    #[test]
1164    fn multiple_call_env() {
1165        let mem = &mut Stack::new_for_tests();
1166        mem.add("a".to_owned(), val(1), sr()).unwrap();
1167        mem.add("b".to_owned(), val(3), sr()).unwrap();
1168
1169        mem.push_new_env_for_call(mem.current_env);
1170        assert_get(mem, "b", 3);
1171        mem.add("b".to_owned(), val(4), sr()).unwrap();
1172        mem.add("c".to_owned(), val(5), sr()).unwrap();
1173        assert_get(mem, "b", 4);
1174        assert_get(mem, "c", 5);
1175        mem.pop_env();
1176
1177        mem.push_new_env_for_call(mem.current_env);
1178        assert_get(mem, "b", 3);
1179        mem.add("b".to_owned(), val(6), sr()).unwrap();
1180        mem.add("d".to_owned(), val(7), sr()).unwrap();
1181        assert_get(mem, "b", 6);
1182        assert_get(mem, "d", 7);
1183        mem.get("c", sr()).unwrap_err();
1184        mem.pop_env();
1185    }
1186
1187    #[test]
1188    fn root_env() {
1189        let mem = &mut Stack::new_for_tests();
1190        mem.add("a".to_owned(), val(1), sr()).unwrap();
1191        mem.add("b".to_owned(), val(3), sr()).unwrap();
1192
1193        mem.push_new_root_env(false);
1194        mem.get("b", sr()).unwrap_err();
1195        mem.add("b".to_owned(), val(4), sr()).unwrap();
1196        mem.add("c".to_owned(), val(5), sr()).unwrap();
1197        assert_get(mem, "b", 4);
1198        assert_get(mem, "c", 5);
1199
1200        let callee = mem.pop_env();
1201        assert_get(mem, "b", 3);
1202        mem.get("c", sr()).unwrap_err();
1203
1204        // callee stack frame is preserved
1205        assert_get_from(mem, "b", 4, callee);
1206        assert_get_from(mem, "c", 5, callee);
1207    }
1208
1209    #[test]
1210    fn deep_call_env() {
1211        let mem = &mut Stack::new_for_tests();
1212        mem.add("a".to_owned(), val(1), sr()).unwrap();
1213        mem.add("b".to_owned(), val(3), sr()).unwrap();
1214
1215        mem.push_new_env_for_call(mem.current_env);
1216        assert_get(mem, "b", 3);
1217        mem.add("b".to_owned(), val(4), sr()).unwrap();
1218        mem.add("c".to_owned(), val(5), sr()).unwrap();
1219        assert_get(mem, "b", 4);
1220        assert_get(mem, "c", 5);
1221
1222        mem.push_new_env_for_call(mem.current_env);
1223        assert_get(mem, "b", 4);
1224        mem.add("b".to_owned(), val(6), sr()).unwrap();
1225        mem.add("d".to_owned(), val(7), sr()).unwrap();
1226        assert_get(mem, "b", 6);
1227        assert_get(mem, "c", 5);
1228        assert_get(mem, "d", 7);
1229
1230        mem.pop_env();
1231        assert_get(mem, "b", 4);
1232        assert_get(mem, "c", 5);
1233        mem.get("d", sr()).unwrap_err();
1234
1235        mem.pop_env();
1236        assert_get(mem, "b", 3);
1237        mem.get("c", sr()).unwrap_err();
1238        mem.get("d", sr()).unwrap_err();
1239    }
1240
1241    #[test]
1242    fn snap_env() {
1243        let mem = &mut Stack::new_for_tests();
1244        mem.add("a".to_owned(), val(1), sr()).unwrap();
1245
1246        let sn = mem.snapshot();
1247        mem.add("b".to_owned(), val(3), sr()).unwrap();
1248
1249        mem.push_new_env_for_call(sn);
1250        mem.get("b", sr()).unwrap_err();
1251        mem.add("b".to_owned(), val(4), sr()).unwrap();
1252        mem.add("c".to_owned(), val(5), sr()).unwrap();
1253        assert_get(mem, "b", 4);
1254        assert_get(mem, "c", 5);
1255
1256        mem.pop_env();
1257        // old snapshot still untouched
1258        mem.memory.get_from_unchecked("b", sn).unwrap_err();
1259    }
1260
1261    #[test]
1262    fn snap_env2() {
1263        let mem = &mut Stack::new_for_tests();
1264        mem.add("a".to_owned(), val(1), sr()).unwrap();
1265
1266        let sn1 = mem.snapshot();
1267        mem.add("b".to_owned(), val(3), sr()).unwrap();
1268
1269        mem.push_new_env_for_call(mem.current_env);
1270        let sn2 = mem.snapshot();
1271        mem.add("b".to_owned(), val(4), sr()).unwrap();
1272        let sn3 = mem.snapshot();
1273        assert_get_from(mem, "b", 3, sn2);
1274        mem.add("c".to_owned(), val(5), sr()).unwrap();
1275        assert_get(mem, "b", 4);
1276        assert_get(mem, "c", 5);
1277
1278        mem.pop_env();
1279        // old snapshots still untouched
1280        mem.memory.get_from_unchecked("b", sn1).unwrap_err();
1281        assert_get_from(mem, "b", 3, sn2);
1282        mem.memory.get_from_unchecked("c", sn2).unwrap_err();
1283        assert_get_from(mem, "b", 4, sn3);
1284        mem.memory.get_from_unchecked("c", sn3).unwrap_err();
1285    }
1286
1287    #[test]
1288    fn squash_env() {
1289        let mem = &mut Stack::new_for_tests();
1290        mem.add("a".to_owned(), val(1), sr()).unwrap();
1291        mem.add("b".to_owned(), val(3), sr()).unwrap();
1292        let sn1 = mem.snapshot();
1293        mem.push_new_env_for_call(sn1);
1294        mem.add("b".to_owned(), val(2), sr()).unwrap();
1295
1296        let sn2 = mem.snapshot();
1297        mem.add(
1298            "f".to_owned(),
1299            KclValue::Function {
1300                value: Box::new(FunctionSource::kcl(
1301                    crate::parsing::ast::types::FunctionExpression::dummy(),
1302                    sn2,
1303                    crate::execution::kcl_value::KclFunctionSourceParams {
1304                        is_std: false,
1305                        experimental: false,
1306                        include_in_feature_tree: false,
1307                    },
1308                )),
1309                meta: Vec::new(),
1310            },
1311            sr(),
1312        )
1313        .unwrap();
1314        let old = mem.pop_and_preserve_env();
1315        mem.squash_env(old);
1316        assert_get(mem, "a", 1);
1317        assert_get(mem, "b", 2);
1318        match mem.get("f", SourceRange::default()).unwrap() {
1319            KclValue::Function { value, .. } => match &**value {
1320                FunctionSource {
1321                    body: crate::execution::kcl_value::FunctionBody::Kcl(memory),
1322                    ..
1323                } if memory.0 == mem.current_env.0 => {}
1324                v => panic!("{v:#?}, expected {sn1:?}"),
1325            },
1326            v => panic!("{v:#?}, expected {sn1:?}"),
1327        }
1328        assert_eq!(mem.memory.envs().len(), 2);
1329    }
1330
1331    #[test]
1332    fn two_stacks() {
1333        let stack1 = &mut Stack::new_for_tests();
1334        let stack2 = &mut stack1.memory.clone().new_stack();
1335        stack2.push_new_root_env(false);
1336
1337        stack1.add("a".to_owned(), val(1), sr()).unwrap();
1338        stack1.push_new_env_for_call(stack1.current_env);
1339
1340        stack2.add("a".to_owned(), val(2), sr()).unwrap();
1341        stack2.push_new_env_for_call(stack2.current_env);
1342
1343        stack2.add("a".to_owned(), val(4), sr()).unwrap();
1344        stack2.push_new_env_for_call(stack2.current_env);
1345
1346        stack1.add("a".to_owned(), val(3), sr()).unwrap();
1347        stack1.push_new_env_for_call(stack1.current_env);
1348
1349        stack1.add("a".to_owned(), val(5), sr()).unwrap();
1350        stack1.push_new_env_for_call(stack1.current_env);
1351
1352        stack2.add("a".to_owned(), val(6), sr()).unwrap();
1353        stack2.push_new_env_for_call(stack2.current_env);
1354
1355        stack1.add("a".to_owned(), val(7), sr()).unwrap();
1356        stack2.add("a".to_owned(), val(8), sr()).unwrap();
1357
1358        assert_get(stack1, "a", 7);
1359        assert_get(stack2, "a", 8);
1360
1361        stack1.pop_env();
1362        assert_get(stack1, "a", 5);
1363        assert_get(stack2, "a", 8);
1364        stack2.pop_env();
1365        assert_get(stack1, "a", 5);
1366        assert_get(stack2, "a", 6);
1367
1368        stack2.pop_env();
1369        assert_get(stack2, "a", 4);
1370        stack2.pop_env();
1371        assert_get(stack2, "a", 2);
1372        stack1.pop_env();
1373        assert_get(stack1, "a", 3);
1374        stack1.pop_env();
1375        assert_get(stack1, "a", 1);
1376    }
1377}