Refactor global variable access in VM and optimizer

Replaces `Rc<RefCell<Vec<Value>>>` with a new `GlobalStore` struct for
accessing global variables. This provides a more structured way to
manage global values and allows for future optimizations like sharing
immutable RTL portions across environments. The `GlobalStore` also
includes an `rtl_len` field to distinguish between RTL and user global
slots, enabling debug-time checks for writes to immutable RTL slots.
This commit is contained in:
2026-03-27 23:40:42 +01:00
parent 893d9936ed
commit 0e8cd0832f
6 changed files with 89 additions and 44 deletions
+28 -11
View File
@@ -4,7 +4,7 @@ use crate::ast::compiler::{CapturePass, TypeChecker, TypedNode};
use crate::ast::nodes::{AnalyzedPhase, Symbol, SyntaxKind, SyntaxNode};
use crate::ast::parser::Parser;
use crate::ast::closure::Closure;
use crate::ast::vm::{TracingObserver, VM};
use crate::ast::vm::{GlobalStore, TracingObserver, VM};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
@@ -51,7 +51,7 @@ pub struct Rtl {
pub slot_count: u32,
pub types: Vec<StaticType>,
pub purity: Vec<Purity>,
pub values: Vec<Value>,
pub values: Rc<[Value]>,
pub rtl_docs: Vec<RtlDocEntry>,
}
@@ -129,7 +129,8 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
let exec_ast = Lowering::lower(Analyzer::analyze(&typed_ast, &self.root_purity.borrow())); // Minimal analysis for macro eval
let mut vm = VM::new(self.root_values.clone());
let mut vm = VM::new(GlobalStore::new(self.root_values.clone(), 0));
// rtl_len=0: MacroEvaluator runs during bootstrap or with flat root_values
vm.run(&exec_ast)
}
}
@@ -168,7 +169,7 @@ impl Environment {
slot_count: 0,
types: vec![],
purity: vec![],
values: vec![],
values: Rc::from([]),
rtl_docs: vec![],
}),
};
@@ -178,15 +179,23 @@ impl Environment {
// Phase 2: Freeze scope 0 and snapshot the RTL state.
env.fixed_scope_idx = 0;
let rtl_slot_count = *env.root_slot_count.borrow();
let rtl_vals: Rc<[Value]> = env.root_values.borrow().clone().into();
env.rtl = Rc::new(Rtl {
scope: env.root_scopes.borrow()[0].clone(),
slot_count: rtl_slot_count,
types: env.root_types.borrow().clone(),
purity: env.root_purity.borrow().clone(),
values: env.root_values.borrow().clone(),
values: Rc::clone(&rtl_vals),
rtl_docs: std::mem::take(&mut env.rtl_docs.borrow_mut()),
});
// Replace root_values with a fresh Rc. Any RTL closures registered during
// bootstrap (e.g. stream generators in streams.rs) still hold the OLD Rc,
// which is now a frozen, RTL-only view. Script-level VMs use this new Rc
// and grow it with user slots — streams cannot access user globals.
// This enforces the invariant: stream lambdas are RTL-only execution contexts.
env.root_values = Rc::new(RefCell::new(rtl_vals.to_vec()));
// Push the first mutable user scope (Level 1)
env.root_scopes.borrow_mut().push(CompilerScope::new());
@@ -214,7 +223,7 @@ impl Environment {
let env = Self {
root_types: Rc::new(RefCell::new(rtl.types.clone())),
root_purity: Rc::new(RefCell::new(rtl.purity.clone())),
root_values: Rc::new(RefCell::new(rtl.values.clone())),
root_values: Rc::new(RefCell::new(rtl.values.to_vec())),
fixed_scope_idx: 0,
root_scopes: Rc::new(RefCell::new(scopes)),
root_slot_count: Rc::new(RefCell::new(rtl.slot_count)),
@@ -248,6 +257,14 @@ impl Environment {
Rc::clone(&self.rtl)
}
/// Returns a [`GlobalStore`] view over this environment's global value space.
///
/// The RTL portion is a shared immutable slice; the user portion is a
/// per-environment mutable vec. Both are reference-counted — cloning is cheap.
pub fn global_store(&self) -> GlobalStore {
GlobalStore::new(Rc::clone(&self.root_values), self.rtl.slot_count as usize)
}
pub fn add_search_path(&self, path: impl AsRef<Path>) {
self.search_paths.borrow_mut().push(path.as_ref().to_path_buf());
}
@@ -707,7 +724,7 @@ impl Environment {
// 4. Optimize
let optimizer = Optimizer::new(self.optimization)
.with_globals(self.root_values.clone())
.with_globals(self.global_store())
.with_purity(self.root_purity.clone())
.with_registry(self.typed_function_registry.clone());
let optimized = optimizer.optimize(specialized);
@@ -744,7 +761,7 @@ impl Environment {
}
// Slow path: run the node once to extract the resulting Closure.
let mut vm = VM::new(self.root_values.clone());
let mut vm = VM::new(self.global_store());
let res = vm.run(&node)?;
if let Value::Closure(obj) = res {
Ok(obj)
@@ -755,7 +772,7 @@ impl Environment {
/// Creates a new `VM` connected to this environment's global scope.
pub fn create_vm(&self) -> VM {
VM::new(self.root_values.clone())
VM::new(self.global_store())
}
fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
@@ -766,7 +783,7 @@ impl Environment {
let rtl_lookup = make_rtl_lookup();
let typed_reg = self.typed_function_registry.clone();
let mono_cache = self.monomorph_cache.clone();
let root_values = self.root_values.clone();
let root_values = self.global_store();
let root_types = self.root_types.clone();
let root_purity = self.root_purity.clone();
let optimization = self.optimization;
@@ -866,7 +883,7 @@ impl Environment {
let compiled = self.compile(source).into_result()?;
let linked = self.link(compiled);
let mut vm = VM::new(self.root_values.clone());
let mut vm = VM::new(self.global_store());
let mut observer = TracingObserver::new();
// 1. Run the script wrapper (returns a closure representing the script)