Refactor GlobalStore to use separate RTL and user value stores

The `GlobalStore` was refactored to clearly distinguish between
immutable RTL values and mutable user-defined global slots.

The `Environment` struct now holds:
- `rtl_values`: An immutable `Rc<[Value]>` for pre-defined RTL values.
- `user_values`: An `Rc<RefCell<Vec<Value>>>` for user-defined mutable
  globals.

The `GlobalStore` struct now takes both `rtl` and `user` as parameters
and uses `rtl_len` to determine which store to access. This change
separates concerns and better reflects the immutability of RTL values
after the bootstrap phase, aligning with the project's concurrency
rules.

Documentation in `docs/Analysis_Environment.md` was updated to reflect
these structural changes.
This commit is contained in:
2026-03-28 00:02:34 +01:00
parent 0e8cd0832f
commit 982d2239b6
4 changed files with 68 additions and 43 deletions
+34 -21
View File
@@ -58,7 +58,11 @@ pub struct Rtl {
pub struct Environment {
pub root_types: Rc<RefCell<Vec<StaticType>>>,
pub root_purity: Rc<RefCell<Vec<Purity>>>,
pub root_values: Rc<RefCell<Vec<Value>>>,
/// Frozen RTL value slice — shared across all environments from the same [`Rtl`].
/// Empty placeholder during the bootstrap phase; set after the RTL freeze.
rtl_values: Rc<[Value]>,
/// Mutable user-defined global slots. Indexed as `[0..)` i.e. offset from `rtl.slot_count`.
user_values: Rc<RefCell<Vec<Value>>>,
pub fixed_scope_idx: i32,
pub root_scopes: Rc<RefCell<Vec<CompilerScope>>>,
pub root_slot_count: Rc<RefCell<u32>>,
@@ -97,7 +101,7 @@ struct RuntimeMacroEvaluator {
root_scopes: Rc<RefCell<Vec<CompilerScope>>>,
root_slot_count: Rc<RefCell<u32>>,
root_types: Rc<RefCell<Vec<StaticType>>>,
root_values: Rc<RefCell<Vec<Value>>>,
globals: GlobalStore,
root_purity: Rc<RefCell<Vec<Purity>>>,
fixed_scope_idx: i32,
}
@@ -129,8 +133,7 @@ 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(GlobalStore::new(self.root_values.clone(), 0));
// rtl_len=0: MacroEvaluator runs during bootstrap or with flat root_values
let mut vm = VM::new(self.globals.clone());
vm.run(&exec_ast)
}
}
@@ -148,7 +151,8 @@ impl Environment {
let mut env = Self {
root_types: Rc::new(RefCell::new(Vec::new())),
root_purity: Rc::new(RefCell::new(Vec::new())),
root_values: Rc::new(RefCell::new(Vec::new())),
rtl_values: Rc::from([]), // placeholder — filled after freeze
user_values: Rc::new(RefCell::new(Vec::new())), // bootstrap scratch during RTL phase
fixed_scope_idx: -1,
root_scopes: Rc::new(RefCell::new(vec![CompilerScope::new()])),
root_slot_count: Rc::new(RefCell::new(0)),
@@ -179,7 +183,8 @@ 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();
// user_values was used as the bootstrap scratch; freeze it into an immutable slice.
let rtl_vals: Rc<[Value]> = env.user_values.borrow().clone().into();
env.rtl = Rc::new(Rtl {
scope: env.root_scopes.borrow()[0].clone(),
slot_count: rtl_slot_count,
@@ -189,12 +194,13 @@ impl Environment {
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()));
// Set the frozen RTL slice. Stream closures registered during bootstrap
// (e.g. in streams.rs) hold a GlobalStore that captured the OLD user_values
// Rc (bootstrap scratch = RTL values, never written again). After resetting
// user_values here, script VMs use the new empty Rc and grow it with user
// slots. Streams cannot access user globals — RTL-only invariant enforced.
env.rtl_values = Rc::clone(&rtl_vals);
env.user_values = Rc::new(RefCell::new(Vec::new()));
// Push the first mutable user scope (Level 1)
env.root_scopes.borrow_mut().push(CompilerScope::new());
@@ -223,7 +229,8 @@ 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.to_vec())),
rtl_values: Rc::clone(&rtl.values), // O(1) Rc increment — no clone of RTL values!
user_values: Rc::new(RefCell::new(Vec::new())),
fixed_scope_idx: 0,
root_scopes: Rc::new(RefCell::new(scopes)),
root_slot_count: Rc::new(RefCell::new(rtl.slot_count)),
@@ -262,7 +269,11 @@ impl Environment {
/// 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)
GlobalStore::new(
Rc::clone(&self.rtl_values),
Rc::clone(&self.user_values),
self.rtl.slot_count as usize,
)
}
pub fn add_search_path(&self, path: impl AsRef<Path>) {
@@ -292,7 +303,7 @@ impl Environment {
root_scopes: self.root_scopes.clone(),
root_slot_count: self.root_slot_count.clone(),
root_types: self.root_types.clone(),
root_values: self.root_values.clone(),
globals: self.global_store(),
root_purity: self.root_purity.clone(),
fixed_scope_idx: self.fixed_scope_idx,
};
@@ -442,12 +453,14 @@ impl Environment {
let bound_ast = CapturePass::apply(bound_ast, &captures);
// Pre-allocate global slots to prevent out-of-bounds during specialization/optimization
// Pre-allocate user global slots to prevent out-of-bounds during specialization/optimization.
// root_slot_count includes RTL slots; subtract to get the number of user-only slots.
{
let mut values = self.root_values.borrow_mut();
let count = *self.root_slot_count.borrow();
if (count as usize) > values.len() {
values.resize(count as usize, Value::Void);
let mut user = self.user_values.borrow_mut();
let count = *self.root_slot_count.borrow() as usize;
let user_count = count.saturating_sub(self.rtl.slot_count as usize);
if user_count > user.len() {
user.resize(user_count, Value::Void);
}
}
@@ -511,7 +524,7 @@ impl Environment {
*slot_count += 1;
self.root_types.borrow_mut().push(ty);
self.root_purity.borrow_mut().push(purity);
self.root_values.borrow_mut().push(val);
self.user_values.borrow_mut().push(val);
}
pub fn register_native(