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        atomic::{AtomicBool, AtomicUsize, Ordering},
211        Arc,
212    },
213};
214
215use anyhow::Result;
216use env::Environment;
217use indexmap::IndexMap;
218use schemars::JsonSchema;
219use serde::{Deserialize, Serialize};
220
221use crate::{
222    errors::{KclError, KclErrorDetails},
223    execution::KclValue,
224    source_range::SourceRange,
225};
226
227/// The distinguished name of the return value of a function.
228pub(crate) const RETURN_NAME: &str = "__return";
229/// Low-budget namespacing for types and modules.
230pub(crate) const TYPE_PREFIX: &str = "__ty_";
231pub(crate) const MODULE_PREFIX: &str = "__mod_";
232
233/// KCL memory. There should be only one ProgramMemory for the interpretation of a program (
234/// including other modules). Multiple interpretation runs should have fresh instances.
235///
236/// See module docs.
237#[derive(Debug)]
238pub(crate) struct ProgramMemory {
239    // Environments are boxed so they will never be moved if the `Vec` reallocates. We use `Pin`
240    // to help guarantee that.
241    environments: UnsafeCell<Vec<Pin<Box<Environment>>>>,
242    /// Memory for the std prelude.
243    std: Option<EnvironmentRef>,
244    /// Statistics about the memory, should not be used for anything other than meta-info.
245    pub(crate) stats: MemoryStats,
246    next_stack_id: AtomicUsize,
247    epoch: AtomicUsize,
248    write_lock: AtomicBool,
249}
250
251unsafe impl Sync for ProgramMemory {}
252
253#[derive(Debug, Clone)]
254pub(crate) struct Stack {
255    pub(crate) memory: Arc<ProgramMemory>,
256    id: usize,
257    /// Invariant: current_env.1.is_none()
258    current_env: EnvironmentRef,
259    /// Invariant: forall er in call_stack: er.1.is_none()
260    call_stack: Vec<EnvironmentRef>,
261}
262
263// Intended for debugging. Do not rely on this output in any way!
264impl fmt::Display for ProgramMemory {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        let envs: Vec<String> = self
267            .envs()
268            .iter()
269            .enumerate()
270            .map(|(i, env)| format!("{i}: {env}"))
271            .collect();
272        write!(
273            f,
274            "ProgramMemory (next stack: {})\nenvs:\n{}",
275            self.next_stack_id.load(Ordering::Relaxed),
276            envs.join("\n")
277        )
278    }
279}
280
281// Intended for debugging. Do not rely on this output in any way!
282impl fmt::Display for Stack {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        let stack: Vec<String> = self
285            .call_stack
286            .iter()
287            .chain(Some(&self.current_env))
288            .map(|e| format!("EnvRef({}, {})", e.0, e.1))
289            .collect();
290        write!(f, "Stack {}\nstack frames:\n{}", self.id, stack.join("\n"))
291    }
292}
293
294impl ProgramMemory {
295    #[allow(clippy::new_without_default)]
296    pub fn new() -> Arc<Self> {
297        Arc::new(Self {
298            // Massively over-allocate here to try and avoid reallocating later.
299            environments: UnsafeCell::new(Vec::with_capacity(512)),
300            std: None,
301            stats: MemoryStats::default(),
302            next_stack_id: AtomicUsize::new(1),
303            epoch: AtomicUsize::new(1),
304            write_lock: AtomicBool::new(false),
305        })
306    }
307
308    /// Clone this ProgramMemory.
309    ///
310    /// This is deliberately not a `Clone` impl or called just `clone` since it requires the write
311    /// lock on the memory and so as to be totally unambiguous with cloning an `Arc` of the memory
312    /// (which you should usually prefer).
313    ///
314    /// This is a long-running operation and holds the write lock, which is bad. Callers must ensure
315    /// that no other task will need to use `self` while this runs.
316    fn deep_clone(&self) -> Self {
317        self.with_envs(|envs| Self {
318            environments: UnsafeCell::new(envs.clone()),
319            std: self.std,
320            stats: MemoryStats::default(),
321            next_stack_id: AtomicUsize::new(self.next_stack_id.load(Ordering::Relaxed)),
322            epoch: AtomicUsize::new(self.epoch.load(Ordering::Relaxed)),
323            write_lock: AtomicBool::new(false),
324        })
325    }
326
327    /// Create a new stack object referencing this `ProgramMemory`.
328    pub fn new_stack(self: Arc<Self>) -> Stack {
329        let id = self.next_stack_id.fetch_add(1, Ordering::Relaxed);
330        assert!(id > 0);
331        Stack {
332            id,
333            memory: self,
334            current_env: EnvironmentRef::dummy(),
335            call_stack: Vec::new(),
336        }
337    }
338
339    /// Set the env var used for the standard library prelude.
340    ///
341    /// Precondition: `self` must be uniquely owned.
342    pub fn set_std(self: &mut Arc<Self>, std: EnvironmentRef) {
343        Arc::get_mut(self).unwrap().std = Some(std);
344    }
345
346    /// Whether this memory still needs to be initialised with its standard library prelude.
347    pub fn requires_std(&self) -> bool {
348        self.std.is_none()
349    }
350
351    /// Get a value from a specific environment of the memory at a specific point in time.
352    pub fn get_from(
353        &self,
354        var: &str,
355        mut env_ref: EnvironmentRef,
356        source_range: SourceRange,
357        owner: usize,
358    ) -> Result<&KclValue, KclError> {
359        loop {
360            let env = self.get_env(env_ref.index());
361            env_ref = match env.get(var, env_ref.1, owner) {
362                Ok(item) => return Ok(item),
363                Err(Some(parent)) => parent,
364                Err(None) => break,
365            };
366        }
367
368        let name = var.trim_start_matches(TYPE_PREFIX).trim_start_matches(MODULE_PREFIX);
369
370        Err(KclError::new_undefined_value(
371            KclErrorDetails::new(format!("`{name}` is not defined"), vec![source_range]),
372            Some(name.to_owned()),
373        ))
374    }
375
376    /// Iterate over all key/value pairs in the specified environment which satisfy the provided
377    /// predicate.
378    fn find_all_in_env<'a>(
379        &'a self,
380        env: EnvironmentRef,
381        pred: impl Fn(&KclValue) -> bool + 'a,
382        owner: usize,
383    ) -> impl Iterator<Item = (&'a String, &'a KclValue)> {
384        assert!(!env.skip_env());
385        self.get_env(env.index()).find_all_by(pred, owner)
386    }
387
388    fn envs(&self) -> &[Pin<Box<Environment>>] {
389        unsafe { self.environments.get().as_ref().unwrap() }
390    }
391
392    #[track_caller]
393    fn get_env(&self, index: usize) -> &Environment {
394        unsafe { &self.environments.get().as_ref().unwrap()[index] }
395    }
396
397    /// Mutable access to the environments. Prefer using higher-level methods if possible.
398    ///
399    /// Uses a spin lock to wait for write access, so `f` must not be even slightly long-running.
400    fn with_envs<T>(&self, f: impl FnOnce(&mut Vec<Pin<Box<Environment>>>) -> T) -> T {
401        // Spin lock
402        while self.write_lock.swap(true, Ordering::AcqRel) {
403            // Atomics wrap on overflow, so no chance of panicking here.
404            self.stats.lock_waits.fetch_add(1, Ordering::Relaxed);
405            std::hint::spin_loop();
406        }
407
408        let envs = unsafe { self.environments.get().as_mut().unwrap() };
409        let result = f(envs);
410
411        let locked = self.write_lock.fetch_not(Ordering::AcqRel);
412        assert!(locked);
413
414        result
415    }
416
417    /// Create a new environment, add it to the list of envs, and return it's ref.
418    fn new_env(&self, parent: Option<EnvironmentRef>, is_root_env: bool, owner: usize) -> EnvironmentRef {
419        assert!(owner > 0);
420        self.stats.env_count.fetch_add(1, Ordering::Relaxed);
421
422        let new_env = Environment::new(parent, is_root_env, owner);
423        self.with_envs(|envs| {
424            let result = EnvironmentRef(envs.len(), usize::MAX);
425            // Note this might reallocate, which would hold the `with_envs` spin lock for way too long
426            // so somehow we should make sure we don't do that (though honestly the chance of that
427            // happening while another thread is waiting for the lock is pretty small).
428            envs.push(Box::pin(new_env));
429            result
430        })
431    }
432
433    /// Handle tidying up an env when it has been popped from the call stack.
434    ///
435    /// If the env must be preserved, it is. If not, then it will be removed or compacted.
436    fn pop_env(&self, old: EnvironmentRef, owner: usize) {
437        // If the env can't be referenced delete all it's bindings.
438        self.get_env(old.index()).compact(owner);
439
440        if self.get_env(old.index()).is_empty() {
441            self.with_envs(|envs| {
442                if old.index() == envs.len() - 1 {
443                    // We can pop the env from the vec.
444                    self.stats.env_gcs.fetch_add(1, Ordering::Relaxed);
445                    envs.pop();
446                } else {
447                    // The env is empty, but we can't pop it. Just leave it around (it can't be
448                    // referenced).
449                    self.stats.skipped_env_gcs.fetch_add(1, Ordering::Relaxed);
450                    envs[old.index()].read_only();
451                }
452            });
453        } else {
454            // Env is non-empty, so preserve it.
455            self.stats.preserved_envs.fetch_add(1, Ordering::Relaxed);
456            self.get_env(old.index()).read_only();
457        }
458    }
459
460    fn take_env(&self, old: EnvironmentRef) -> Pin<Box<Environment>> {
461        self.with_envs(|envs| {
462            if old.index() == envs.len() - 1 {
463                // We can pop the env from the vec.
464                self.stats.env_gcs.fetch_add(1, Ordering::Relaxed);
465                envs.pop().unwrap()
466            } else {
467                // We can't pop because the env is not at the end of the vec and we must maintain
468                // the indices. Replace the env with an empty one. It can no longer be referenced
469                // so we don't care about it.
470                self.stats.skipped_env_gcs.fetch_add(1, Ordering::Relaxed);
471                std::mem::replace(&mut envs[old.index()], Box::pin(Environment::new(None, false, 0)))
472            }
473        })
474    }
475
476    /// Get a value from memory without checking for ownership of the env.
477    ///
478    /// This is not safe to use in general and should only be used if you have unique access to
479    /// the `self` which is generally only true during testing.
480    #[cfg(test)]
481    pub fn get_from_unchecked(&self, var: &str, mut env_ref: EnvironmentRef) -> Result<&KclValue, KclError> {
482        loop {
483            let env = self.get_env(env_ref.index());
484            env_ref = match env.get_unchecked(var, env_ref.1) {
485                Ok(item) => return Ok(item),
486                Err(Some(parent)) => parent,
487                Err(None) => break,
488            };
489        }
490
491        Err(KclError::new_undefined_value(
492            KclErrorDetails::new(format!("`{}` is not defined", var), vec![]),
493            Some(var.to_owned()),
494        ))
495    }
496}
497
498impl Stack {
499    /// Clone this `Stack` and the underlying `ProgramMemory`.
500    ///
501    /// This is a long-running operation and holds the write lock, which is bad. Callers must ensure
502    /// that no other task will need to use the `ProgramMemory` while this runs.
503    pub fn deep_clone(&self) -> Stack {
504        let mem = self.memory.deep_clone();
505        let mut stack = self.clone();
506        stack.memory = Arc::new(mem);
507        stack
508    }
509
510    #[cfg(test)]
511    /// If you're using ProgramMemory directly for testing it must be initialized first.
512    pub fn new_for_tests() -> Stack {
513        let mut stack = ProgramMemory::new().new_stack();
514        stack.push_new_root_env(false);
515        stack.memory.set_std(stack.current_env);
516        stack
517    }
518
519    /// Get the current (globally most recent) epoch.
520    pub fn current_epoch(&self) -> usize {
521        self.memory.epoch.load(Ordering::Relaxed)
522    }
523
524    /// Push a new (standard KCL) stack frame on to the call stack.
525    ///
526    /// `parent` is the environment where the function being called is declared (not the caller's
527    /// environment, which is probably `self.current_env`).
528    pub fn push_new_env_for_call(&mut self, parent: EnvironmentRef) {
529        let env_ref = self.memory.new_env(Some(parent), false, self.id);
530        self.call_stack.push(self.current_env);
531        self.current_env = env_ref;
532    }
533
534    /// Push a stack frame for an inline scope.
535    ///
536    /// This should be used for blocks but is currently only used for mock execution.
537    pub fn push_new_env_for_scope(&mut self) {
538        // We want to use the current env as the parent.
539        // We need to snapshot in case there is a function decl in the new scope.
540        let snapshot = self.snapshot();
541        self.push_new_env_for_call(snapshot);
542    }
543
544    /// Push a new stack frame on to the call stack for callees which should not read or write
545    /// from memory.
546    ///
547    /// This is suitable for calling standard library functions or other functions written in Rust
548    /// which will use 'Rust memory' rather than KCL's memory and cannot reach into the wider
549    /// environment.
550    ///
551    /// Trying to read or write from this environment will panic with an index out of bounds.
552    pub fn push_new_env_for_rust_call(&mut self) {
553        self.call_stack.push(self.current_env);
554        // Rust functions shouldn't try to set or access anything in their environment, so don't
555        // waste time and space on a new env. Using usize::MAX means we'll get an overflow if we
556        // try to access anything rather than a silent error.
557        self.current_env = EnvironmentRef(usize::MAX, 0);
558    }
559
560    /// Push a new stack frame on to the call stack with no connection to a parent environment.
561    ///
562    /// Suitable for executing a separate module.
563    /// Precondition: include_prelude -> !self.memory.requires_std()
564    pub fn push_new_root_env(&mut self, include_prelude: bool) {
565        let parent = include_prelude.then(|| self.memory.std.unwrap());
566        let env_ref = self.memory.new_env(parent, true, self.id);
567        self.call_stack.push(self.current_env);
568        self.current_env = env_ref;
569    }
570
571    /// Push a previously used environment on to the call stack.
572    ///
573    /// SAFETY: the env must not be being used by another `Stack` since we'll move the env from
574    /// read-only to owned.
575    pub fn restore_env(&mut self, env: EnvironmentRef) {
576        self.call_stack.push(self.current_env);
577        self.memory.get_env(env.index()).restore_owner(self.id);
578        self.current_env = env;
579    }
580
581    /// Pop a frame from the call stack and return a reference to the popped environment. The popped
582    /// environment is preserved if it may be referenced (so the returned reference will remain valid).
583    ///
584    /// The popped environment may be retained completely (if it may be referenced by a function decl
585    /// or import) or retained but its contents deleted or completely discarded.
586    pub fn pop_env(&mut self) -> EnvironmentRef {
587        let old = self.current_env;
588        self.current_env = self.call_stack.pop().unwrap();
589
590        if !old.skip_env() {
591            self.memory.pop_env(old, self.id);
592        }
593
594        old
595    }
596
597    /// Pop a frame from the call stack and return a reference to the popped environment. The popped
598    /// environment is always preserved.
599    pub fn pop_and_preserve_env(&mut self) -> EnvironmentRef {
600        let old = self.current_env;
601        self.current_env = self.call_stack.pop().unwrap();
602        if !old.skip_env() {
603            self.memory.get_env(old.index()).read_only();
604        }
605        old
606    }
607
608    /// Merges the specified environment with the current environment, rewriting any environment refs
609    /// taking snapshots into account. Deletes (if possible) or clears the squashed environment.
610    ///
611    /// Precondition: the caller must have unique access to the env pointed to by `old` and there must be
612    /// no extant references to it. If violated there may be dangling references to the old env once
613    /// it is removed from storage.
614    pub fn squash_env(&mut self, old: EnvironmentRef) {
615        assert!(!old.skip_env());
616        if self.current_env.skip_env() {
617            return;
618        }
619
620        let mut old_env = self.memory.take_env(old);
621        if old_env.is_empty() {
622            return;
623        }
624
625        // Make a new scope so we override variables properly.
626        self.push_new_env_for_scope();
627        // Move the variables in the popped env into the current env.
628        let env = self.memory.get_env(self.current_env.index());
629        for (k, (e, v)) in old_env.as_mut().take_bindings() {
630            env.insert(k, e, v.map_env_ref(old.0, self.current_env.0), self.id);
631        }
632    }
633
634    /// Snapshot the current state of the memory.
635    pub fn snapshot(&mut self) -> EnvironmentRef {
636        self.memory.stats.epoch_count.fetch_add(1, Ordering::Relaxed);
637
638        let env = self.memory.get_env(self.current_env.index());
639        env.mark_as_refed();
640
641        let prev_epoch = self.memory.epoch.fetch_add(1, Ordering::Relaxed);
642        EnvironmentRef(self.current_env.0, prev_epoch)
643    }
644
645    /// Add a value to the program memory (in the current scope). The value must not already exist.
646    pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> {
647        let env = self.memory.get_env(self.current_env.index());
648        if env.contains_key(&key) {
649            return Err(KclError::new_value_already_defined(KclErrorDetails::new(
650                format!("Cannot redefine `{}`", key),
651                vec![source_range],
652            )));
653        }
654
655        self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
656
657        env.insert(key, self.memory.epoch.load(Ordering::Relaxed), value, self.id);
658
659        Ok(())
660    }
661
662    /// Update a variable in memory. `key` must exist in memory. If it doesn't, this function will panic
663    /// in debug builds and do nothing in release builds.
664    pub fn update(&mut self, key: &str, f: impl Fn(&mut KclValue, usize)) {
665        self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
666        self.memory.get_env(self.current_env.index()).update(
667            key,
668            f,
669            self.memory.epoch.load(Ordering::Relaxed),
670            self.id,
671        );
672    }
673
674    /// Get a value from the program memory.
675    /// Return Err if not found.
676    pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&KclValue, KclError> {
677        self.memory.get_from(var, self.current_env, source_range, self.id)
678    }
679
680    /// Whether the current frame of the stack contains a variable with the given name.
681    pub fn cur_frame_contains(&self, var: &str) -> bool {
682        let env = self.memory.get_env(self.current_env.index());
683        env.contains_key(var)
684    }
685
686    /// Get a key from the first KCL (i.e., non-Rust) stack frame on the call stack.
687    pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<(usize, &KclValue), KclError> {
688        if !self.current_env.skip_env() {
689            return Ok((self.current_env.1, self.get(key, source_range)?));
690        }
691
692        for env in self.call_stack.iter().rev() {
693            if !env.skip_env() {
694                return Ok((env.1, self.memory.get_from(key, *env, source_range, self.id)?));
695            }
696        }
697
698        unreachable!("It can't be Rust frames all the way down");
699    }
700
701    /// Iterate over all keys in the current environment which satisfy the provided predicate.
702    pub fn find_keys_in_current_env<'a>(
703        &'a self,
704        pred: impl Fn(&KclValue) -> bool + 'a,
705    ) -> impl Iterator<Item = &'a String> {
706        self.memory
707            .find_all_in_env(self.current_env, pred, self.id)
708            .map(|(k, _)| k)
709    }
710
711    /// Iterate over all key/value pairs in the specified environment which satisfy the provided
712    /// predicate. `env` must either be read-only or owned by `self`.
713    pub fn find_all_in_env(&self, env: EnvironmentRef) -> impl Iterator<Item = (&String, &KclValue)> {
714        self.memory.find_all_in_env(env, |_| true, self.id)
715    }
716
717    /// Walk all values accessible from any environment in the call stack.
718    ///
719    /// This may include duplicate values or different versions of a value known by the same key,
720    /// since an environment may be accessible via multiple paths.
721    pub fn walk_call_stack(&self) -> impl Iterator<Item = &KclValue> {
722        let mut cur_env = self.current_env;
723        let mut stack_index = self.call_stack.len();
724        while cur_env.skip_env() {
725            stack_index -= 1;
726            cur_env = self.call_stack[stack_index];
727        }
728
729        let mut result = CallStackIterator {
730            cur_env,
731            cur_values: None,
732            stack_index,
733            stack: self,
734        };
735        result.init_iter();
736        result
737    }
738}
739
740// See walk_call_stack.
741struct CallStackIterator<'a> {
742    stack: &'a Stack,
743    cur_env: EnvironmentRef,
744    cur_values: Option<Box<dyn Iterator<Item = &'a KclValue> + 'a>>,
745    stack_index: usize,
746}
747
748impl CallStackIterator<'_> {
749    fn init_iter(&mut self) {
750        self.cur_values = Some(self.stack.memory.get_env(self.cur_env.index()).values(self.cur_env.1));
751    }
752}
753
754impl<'a> Iterator for CallStackIterator<'a> {
755    type Item = &'a KclValue;
756
757    fn next(&mut self) -> Option<Self::Item> {
758        self.cur_values.as_ref()?;
759
760        // Loop over each frame in the call stack.
761        'outer: loop {
762            // Loop over each environment in the tree of scopes of which the current stack frame is a leaf.
763            loop {
764                // `unwrap` is OK since we check for None at the start of the function, and if we update
765                // cur_values then it must be to Some(..).
766                let next = self.cur_values.as_mut().unwrap().next();
767                if next.is_some() {
768                    return next;
769                }
770
771                if let Some(env_ref) = self.stack.memory.get_env(self.cur_env.index()).parent() {
772                    self.cur_env = env_ref;
773                    self.init_iter();
774                } else {
775                    break;
776                }
777            }
778
779            if self.stack_index > 0 {
780                // Loop to skip any non-KCL stack frames.
781                loop {
782                    self.stack_index -= 1;
783                    let env_ref = self.stack.call_stack[self.stack_index];
784
785                    if !env_ref.skip_env() {
786                        self.cur_env = env_ref;
787                        self.init_iter();
788                        break;
789                    } else if self.stack_index == 0 {
790                        break 'outer;
791                    }
792                }
793            } else {
794                break;
795            }
796        }
797
798        self.cur_values = None;
799        None
800    }
801}
802
803#[cfg(test)]
804impl PartialEq for Stack {
805    fn eq(&self, other: &Self) -> bool {
806        let vars: Vec<_> = self.find_keys_in_current_env(|_| true).collect();
807        let vars_other: Vec<_> = other.find_keys_in_current_env(|_| true).collect();
808        if vars != vars_other {
809            return false;
810        }
811
812        vars.iter()
813            .all(|k| self.get(k, SourceRange::default()).unwrap() == other.get(k, SourceRange::default()).unwrap())
814    }
815}
816
817/// An index pointing to an environment at a point in time.
818///
819/// The first field indexes an environment, the second field is an epoch. An epoch of 0 is indicates
820/// a dummy, error, or placeholder env ref, an epoch of `usize::MAX` represents the current most
821/// recent epoch.
822#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Hash, Eq, ts_rs::TS, JsonSchema)]
823pub struct EnvironmentRef(usize, usize);
824
825impl EnvironmentRef {
826    pub fn dummy() -> Self {
827        Self(usize::MAX, 0)
828    }
829
830    fn is_regular(&self) -> bool {
831        self.0 < usize::MAX && self.1 > 0
832    }
833
834    fn index(&self) -> usize {
835        self.0
836    }
837
838    fn skip_env(&self) -> bool {
839        self.0 == usize::MAX
840    }
841
842    pub fn replace_env(&mut self, old: usize, new: usize) {
843        if self.0 == old {
844            self.0 = new;
845        }
846    }
847}
848
849// TODO keep per-stack stats to avoid so many atomic updates
850#[derive(Debug, Default)]
851pub(crate) struct MemoryStats {
852    // Total number of environments created.
853    env_count: AtomicUsize,
854    // Total number of epochs.
855    epoch_count: AtomicUsize,
856    // Total number of values inserted or updated.
857    mutation_count: AtomicUsize,
858    // The number of envs we delete when popped from the call stack.
859    env_gcs: AtomicUsize,
860    // The number of empty envs we can't delete when popped from the call stack.
861    skipped_env_gcs: AtomicUsize,
862    // The number of envs we can't delete when popped from the call stack because they may be referenced.
863    preserved_envs: AtomicUsize,
864    // The number of iterations waiting for a spin lock.
865    lock_waits: AtomicUsize,
866}
867
868// Use a sub-module to protect access to `Environment::bindings` and prevent unexpected mutatation
869// of stored values.
870mod env {
871    use std::marker::PhantomPinned;
872
873    use super::*;
874
875    #[derive(Debug)]
876    pub(super) struct Environment {
877        bindings: UnsafeCell<IndexMap<String, (usize, KclValue)>>,
878        // An outer scope, if one exists.
879        parent: Option<EnvironmentRef>,
880        might_be_refed: AtomicBool,
881        // The id of the `Stack` if this `Environment` is on a call stack. If this is >0 then it may
882        // only be read or written by that `Stack`; if 0 then the env is read-only.
883        owner: AtomicUsize,
884        // Ensure Environment: !Unpin
885        _unpin: PhantomPinned,
886    }
887
888    impl Clone for Environment {
889        fn clone(&self) -> Self {
890            assert!(self.owner.load(Ordering::Acquire) == 0);
891            Self {
892                bindings: UnsafeCell::new(self.get_bindings().clone()),
893                parent: self.parent,
894                might_be_refed: AtomicBool::new(self.might_be_refed.load(Ordering::Acquire)),
895                owner: AtomicUsize::new(0),
896                _unpin: PhantomPinned,
897            }
898        }
899    }
900
901    impl fmt::Display for Environment {
902        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
903            let parent = self
904                .parent
905                .map(|e| format!("EnvRef({}, {})", e.0, e.1))
906                .unwrap_or("_".to_owned());
907            let data: Vec<String> = self
908                .get_bindings()
909                .iter()
910                .map(|(k, v)| format!("{k}: {}@{}", v.1.human_friendly_type(), v.0))
911                .collect();
912            write!(
913                f,
914                "Env {{\n  parent: {parent},\n  owner: {},\n  ref'ed?: {},\n  bindings:\n    {}\n}}",
915                self.owner.load(Ordering::Relaxed),
916                self.might_be_refed.load(Ordering::Relaxed),
917                data.join("\n    "),
918            )
919        }
920    }
921
922    impl Environment {
923        /// Create a new environment, parent points to it's surrounding lexical scope or the std
924        /// env if it's a root scope.
925        pub(super) fn new(parent: Option<EnvironmentRef>, might_be_refed: bool, owner: usize) -> Self {
926            assert!(parent.map(|p| p.is_regular()).unwrap_or(true));
927            Self {
928                bindings: UnsafeCell::new(IndexMap::new()),
929                parent,
930                might_be_refed: AtomicBool::new(might_be_refed),
931                owner: AtomicUsize::new(owner),
932                _unpin: PhantomPinned,
933            }
934        }
935
936        /// Mark this env as read-only (see module docs).
937        pub(super) fn read_only(&self) {
938            self.owner.store(0, Ordering::Release);
939        }
940
941        /// Mark this env as owned (see module docs).
942        pub(super) fn restore_owner(&self, owner: usize) {
943            self.owner.store(owner, Ordering::Release);
944        }
945
946        /// Mark this environment as possibly having external references.
947        pub(super) fn mark_as_refed(&self) {
948            self.might_be_refed.store(true, Ordering::Release);
949        }
950
951        // SAFETY: either the owner of the env is on the Rust stack or the env is read-only.
952        fn get_bindings(&self) -> &IndexMap<String, (usize, KclValue)> {
953            unsafe { self.bindings.get().as_ref().unwrap() }
954        }
955
956        // SAFETY do not call this function while a previous mutable reference is live
957        #[allow(clippy::mut_from_ref)]
958        fn get_mut_bindings(&self, owner: usize) -> &mut IndexMap<String, (usize, KclValue)> {
959            assert!(owner > 0 && self.owner.load(Ordering::Acquire) == owner);
960            unsafe { self.bindings.get().as_mut().unwrap() }
961        }
962
963        // True if the env is empty and has no external references.
964        pub(super) fn is_empty(&self) -> bool {
965            self.get_bindings().is_empty() && !self.might_be_refed.load(Ordering::Acquire)
966        }
967
968        /// Possibly compress this environment by deleting the memory.
969        ///
970        /// This method will return without changing anything if the environment may be referenced
971        /// (this is a pretty conservative approximation, but if you keep an EnvironmentRef around
972        /// in a new way it might be incorrect).
973        ///
974        /// See module docs for more details.
975        pub(super) fn compact(&self, owner: usize) {
976            // Don't compress if there might be a closure or import referencing us.
977            if self.might_be_refed.load(Ordering::Acquire) {
978                return;
979            }
980
981            *self.get_mut_bindings(owner) = IndexMap::new();
982        }
983
984        pub(super) fn get(&self, key: &str, epoch: usize, owner: usize) -> Result<&KclValue, Option<EnvironmentRef>> {
985            let env_owner = self.owner.load(Ordering::Acquire);
986            assert!(env_owner == 0 || env_owner == owner);
987
988            self.get_unchecked(key, epoch)
989        }
990
991        /// Get a value from memory without checking the env's ownership invariant. Prefer to use `get`.
992        pub(super) fn get_unchecked(&self, key: &str, epoch: usize) -> Result<&KclValue, Option<EnvironmentRef>> {
993            self.get_bindings()
994                .get(key)
995                .and_then(|(e, v)| if *e <= epoch { Some(v) } else { None })
996                .ok_or(self.parent)
997        }
998
999        pub(super) fn update(&self, key: &str, f: impl Fn(&mut KclValue, usize), epoch: usize, owner: usize) {
1000            let Some((_, value)) = self.get_mut_bindings(owner).get_mut(key) else {
1001                debug_assert!(false, "Missing memory entry for {key}");
1002                return;
1003            };
1004
1005            f(value, epoch);
1006        }
1007
1008        pub(super) fn parent(&self) -> Option<EnvironmentRef> {
1009            self.parent
1010        }
1011
1012        /// Iterate over all values in the environment at the specified epoch.
1013        pub(super) fn values<'a>(&'a self, epoch: usize) -> Box<dyn Iterator<Item = &'a KclValue> + 'a> {
1014            Box::new(
1015                self.get_bindings()
1016                    .values()
1017                    .filter_map(move |(e, v)| (*e <= epoch).then_some(v)),
1018            )
1019        }
1020
1021        /// Pure insert, panics if `key` is already in this environment.
1022        ///
1023        /// Precondition: !self.contains_key(key)
1024        pub(super) fn insert(&self, key: String, epoch: usize, value: KclValue, owner: usize) {
1025            debug_assert!(!self.get_bindings().contains_key(&key));
1026            self.get_mut_bindings(owner).insert(key, (epoch, value));
1027        }
1028
1029        /// Is the key currently contained in this environment.
1030        pub(super) fn contains_key(&self, key: &str) -> bool {
1031            self.get_bindings().contains_key(key)
1032        }
1033
1034        /// Iterate over all key/value pairs currently in this environment where the value satisfies
1035        /// the providied predicate (`f`).
1036        pub(super) fn find_all_by<'a>(
1037            &'a self,
1038            f: impl Fn(&KclValue) -> bool + 'a,
1039            owner: usize,
1040        ) -> impl Iterator<Item = (&'a String, &'a KclValue)> {
1041            let env_owner = self.owner.load(Ordering::Acquire);
1042            assert!(env_owner == 0 || env_owner == owner);
1043
1044            self.get_bindings()
1045                .iter()
1046                .filter_map(move |(k, (_, v))| f(v).then_some((k, v)))
1047        }
1048
1049        /// Take all bindings from the environment.
1050        pub(super) fn take_bindings(self: Pin<&mut Self>) -> impl Iterator<Item = (String, (usize, KclValue))> {
1051            // SAFETY: caller must have unique access since self is mut. We're not moving or invalidating `self`.
1052            let bindings = std::mem::take(unsafe { self.bindings.get().as_mut().unwrap() });
1053            bindings.into_iter()
1054        }
1055    }
1056}
1057
1058#[cfg(test)]
1059mod test {
1060    use super::*;
1061    use crate::execution::{kcl_value::FunctionSource, types::NumericType};
1062
1063    fn sr() -> SourceRange {
1064        SourceRange::default()
1065    }
1066
1067    fn val(value: i64) -> KclValue {
1068        KclValue::Number {
1069            value: value as f64,
1070            ty: NumericType::count(),
1071            meta: Vec::new(),
1072        }
1073    }
1074
1075    #[track_caller]
1076    fn assert_get(mem: &Stack, key: &str, n: i64) {
1077        match mem.get(key, sr()).unwrap() {
1078            KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
1079            _ => unreachable!(),
1080        }
1081    }
1082
1083    #[track_caller]
1084    fn assert_get_from(mem: &Stack, key: &str, n: i64, snapshot: EnvironmentRef) {
1085        match mem.memory.get_from_unchecked(key, snapshot).unwrap() {
1086            KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
1087            _ => unreachable!(),
1088        }
1089    }
1090
1091    #[test]
1092    fn mem_smoke() {
1093        // Follows test_pattern_transform_function_cannot_access_future_definitions
1094
1095        let mem = &mut Stack::new_for_tests();
1096        let transform = mem.snapshot();
1097        mem.add("transform".to_owned(), val(1), sr()).unwrap();
1098        let layer = mem.snapshot();
1099        mem.add("layer".to_owned(), val(1), sr()).unwrap();
1100        mem.add("x".to_owned(), val(1), sr()).unwrap();
1101
1102        mem.push_new_env_for_call(layer);
1103        mem.pop_env();
1104
1105        mem.push_new_env_for_call(transform);
1106        mem.get("x", sr()).unwrap_err();
1107        mem.pop_env();
1108    }
1109
1110    #[test]
1111    fn simple_snapshot() {
1112        let mem = &mut Stack::new_for_tests();
1113        mem.add("a".to_owned(), val(1), sr()).unwrap();
1114        assert_get(mem, "a", 1);
1115        mem.add("a".to_owned(), val(2), sr()).unwrap_err();
1116        assert_get(mem, "a", 1);
1117        mem.get("b", sr()).unwrap_err();
1118
1119        let sn = mem.snapshot();
1120        mem.add("a".to_owned(), val(2), sr()).unwrap_err();
1121        assert_get(mem, "a", 1);
1122        mem.add("b".to_owned(), val(3), sr()).unwrap();
1123        assert_get(mem, "b", 3);
1124        mem.memory.get_from_unchecked("b", sn).unwrap_err();
1125    }
1126
1127    #[test]
1128    fn multiple_snapshot() {
1129        let mem = &mut Stack::new_for_tests();
1130        mem.add("a".to_owned(), val(1), sr()).unwrap();
1131
1132        let sn1 = mem.snapshot();
1133        mem.add("b".to_owned(), val(3), sr()).unwrap();
1134
1135        let sn2 = mem.snapshot();
1136        mem.add("a".to_owned(), val(4), sr()).unwrap_err();
1137        mem.add("b".to_owned(), val(5), sr()).unwrap_err();
1138        mem.add("c".to_owned(), val(6), sr()).unwrap();
1139        assert_get(mem, "a", 1);
1140        assert_get(mem, "b", 3);
1141        assert_get(mem, "c", 6);
1142        assert_get_from(mem, "a", 1, sn1);
1143        mem.memory.get_from_unchecked("b", sn1).unwrap_err();
1144        mem.memory.get_from_unchecked("c", sn1).unwrap_err();
1145        assert_get_from(mem, "a", 1, sn2);
1146        assert_get_from(mem, "b", 3, sn2);
1147        mem.memory.get_from_unchecked("c", sn2).unwrap_err();
1148    }
1149
1150    #[test]
1151    fn simple_call_env() {
1152        let mem = &mut Stack::new_for_tests();
1153        mem.add("a".to_owned(), val(1), sr()).unwrap();
1154        mem.add("b".to_owned(), val(3), sr()).unwrap();
1155
1156        mem.push_new_env_for_call(mem.current_env);
1157        assert_get(mem, "b", 3);
1158        mem.add("b".to_owned(), val(4), sr()).unwrap();
1159        mem.add("c".to_owned(), val(5), sr()).unwrap();
1160        assert_get(mem, "b", 4);
1161        assert_get(mem, "c", 5);
1162        // Preserve the callee stack frame
1163        mem.snapshot();
1164
1165        let callee = mem.pop_env();
1166        assert_get(mem, "b", 3);
1167        mem.get("c", sr()).unwrap_err();
1168
1169        // callee stack frame is preserved
1170        assert_get_from(mem, "b", 4, callee);
1171        assert_get_from(mem, "c", 5, callee);
1172    }
1173
1174    #[test]
1175    fn multiple_call_env() {
1176        let mem = &mut Stack::new_for_tests();
1177        mem.add("a".to_owned(), val(1), sr()).unwrap();
1178        mem.add("b".to_owned(), val(3), sr()).unwrap();
1179
1180        mem.push_new_env_for_call(mem.current_env);
1181        assert_get(mem, "b", 3);
1182        mem.add("b".to_owned(), val(4), sr()).unwrap();
1183        mem.add("c".to_owned(), val(5), sr()).unwrap();
1184        assert_get(mem, "b", 4);
1185        assert_get(mem, "c", 5);
1186        mem.pop_env();
1187
1188        mem.push_new_env_for_call(mem.current_env);
1189        assert_get(mem, "b", 3);
1190        mem.add("b".to_owned(), val(6), sr()).unwrap();
1191        mem.add("d".to_owned(), val(7), sr()).unwrap();
1192        assert_get(mem, "b", 6);
1193        assert_get(mem, "d", 7);
1194        mem.get("c", sr()).unwrap_err();
1195        mem.pop_env();
1196    }
1197
1198    #[test]
1199    fn root_env() {
1200        let mem = &mut Stack::new_for_tests();
1201        mem.add("a".to_owned(), val(1), sr()).unwrap();
1202        mem.add("b".to_owned(), val(3), sr()).unwrap();
1203
1204        mem.push_new_root_env(false);
1205        mem.get("b", sr()).unwrap_err();
1206        mem.add("b".to_owned(), val(4), sr()).unwrap();
1207        mem.add("c".to_owned(), val(5), sr()).unwrap();
1208        assert_get(mem, "b", 4);
1209        assert_get(mem, "c", 5);
1210
1211        let callee = mem.pop_env();
1212        assert_get(mem, "b", 3);
1213        mem.get("c", sr()).unwrap_err();
1214
1215        // callee stack frame is preserved
1216        assert_get_from(mem, "b", 4, callee);
1217        assert_get_from(mem, "c", 5, callee);
1218    }
1219
1220    #[test]
1221    fn rust_env() {
1222        let mem = &mut Stack::new_for_tests();
1223        mem.add("a".to_owned(), val(1), sr()).unwrap();
1224        mem.add("b".to_owned(), val(3), sr()).unwrap();
1225        let sn = mem.snapshot();
1226
1227        mem.push_new_env_for_rust_call();
1228        mem.push_new_env_for_call(sn);
1229        assert_get(mem, "b", 3);
1230        mem.add("b".to_owned(), val(4), sr()).unwrap();
1231        assert_get(mem, "b", 4);
1232
1233        mem.pop_env();
1234        mem.pop_env();
1235        assert_get(mem, "b", 3);
1236    }
1237
1238    #[test]
1239    fn deep_call_env() {
1240        let mem = &mut Stack::new_for_tests();
1241        mem.add("a".to_owned(), val(1), sr()).unwrap();
1242        mem.add("b".to_owned(), val(3), sr()).unwrap();
1243
1244        mem.push_new_env_for_call(mem.current_env);
1245        assert_get(mem, "b", 3);
1246        mem.add("b".to_owned(), val(4), sr()).unwrap();
1247        mem.add("c".to_owned(), val(5), sr()).unwrap();
1248        assert_get(mem, "b", 4);
1249        assert_get(mem, "c", 5);
1250
1251        mem.push_new_env_for_call(mem.current_env);
1252        assert_get(mem, "b", 4);
1253        mem.add("b".to_owned(), val(6), sr()).unwrap();
1254        mem.add("d".to_owned(), val(7), sr()).unwrap();
1255        assert_get(mem, "b", 6);
1256        assert_get(mem, "c", 5);
1257        assert_get(mem, "d", 7);
1258
1259        mem.pop_env();
1260        assert_get(mem, "b", 4);
1261        assert_get(mem, "c", 5);
1262        mem.get("d", sr()).unwrap_err();
1263
1264        mem.pop_env();
1265        assert_get(mem, "b", 3);
1266        mem.get("c", sr()).unwrap_err();
1267        mem.get("d", sr()).unwrap_err();
1268    }
1269
1270    #[test]
1271    fn snap_env() {
1272        let mem = &mut Stack::new_for_tests();
1273        mem.add("a".to_owned(), val(1), sr()).unwrap();
1274
1275        let sn = mem.snapshot();
1276        mem.add("b".to_owned(), val(3), sr()).unwrap();
1277
1278        mem.push_new_env_for_call(sn);
1279        mem.get("b", sr()).unwrap_err();
1280        mem.add("b".to_owned(), val(4), sr()).unwrap();
1281        mem.add("c".to_owned(), val(5), sr()).unwrap();
1282        assert_get(mem, "b", 4);
1283        assert_get(mem, "c", 5);
1284
1285        mem.pop_env();
1286        // old snapshot still untouched
1287        mem.memory.get_from_unchecked("b", sn).unwrap_err();
1288    }
1289
1290    #[test]
1291    fn snap_env2() {
1292        let mem = &mut Stack::new_for_tests();
1293        mem.add("a".to_owned(), val(1), sr()).unwrap();
1294
1295        let sn1 = mem.snapshot();
1296        mem.add("b".to_owned(), val(3), sr()).unwrap();
1297
1298        mem.push_new_env_for_call(mem.current_env);
1299        let sn2 = mem.snapshot();
1300        mem.add("b".to_owned(), val(4), sr()).unwrap();
1301        let sn3 = mem.snapshot();
1302        assert_get_from(mem, "b", 3, sn2);
1303        mem.add("c".to_owned(), val(5), sr()).unwrap();
1304        assert_get(mem, "b", 4);
1305        assert_get(mem, "c", 5);
1306
1307        mem.pop_env();
1308        // old snapshots still untouched
1309        mem.memory.get_from_unchecked("b", sn1).unwrap_err();
1310        assert_get_from(mem, "b", 3, sn2);
1311        mem.memory.get_from_unchecked("c", sn2).unwrap_err();
1312        assert_get_from(mem, "b", 4, sn3);
1313        mem.memory.get_from_unchecked("c", sn3).unwrap_err();
1314    }
1315
1316    #[test]
1317    fn squash_env() {
1318        let mem = &mut Stack::new_for_tests();
1319        mem.add("a".to_owned(), val(1), sr()).unwrap();
1320        mem.add("b".to_owned(), val(3), sr()).unwrap();
1321        let sn1 = mem.snapshot();
1322        mem.push_new_env_for_call(sn1);
1323        mem.add("b".to_owned(), val(2), sr()).unwrap();
1324
1325        let sn2 = mem.snapshot();
1326        mem.add(
1327            "f".to_owned(),
1328            KclValue::Function {
1329                value: FunctionSource::User {
1330                    ast: crate::parsing::ast::types::FunctionExpression::dummy(),
1331                    settings: crate::MetaSettings::default(),
1332                    memory: sn2,
1333                },
1334                meta: Vec::new(),
1335            },
1336            sr(),
1337        )
1338        .unwrap();
1339        let old = mem.pop_and_preserve_env();
1340        mem.squash_env(old);
1341        assert_get(mem, "a", 1);
1342        assert_get(mem, "b", 2);
1343        match mem.get("f", SourceRange::default()).unwrap() {
1344            KclValue::Function {
1345                value: FunctionSource::User { memory, .. },
1346                ..
1347            } if memory.0 == mem.current_env.0 => {}
1348            v => panic!("{v:#?}, expected {sn1:?}"),
1349        }
1350        assert_eq!(mem.memory.envs().len(), 2);
1351    }
1352
1353    #[test]
1354    fn two_stacks() {
1355        let stack1 = &mut Stack::new_for_tests();
1356        let stack2 = &mut stack1.memory.clone().new_stack();
1357        stack2.push_new_root_env(false);
1358
1359        stack1.add("a".to_owned(), val(1), sr()).unwrap();
1360        stack1.push_new_env_for_call(stack1.current_env);
1361
1362        stack2.add("a".to_owned(), val(2), sr()).unwrap();
1363        stack2.push_new_env_for_call(stack2.current_env);
1364
1365        stack2.add("a".to_owned(), val(4), sr()).unwrap();
1366        stack2.push_new_env_for_call(stack2.current_env);
1367
1368        stack1.add("a".to_owned(), val(3), sr()).unwrap();
1369        stack1.push_new_env_for_call(stack1.current_env);
1370
1371        stack1.add("a".to_owned(), val(5), sr()).unwrap();
1372        stack1.push_new_env_for_call(stack1.current_env);
1373
1374        stack2.add("a".to_owned(), val(6), sr()).unwrap();
1375        stack2.push_new_env_for_call(stack2.current_env);
1376
1377        stack1.add("a".to_owned(), val(7), sr()).unwrap();
1378        stack2.add("a".to_owned(), val(8), sr()).unwrap();
1379
1380        assert_get(stack1, "a", 7);
1381        assert_get(stack2, "a", 8);
1382
1383        stack1.pop_env();
1384        assert_get(stack1, "a", 5);
1385        assert_get(stack2, "a", 8);
1386        stack2.pop_env();
1387        assert_get(stack1, "a", 5);
1388        assert_get(stack2, "a", 6);
1389
1390        stack2.pop_env();
1391        assert_get(stack2, "a", 4);
1392        stack2.pop_env();
1393        assert_get(stack2, "a", 2);
1394        stack1.pop_env();
1395        assert_get(stack1, "a", 3);
1396        stack1.pop_env();
1397        assert_get(stack1, "a", 1);
1398    }
1399}