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:
@@ -3,6 +3,7 @@ use crate::ast::nodes::{
|
|||||||
IdentifierBinding, LambdaBinding, Node, NodeKind, UpvalueIdx, VirtualId,
|
IdentifierBinding, LambdaBinding, Node, NodeKind, UpvalueIdx, VirtualId,
|
||||||
};
|
};
|
||||||
use crate::ast::types::{Purity, Value};
|
use crate::ast::types::{Purity, Value};
|
||||||
|
use crate::ast::vm::GlobalStore;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -15,7 +16,7 @@ use super::utils::{collect_pattern_addrs, PathTracker, UsageInfo};
|
|||||||
pub struct Optimizer {
|
pub struct Optimizer {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
max_passes: usize,
|
max_passes: usize,
|
||||||
pub globals: Option<Rc<RefCell<Vec<Value>>>>,
|
pub globals: Option<GlobalStore>,
|
||||||
pub root_purity: Option<Rc<RefCell<Vec<Purity>>>>,
|
pub root_purity: Option<Rc<RefCell<Vec<Purity>>>>,
|
||||||
pub lambda_registry: Option<Rc<RefCell<GlobalAnalyzedRegistry>>>,
|
pub lambda_registry: Option<Rc<RefCell<GlobalAnalyzedRegistry>>>,
|
||||||
}
|
}
|
||||||
@@ -31,7 +32,7 @@ impl Optimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_globals(mut self, globals: Rc<RefCell<Vec<Value>>>) -> Self {
|
pub fn with_globals(mut self, globals: GlobalStore) -> Self {
|
||||||
self.globals = Some(globals);
|
self.globals = Some(globals);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -127,14 +128,11 @@ impl Optimizer {
|
|||||||
|
|
||||||
// 3. Fallback for Globals: check the actual VM environment
|
// 3. Fallback for Globals: check the actual VM environment
|
||||||
if let Address::Global(idx) = addr
|
if let Address::Global(idx) = addr
|
||||||
&& let Some(globals_rc) = &self.globals
|
&& let Some(globals_store) = &self.globals
|
||||||
|
&& let Some(val) = globals_store.get(idx.0 as usize)
|
||||||
|
&& inliner.is_inlinable_value(&val, addr)
|
||||||
{
|
{
|
||||||
let globals = globals_rc.borrow();
|
return Rc::new(folder.make_constant_node(val, node));
|
||||||
if let Some(val) = globals.get(idx.0 as usize)
|
|
||||||
&& inliner.is_inlinable_value(val, addr)
|
|
||||||
{
|
|
||||||
return Rc::new(folder.make_constant_node(val.clone(), node));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ use crate::ast::nodes::{
|
|||||||
Address, AnalyzedNode, IdentifierBinding, Node, NodeKind, NodeMetrics,
|
Address, AnalyzedNode, IdentifierBinding, Node, NodeKind, NodeMetrics,
|
||||||
};
|
};
|
||||||
use crate::ast::types::{Purity, RecordLayout, StaticType, Value};
|
use crate::ast::types::{Purity, RecordLayout, StaticType, Value};
|
||||||
use std::cell::RefCell;
|
use crate::ast::vm::GlobalStore;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct Folder<'a> {
|
pub struct Folder<'a> {
|
||||||
pub globals: &'a Option<Rc<RefCell<Vec<Value>>>>,
|
pub globals: &'a Option<GlobalStore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Folder<'a> {
|
impl<'a> Folder<'a> {
|
||||||
pub fn new(globals: &'a Option<Rc<RefCell<Vec<Value>>>>) -> Self {
|
pub fn new(globals: &'a Option<GlobalStore>) -> Self {
|
||||||
Self { globals }
|
Self { globals }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ impl<'a> Folder<'a> {
|
|||||||
NodeKind::Identifier {
|
NodeKind::Identifier {
|
||||||
binding: IdentifierBinding::Reference(Address::Global(idx)),
|
binding: IdentifierBinding::Reference(Address::Global(idx)),
|
||||||
..
|
..
|
||||||
} => self.globals.as_ref()?.borrow().get(idx.0 as usize)?.clone(),
|
} => self.globals.as_ref()?.get(idx.0 as usize)?,
|
||||||
NodeKind::Constant(val) => val.clone(),
|
NodeKind::Constant(val) => val.clone(),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::ast::nodes::{
|
|||||||
Address, AnalyzedNode, IdentifierBinding, NodeKind, VirtualId,
|
Address, AnalyzedNode, IdentifierBinding, NodeKind, VirtualId,
|
||||||
};
|
};
|
||||||
use crate::ast::types::{Purity, Value};
|
use crate::ast::types::{Purity, Value};
|
||||||
|
use crate::ast::vm::GlobalStore;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -11,13 +12,13 @@ use super::substitution_map::SubstitutionMap;
|
|||||||
use super::utils::UsageInfo;
|
use super::utils::UsageInfo;
|
||||||
|
|
||||||
pub struct Inliner<'a> {
|
pub struct Inliner<'a> {
|
||||||
pub globals: &'a Option<Rc<RefCell<Vec<Value>>>>,
|
pub globals: &'a Option<GlobalStore>,
|
||||||
pub root_purity: &'a Option<Rc<RefCell<Vec<Purity>>>>,
|
pub root_purity: &'a Option<Rc<RefCell<Vec<Purity>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Inliner<'a> {
|
impl<'a> Inliner<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
globals: &'a Option<Rc<RefCell<Vec<Value>>>>,
|
globals: &'a Option<GlobalStore>,
|
||||||
root_purity: &'a Option<Rc<RefCell<Vec<Purity>>>>,
|
root_purity: &'a Option<Rc<RefCell<Vec<Purity>>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
+28
-11
@@ -4,7 +4,7 @@ use crate::ast::compiler::{CapturePass, TypeChecker, TypedNode};
|
|||||||
use crate::ast::nodes::{AnalyzedPhase, Symbol, SyntaxKind, SyntaxNode};
|
use crate::ast::nodes::{AnalyzedPhase, Symbol, SyntaxKind, SyntaxNode};
|
||||||
use crate::ast::parser::Parser;
|
use crate::ast::parser::Parser;
|
||||||
use crate::ast::closure::Closure;
|
use crate::ast::closure::Closure;
|
||||||
use crate::ast::vm::{TracingObserver, VM};
|
use crate::ast::vm::{GlobalStore, TracingObserver, VM};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -51,7 +51,7 @@ pub struct Rtl {
|
|||||||
pub slot_count: u32,
|
pub slot_count: u32,
|
||||||
pub types: Vec<StaticType>,
|
pub types: Vec<StaticType>,
|
||||||
pub purity: Vec<Purity>,
|
pub purity: Vec<Purity>,
|
||||||
pub values: Vec<Value>,
|
pub values: Rc<[Value]>,
|
||||||
pub rtl_docs: Vec<RtlDocEntry>,
|
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 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)
|
vm.run(&exec_ast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,7 +169,7 @@ impl Environment {
|
|||||||
slot_count: 0,
|
slot_count: 0,
|
||||||
types: vec![],
|
types: vec![],
|
||||||
purity: vec![],
|
purity: vec![],
|
||||||
values: vec![],
|
values: Rc::from([]),
|
||||||
rtl_docs: vec![],
|
rtl_docs: vec![],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -178,15 +179,23 @@ impl Environment {
|
|||||||
// Phase 2: Freeze scope 0 and snapshot the RTL state.
|
// Phase 2: Freeze scope 0 and snapshot the RTL state.
|
||||||
env.fixed_scope_idx = 0;
|
env.fixed_scope_idx = 0;
|
||||||
let rtl_slot_count = *env.root_slot_count.borrow();
|
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 {
|
env.rtl = Rc::new(Rtl {
|
||||||
scope: env.root_scopes.borrow()[0].clone(),
|
scope: env.root_scopes.borrow()[0].clone(),
|
||||||
slot_count: rtl_slot_count,
|
slot_count: rtl_slot_count,
|
||||||
types: env.root_types.borrow().clone(),
|
types: env.root_types.borrow().clone(),
|
||||||
purity: env.root_purity.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()),
|
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)
|
// Push the first mutable user scope (Level 1)
|
||||||
env.root_scopes.borrow_mut().push(CompilerScope::new());
|
env.root_scopes.borrow_mut().push(CompilerScope::new());
|
||||||
|
|
||||||
@@ -214,7 +223,7 @@ impl Environment {
|
|||||||
let env = Self {
|
let env = Self {
|
||||||
root_types: Rc::new(RefCell::new(rtl.types.clone())),
|
root_types: Rc::new(RefCell::new(rtl.types.clone())),
|
||||||
root_purity: Rc::new(RefCell::new(rtl.purity.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,
|
fixed_scope_idx: 0,
|
||||||
root_scopes: Rc::new(RefCell::new(scopes)),
|
root_scopes: Rc::new(RefCell::new(scopes)),
|
||||||
root_slot_count: Rc::new(RefCell::new(rtl.slot_count)),
|
root_slot_count: Rc::new(RefCell::new(rtl.slot_count)),
|
||||||
@@ -248,6 +257,14 @@ impl Environment {
|
|||||||
Rc::clone(&self.rtl)
|
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>) {
|
pub fn add_search_path(&self, path: impl AsRef<Path>) {
|
||||||
self.search_paths.borrow_mut().push(path.as_ref().to_path_buf());
|
self.search_paths.borrow_mut().push(path.as_ref().to_path_buf());
|
||||||
}
|
}
|
||||||
@@ -707,7 +724,7 @@ impl Environment {
|
|||||||
|
|
||||||
// 4. Optimize
|
// 4. Optimize
|
||||||
let optimizer = Optimizer::new(self.optimization)
|
let optimizer = Optimizer::new(self.optimization)
|
||||||
.with_globals(self.root_values.clone())
|
.with_globals(self.global_store())
|
||||||
.with_purity(self.root_purity.clone())
|
.with_purity(self.root_purity.clone())
|
||||||
.with_registry(self.typed_function_registry.clone());
|
.with_registry(self.typed_function_registry.clone());
|
||||||
let optimized = optimizer.optimize(specialized);
|
let optimized = optimizer.optimize(specialized);
|
||||||
@@ -744,7 +761,7 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slow path: run the node once to extract the resulting Closure.
|
// 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)?;
|
let res = vm.run(&node)?;
|
||||||
if let Value::Closure(obj) = res {
|
if let Value::Closure(obj) = res {
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
@@ -755,7 +772,7 @@ impl Environment {
|
|||||||
|
|
||||||
/// Creates a new `VM` connected to this environment's global scope.
|
/// Creates a new `VM` connected to this environment's global scope.
|
||||||
pub fn create_vm(&self) -> VM {
|
pub fn create_vm(&self) -> VM {
|
||||||
VM::new(self.root_values.clone())
|
VM::new(self.global_store())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
|
fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
|
||||||
@@ -766,7 +783,7 @@ impl Environment {
|
|||||||
let rtl_lookup = make_rtl_lookup();
|
let rtl_lookup = make_rtl_lookup();
|
||||||
let typed_reg = self.typed_function_registry.clone();
|
let typed_reg = self.typed_function_registry.clone();
|
||||||
let mono_cache = self.monomorph_cache.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_types = self.root_types.clone();
|
||||||
let root_purity = self.root_purity.clone();
|
let root_purity = self.root_purity.clone();
|
||||||
let optimization = self.optimization;
|
let optimization = self.optimization;
|
||||||
@@ -866,7 +883,7 @@ impl Environment {
|
|||||||
let compiled = self.compile(source).into_result()?;
|
let compiled = self.compile(source).into_result()?;
|
||||||
let linked = self.link(compiled);
|
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();
|
let mut observer = TracingObserver::new();
|
||||||
|
|
||||||
// 1. Run the script wrapper (returns a closure representing the script)
|
// 1. Run the script wrapper (returns a closure representing the script)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::ast::rtl::series::{RingBuffer, ScalarValue, SeriesMember};
|
use crate::ast::rtl::series::{RingBuffer, ScalarValue, SeriesMember};
|
||||||
use crate::ast::types::{Object, PipeFn, Value};
|
use crate::ast::types::{Object, PipeFn, Value};
|
||||||
use crate::ast::vm::VM;
|
use crate::ast::vm::{GlobalStore, VM};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -473,7 +473,10 @@ pub fn register(env: &Environment) {
|
|||||||
|
|
||||||
// (create-ticker condition-closure) -> StreamNode
|
// (create-ticker condition-closure) -> StreamNode
|
||||||
let ticker_generators = env.pipeline_generators.clone();
|
let ticker_generators = env.pipeline_generators.clone();
|
||||||
let globals = env.root_values.clone();
|
// Captures root_values at registration time (during bootstrap = RTL-only).
|
||||||
|
// After Environment::new() replaces root_values, this Rc becomes a frozen
|
||||||
|
// RTL-only view — enforcing that stream lambdas cannot access user globals.
|
||||||
|
let globals = GlobalStore::new(env.root_values.clone(), 0);
|
||||||
|
|
||||||
env.register_native_fn(
|
env.register_native_fn(
|
||||||
"create-ticker",
|
"create-ticker",
|
||||||
@@ -533,7 +536,8 @@ pub fn register(env: &Environment) {
|
|||||||
// (pipe inputs lambda) -> StreamNode
|
// (pipe inputs lambda) -> StreamNode
|
||||||
// inputs: a Tuple of StreamNodes or a single StreamNode
|
// inputs: a Tuple of StreamNodes or a single StreamNode
|
||||||
// lambda: a Closure or Function
|
// lambda: a Closure or Function
|
||||||
let globals = env.root_values.clone();
|
// Same RTL-only capture as create-ticker above.
|
||||||
|
let globals = GlobalStore::new(env.root_values.clone(), 0);
|
||||||
env.register_native_fn(
|
env.register_native_fn(
|
||||||
"pipe",
|
"pipe",
|
||||||
StaticType::PolymorphicFn {
|
StaticType::PolymorphicFn {
|
||||||
|
|||||||
+40
-15
@@ -82,9 +82,43 @@ impl VMObserver for TracingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unified view of the global value space for VM and optimizer access.
|
||||||
|
///
|
||||||
|
/// Wraps a flat `Vec<Value>` containing both RTL slots `[0..rtl_len)` and user
|
||||||
|
/// slots `[rtl_len..)`. The `rtl_len` boundary is informational — it enables
|
||||||
|
/// a debug-mode write guard and prepares for future RTL sharing.
|
||||||
|
///
|
||||||
|
/// True RTL/user splitting (sharing the immutable RTL portion across environments)
|
||||||
|
/// is a planned future optimization once the stream-bootstrap timing is resolved.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GlobalStore {
|
||||||
|
values: Rc<RefCell<Vec<Value>>>,
|
||||||
|
/// Index boundary between frozen RTL slots and mutable user slots.
|
||||||
|
pub rtl_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalStore {
|
||||||
|
pub fn new(values: Rc<RefCell<Vec<Value>>>, rtl_len: usize) -> Self {
|
||||||
|
Self { values, rtl_len }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, idx: usize) -> Option<Value> {
|
||||||
|
self.values.borrow().get(idx).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&self, idx: usize, value: Value) {
|
||||||
|
debug_assert!(idx >= self.rtl_len, "Cannot write to immutable RTL slot {}", idx);
|
||||||
|
let mut v = self.values.borrow_mut();
|
||||||
|
if idx >= v.len() {
|
||||||
|
v.resize(idx + 1, Value::Void);
|
||||||
|
}
|
||||||
|
v[idx] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct VM {
|
pub struct VM {
|
||||||
stack: Vec<Value>,
|
stack: Vec<Value>,
|
||||||
globals: Rc<RefCell<Vec<Value>>>,
|
globals: GlobalStore,
|
||||||
frames: Vec<CallFrame>,
|
frames: Vec<CallFrame>,
|
||||||
/// Side-channel for tail-call signaling. Set by `eval_internal` when a tail-call
|
/// Side-channel for tail-call signaling. Set by `eval_internal` when a tail-call
|
||||||
/// is requested; consumed by `resolve_tail_calls` and the inline TCO loop.
|
/// is requested; consumed by `resolve_tail_calls` and the inline TCO loop.
|
||||||
@@ -92,7 +126,7 @@ pub struct VM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VM {
|
impl VM {
|
||||||
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self {
|
pub fn new(globals: GlobalStore) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack: Vec::new(),
|
stack: Vec::new(),
|
||||||
globals,
|
globals,
|
||||||
@@ -883,13 +917,9 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Address::Global(idx) => {
|
Address::Global(idx) => {
|
||||||
let g_idx = idx.0 as usize;
|
self.globals
|
||||||
let globals = self.globals.borrow();
|
.get(idx.0 as usize)
|
||||||
if g_idx < globals.len() {
|
.ok_or_else(|| format!("Global access out of bounds {}", idx))
|
||||||
Ok(globals[g_idx].clone())
|
|
||||||
} else {
|
|
||||||
Err(format!("Global access out of bounds {}", idx))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Address::Upvalue(idx) => {
|
Address::Upvalue(idx) => {
|
||||||
let frame = self.frames.last().ok_or("No call frame")?;
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
@@ -926,12 +956,7 @@ impl VM {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Address::Global(idx) => {
|
Address::Global(idx) => {
|
||||||
let g_idx = idx.0 as usize;
|
self.globals.set(idx.0 as usize, value);
|
||||||
let mut globals = self.globals.borrow_mut();
|
|
||||||
if g_idx >= globals.len() {
|
|
||||||
globals.resize(g_idx + 1, Value::Void);
|
|
||||||
}
|
|
||||||
globals[g_idx] = value;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Address::Upvalue(idx) => {
|
Address::Upvalue(idx) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user