Skip to main content

kcl_lib/execution/
memory.rs

1//! Facade for KCL memory implementations.
2//!
3//! Keeping memory implementations behind this facade lets new memory backends
4//! be added without moving call sites first.
5
6mod arena;
7
8use std::env;
9use std::fmt;
10use std::sync::Arc;
11#[cfg(test)]
12use std::sync::atomic::AtomicU8;
13use std::sync::atomic::AtomicUsize;
14#[cfg(test)]
15use std::sync::atomic::Ordering;
16
17use indexmap::IndexMap;
18use serde::Deserialize;
19use serde::Serialize;
20
21use crate::SourceRange;
22use crate::errors::KclError;
23use crate::execution::KclValue;
24
25/// The distinguished name of the return value of a function.
26pub(crate) const RETURN_NAME: &str = "__return";
27/// Low-budget namespacing for types and modules.
28pub(crate) const TYPE_PREFIX: &str = "__ty_";
29pub(crate) const MODULE_PREFIX: &str = "__mod_";
30pub(crate) const SKETCH_PREFIX: &str = "__sketch_";
31
32pub(crate) const KCL_MEMORY_IMPL_ENV_VAR: &str = "KCL_MEMORY_IMPL";
33
34/// An index pointing to an environment at a point in time.
35///
36/// The first field indexes an environment, the second field is an epoch. An epoch of 0 is indicates
37/// a dummy, error, or placeholder env ref, an epoch of `usize::MAX` represents the current most
38/// recent epoch.
39#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Hash, Eq, ts_rs::TS)]
40pub struct EnvironmentRef(pub(crate) usize, pub(crate) usize);
41
42impl EnvironmentRef {
43    pub fn dummy() -> Self {
44        Self(usize::MAX, 0)
45    }
46
47    pub(crate) fn current(index: usize) -> Self {
48        Self(index, usize::MAX)
49    }
50
51    pub(crate) fn at_epoch(index: usize, epoch: usize) -> Self {
52        Self(index, epoch)
53    }
54
55    pub(crate) fn is_regular(&self) -> bool {
56        self.0 < usize::MAX && self.1 > 0
57    }
58
59    pub(crate) fn index(&self) -> usize {
60        self.0
61    }
62
63    pub(crate) fn epoch(&self) -> usize {
64        self.1
65    }
66
67    pub(crate) fn skip_env(&self) -> bool {
68        self.0 == usize::MAX
69    }
70
71    /// Replace only the env index if it matches `old`.
72    pub fn replace_env(&mut self, old: Self, new: Self) {
73        if self.0 == old.0 {
74            self.0 = new.0;
75        }
76    }
77
78    /// Replace if it matches `old`.
79    pub fn replace_env_and_epoch(&mut self, old: Self, new: Self) {
80        if self.0 == old.0 && self.1 == old.1 {
81            self.0 = new.0;
82            self.1 = new.1;
83        }
84    }
85}
86
87// TODO keep per-stack stats to avoid so many atomic updates
88#[derive(Debug, Default)]
89pub(crate) struct MemoryStats {
90    // Total number of environments created.
91    env_count: AtomicUsize,
92    // Total number of epochs.
93    epoch_count: AtomicUsize,
94    // Total number of values inserted or updated.
95    mutation_count: AtomicUsize,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub(crate) enum MemoryBackendKind {
100    Arena,
101}
102
103impl MemoryBackendKind {
104    pub(crate) fn from_env() -> Self {
105        #[cfg(test)]
106        if let Some(backend) = Self::test_override() {
107            return backend;
108        }
109
110        match env::var(KCL_MEMORY_IMPL_ENV_VAR) {
111            Ok(value) => Self::parse(&value),
112            Err(env::VarError::NotPresent) => Self::Arena,
113            Err(env::VarError::NotUnicode(value)) => {
114                panic!(
115                    "{KCL_MEMORY_IMPL_ENV_VAR} must be valid unicode; got `{}`.",
116                    value.to_string_lossy()
117                )
118            }
119        }
120    }
121
122    fn parse(value: &str) -> Self {
123        if value.trim().is_empty() || value.eq_ignore_ascii_case("arena") {
124            return Self::Arena;
125        }
126
127        panic!("Unsupported {KCL_MEMORY_IMPL_ENV_VAR} value `{value}`. Expected `arena`.",);
128    }
129
130    #[cfg(test)]
131    pub(crate) fn all() -> &'static [Self] {
132        &[Self::Arena]
133    }
134
135    #[cfg(test)]
136    pub(crate) fn override_for_test(backend: Self) -> MemoryBackendOverrideGuard {
137        let previous = TEST_BACKEND_OVERRIDE.swap(backend.test_override_value(), Ordering::SeqCst);
138        MemoryBackendOverrideGuard { previous }
139    }
140
141    #[cfg(test)]
142    fn test_override() -> Option<Self> {
143        match TEST_BACKEND_OVERRIDE.load(Ordering::SeqCst) {
144            2 => Some(Self::Arena),
145            _ => None,
146        }
147    }
148
149    #[cfg(test)]
150    fn test_override_value(self) -> u8 {
151        match self {
152            Self::Arena => 2,
153        }
154    }
155}
156
157#[cfg(test)]
158static TEST_BACKEND_OVERRIDE: AtomicU8 = AtomicU8::new(0);
159
160#[cfg(test)]
161pub(crate) struct MemoryBackendOverrideGuard {
162    previous: u8,
163}
164
165#[cfg(test)]
166impl Drop for MemoryBackendOverrideGuard {
167    fn drop(&mut self) {
168        TEST_BACKEND_OVERRIDE.store(self.previous, Ordering::SeqCst);
169    }
170}
171
172#[derive(Debug)]
173enum ProgramMemoryBackend {
174    Arena(Arc<arena::ProgramMemory>),
175}
176
177#[derive(Debug, Clone)]
178enum StackBackend {
179    Arena(arena::Stack),
180}
181
182/// Switchable KCL memory facade.
183#[derive(Debug)]
184pub(crate) struct ProgramMemory {
185    backend: ProgramMemoryBackend,
186}
187
188/// Switchable KCL stack facade.
189#[derive(Debug, Clone)]
190pub(crate) struct Stack {
191    pub(crate) memory: Arc<ProgramMemory>,
192    backend: StackBackend,
193}
194
195impl fmt::Display for ProgramMemory {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        match &self.backend {
198            ProgramMemoryBackend::Arena(memory) => memory.fmt(f),
199        }
200    }
201}
202
203impl fmt::Display for Stack {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        match &self.backend {
206            StackBackend::Arena(stack) => stack.fmt(f),
207        }
208    }
209}
210
211impl ProgramMemory {
212    #[allow(clippy::new_without_default)]
213    pub fn new() -> Arc<Self> {
214        Self::new_with_backend(MemoryBackendKind::from_env())
215    }
216
217    pub(crate) fn new_with_backend(backend: MemoryBackendKind) -> Arc<Self> {
218        match backend {
219            MemoryBackendKind::Arena => Arc::new(Self {
220                backend: ProgramMemoryBackend::Arena(arena::ProgramMemory::new()),
221            }),
222        }
223    }
224
225    pub fn new_stack(self: Arc<Self>) -> Stack {
226        let backend = match &self.backend {
227            ProgramMemoryBackend::Arena(memory) => StackBackend::Arena(Arc::clone(memory).new_stack()),
228        };
229
230        Stack { memory: self, backend }
231    }
232
233    pub fn set_std(self: &mut Arc<Self>, std: EnvironmentRef) -> Result<(), KclError> {
234        match &self.backend {
235            ProgramMemoryBackend::Arena(memory) => {
236                let mut memory = Arc::clone(memory);
237                memory.set_std(std)
238            }
239        }
240    }
241
242    pub fn requires_std(&self) -> bool {
243        match &self.backend {
244            ProgramMemoryBackend::Arena(memory) => memory.requires_std(),
245        }
246    }
247
248    pub(crate) fn stats(&self) -> &MemoryStats {
249        match &self.backend {
250            ProgramMemoryBackend::Arena(memory) => &memory.stats,
251        }
252    }
253
254    pub fn get_from_owned(
255        &self,
256        var: &str,
257        env_ref: EnvironmentRef,
258        source_range: SourceRange,
259        owner: usize,
260    ) -> Result<KclValue, KclError> {
261        match &self.backend {
262            ProgramMemoryBackend::Arena(memory) => memory.get_from_owned(var, env_ref, source_range, owner),
263        }
264    }
265
266    #[cfg(test)]
267    pub fn get_from_unchecked(&self, var: &str, env_ref: EnvironmentRef) -> Result<KclValue, KclError> {
268        match &self.backend {
269            ProgramMemoryBackend::Arena(memory) => memory.get_from(var, env_ref, SourceRange::default(), 0),
270        }
271    }
272}
273
274impl Stack {
275    pub fn deep_clone(&self) -> Result<Stack, KclError> {
276        match &self.backend {
277            StackBackend::Arena(stack) => {
278                let stack = stack.deep_clone()?;
279                Ok(Stack {
280                    memory: Arc::new(ProgramMemory {
281                        backend: ProgramMemoryBackend::Arena(Arc::clone(&stack.memory)),
282                    }),
283                    backend: StackBackend::Arena(stack),
284                })
285            }
286        }
287    }
288
289    #[cfg(test)]
290    pub fn new_for_tests() -> Stack {
291        Self::new_for_tests_with_backend(MemoryBackendKind::from_env())
292    }
293
294    #[cfg(test)]
295    pub(crate) fn new_for_tests_with_backend(backend: MemoryBackendKind) -> Stack {
296        let mut stack = ProgramMemory::new_with_backend(backend).new_stack();
297        stack
298            .push_new_root_env(false)
299            .expect("test stack root environment should be created");
300        let std = stack.current_env_ref();
301        stack
302            .memory
303            .set_std(std)
304            .expect("test standard library prelude should be initialized");
305        stack
306    }
307
308    pub fn current_epoch(&self) -> usize {
309        match &self.backend {
310            StackBackend::Arena(stack) => stack.current_epoch(),
311        }
312    }
313
314    #[cfg(test)]
315    pub(crate) fn current_env_ref(&self) -> EnvironmentRef {
316        match &self.backend {
317            StackBackend::Arena(stack) => stack.current_env_ref(),
318        }
319    }
320
321    pub fn push_new_env_for_call(&mut self, parent: EnvironmentRef) -> Result<(), KclError> {
322        match &mut self.backend {
323            StackBackend::Arena(stack) => stack.push_new_env_for_call(parent),
324        }
325    }
326
327    pub fn push_new_env_for_scope(&mut self) -> Result<(), KclError> {
328        match &mut self.backend {
329            StackBackend::Arena(stack) => stack.push_new_env_for_scope(),
330        }
331    }
332
333    pub fn push_new_root_env(&mut self, include_prelude: bool) -> Result<(), KclError> {
334        match &mut self.backend {
335            StackBackend::Arena(stack) => stack.push_new_root_env(include_prelude),
336        }
337    }
338
339    pub fn restore_env(&mut self, env: EnvironmentRef) -> Result<(), KclError> {
340        match &mut self.backend {
341            StackBackend::Arena(stack) => stack.restore_env(env),
342        }
343    }
344
345    pub fn pop_env(&mut self) -> Result<EnvironmentRef, KclError> {
346        match &mut self.backend {
347            StackBackend::Arena(stack) => stack.pop_env(),
348        }
349    }
350
351    pub fn pop_and_preserve_env(&mut self) -> Result<EnvironmentRef, KclError> {
352        match &mut self.backend {
353            StackBackend::Arena(stack) => stack.pop_and_preserve_env(),
354        }
355    }
356
357    pub fn squash_env(&mut self, old: EnvironmentRef) -> Result<(), KclError> {
358        match &mut self.backend {
359            StackBackend::Arena(stack) => stack.squash_env(old),
360        }
361    }
362
363    pub fn snapshot(&mut self) -> Result<EnvironmentRef, KclError> {
364        match &mut self.backend {
365            StackBackend::Arena(stack) => stack.snapshot(),
366        }
367    }
368
369    pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> {
370        match &mut self.backend {
371            StackBackend::Arena(stack) => stack.add(key, value, source_range),
372        }
373    }
374
375    pub fn add_recursive_closure(
376        &mut self,
377        key: String,
378        value: KclValue,
379        placeholder_env_ref: EnvironmentRef,
380        source_range: SourceRange,
381    ) -> Result<KclValue, KclError> {
382        match &mut self.backend {
383            StackBackend::Arena(stack) => stack.add_recursive_closure(key, value, placeholder_env_ref, source_range),
384        }
385    }
386
387    pub fn update(&mut self, key: &str, f: impl Fn(&mut KclValue, usize)) -> Result<(), KclError> {
388        match &mut self.backend {
389            StackBackend::Arena(stack) => stack.update(key, f),
390        }
391    }
392
393    pub fn get(&self, var: &str, source_range: SourceRange) -> Result<KclValue, KclError> {
394        match &self.backend {
395            StackBackend::Arena(stack) => stack.get(var, source_range),
396        }
397    }
398
399    pub fn get_owned(&self, var: &str, source_range: SourceRange) -> Result<KclValue, KclError> {
400        match &self.backend {
401            StackBackend::Arena(stack) => stack.get_owned(var, source_range),
402        }
403    }
404
405    pub fn cur_frame_contains(&self, var: &str) -> Result<bool, KclError> {
406        match &self.backend {
407            StackBackend::Arena(stack) => stack.cur_frame_contains(var),
408        }
409    }
410
411    pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<(usize, KclValue), KclError> {
412        match &self.backend {
413            StackBackend::Arena(stack) => stack.get_from_call_stack(key, source_range),
414        }
415    }
416
417    pub fn find_keys_in_current_env(&self, pred: impl Fn(&KclValue) -> bool) -> Result<Vec<String>, KclError> {
418        match &self.backend {
419            StackBackend::Arena(stack) => stack.find_keys_in_current_env(pred),
420        }
421    }
422
423    pub fn find_all_in_current_env(&self) -> Result<Vec<(String, KclValue)>, KclError> {
424        match &self.backend {
425            StackBackend::Arena(stack) => stack.find_all_in_current_env(),
426        }
427    }
428
429    pub fn find_all_in_env(&self, env: EnvironmentRef) -> Result<Vec<(String, KclValue)>, KclError> {
430        match &self.backend {
431            StackBackend::Arena(stack) => stack.find_all_in_env(env),
432        }
433    }
434
435    pub(crate) fn find_all_in_env_owned(&self, env: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
436        Ok(self.find_all_in_env(env)?.into_iter().collect())
437    }
438
439    pub(crate) fn find_var_name_in_all_envs(
440        &self,
441        pred: impl Fn(&KclValue) -> bool,
442    ) -> Result<Option<String>, KclError> {
443        match &self.backend {
444            StackBackend::Arena(stack) => stack.find_var_name_in_all_envs(pred),
445        }
446    }
447
448    pub fn walk_call_stack_with<T>(&self, f: impl FnMut(&KclValue) -> Option<T>) -> Result<Vec<T>, KclError> {
449        match &self.backend {
450            StackBackend::Arena(stack) => stack.walk_call_stack_with(f),
451        }
452    }
453}
454
455#[cfg(test)]
456impl PartialEq for Stack {
457    fn eq(&self, other: &Self) -> bool {
458        let vars = self
459            .find_keys_in_current_env(|_| true)
460            .expect("stack equality should enumerate current env");
461        let vars_other = other
462            .find_keys_in_current_env(|_| true)
463            .expect("stack equality should enumerate other current env");
464        if vars != vars_other {
465            return false;
466        }
467
468        vars.iter().all(|key| {
469            self.get(key, SourceRange::default()).unwrap() == other.get(key, SourceRange::default()).unwrap()
470        })
471    }
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477
478    #[test]
479    fn parses_arena_backend_name() {
480        assert_eq!(MemoryBackendKind::parse("arena"), MemoryBackendKind::Arena);
481        assert_eq!(MemoryBackendKind::parse("ArEnA"), MemoryBackendKind::Arena);
482    }
483
484    #[test]
485    fn empty_backend_name_uses_arena() {
486        assert_eq!(MemoryBackendKind::parse(""), MemoryBackendKind::Arena);
487        assert_eq!(MemoryBackendKind::parse("   "), MemoryBackendKind::Arena);
488    }
489
490    #[test]
491    #[should_panic(expected = "Unsupported KCL_MEMORY_IMPL value `frozen`. Expected `arena`.")]
492    fn unsupported_backend_name_panics() {
493        MemoryBackendKind::parse("frozen");
494    }
495}