Refactor: Implement late stack size calculation
This commit introduces a new mechanism for calculating the required stack size for closures at bind time, rather than storing it in the AST. This is achieved by adding a `calculate_stack_size` method to `BoundNode`. Key changes include: - `BoundNode::calculate_stack_size`: Recursively traverses the bound AST to find the maximum `LocalSlot` index used. - Recursion blocker for lambdas: Ensures that nested lambdas are not visited during stack size calculation for the outer closure, preventing incorrect stack size estimations. - VM update: The `VM::run` method now accepts and uses the pre-calculated stack size for setting up call frames. - `Closure` struct update: Stores the `stack_size` directly within the `Closure` struct. - Binder and TypeChecker updates: Minor adjustments to accommodate the new strategy and error reporting. - Optimizer enhancements: Constant and pure-lambda propagation for `Define` statements within blocks to ensure inlinability. This refactoring addresses issues related to accurate stack size management and improves the overall robustness of the binder and VM.
This commit is contained in:
+23
-8
@@ -144,7 +144,8 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
|
||||
let exec_ast = TCO::optimize(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval
|
||||
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
vm.run(&exec_ast)
|
||||
let stack_size = exec_ast.calculate_stack_size();
|
||||
vm.run(&exec_ast, stack_size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,12 +601,14 @@ impl Environment {
|
||||
} = &node.kind
|
||||
&& upvalues.is_empty()
|
||||
{
|
||||
let stack_size = node.calculate_stack_size();
|
||||
let closure = Rc::new(crate::ast::vm::Closure::new(
|
||||
params.ty.original.clone(),
|
||||
body.ty.original.clone(),
|
||||
body.clone(),
|
||||
vec![],
|
||||
*positional_count,
|
||||
stack_size,
|
||||
));
|
||||
let closure_obj: Rc<dyn crate::ast::types::Object> = closure;
|
||||
|
||||
@@ -626,7 +629,7 @@ impl Environment {
|
||||
purity: Purity::Impure,
|
||||
func: Rc::new(move |args| {
|
||||
let mut vm = VM::new(global_values.clone());
|
||||
let res = match vm.run(&exec_node) {
|
||||
let res = match vm.run(&exec_node, 0) { // Root call, use 0 if node is not a lambda
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Myc Runtime Error: {}", e),
|
||||
};
|
||||
@@ -705,7 +708,8 @@ impl Environment {
|
||||
|
||||
let tco_ast = TCO::optimize(optimized_ast);
|
||||
let mut vm = VM::new(global_values.clone());
|
||||
let compiled_val = match vm.run(&tco_ast) {
|
||||
let stack_size = tco_ast.calculate_stack_size();
|
||||
let compiled_val = match vm.run(&tco_ast, stack_size) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(format!("VM Error during specialization: {}", e)),
|
||||
};
|
||||
@@ -742,6 +746,7 @@ impl Environment {
|
||||
|
||||
pub fn run_script_compiled(&self, compiled: TypedNode) -> Result<Value, String> {
|
||||
let linked = self.link(compiled);
|
||||
let _stack_size = linked.calculate_stack_size();
|
||||
let func = self.instantiate(linked);
|
||||
let res = (func.func)(&[]);
|
||||
self.run_pipeline();
|
||||
@@ -752,18 +757,28 @@ impl Environment {
|
||||
self.preload_dependencies(source, None)?;
|
||||
let compiled = self.compile(source).into_result()?;
|
||||
let linked = self.link(compiled);
|
||||
|
||||
// Late Counting: Determine stack size after all optimizations are finished.
|
||||
// This keeps AST nodes clean and ensures the VM always has enough space.
|
||||
let stack_size = linked.calculate_stack_size();
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
let mut observer = TracingObserver::new();
|
||||
let mut result = vm.run_with_observer(&mut observer, &linked);
|
||||
|
||||
// 1. Run the script wrapper (returns a closure representing the script)
|
||||
let result = vm.run_with_observer(&mut observer, &linked, stack_size);
|
||||
|
||||
if let Ok(Value::Object(obj)) = &result
|
||||
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
||||
// 2. Execute the root closure immediately to get the actual script result.
|
||||
// All Myc scripts are wrapped in a parameterless lambda for consistency.
|
||||
let mut final_result = result;
|
||||
if let Ok(Value::Object(obj)) = &final_result
|
||||
&& let Some(_closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
||||
{
|
||||
result = vm.run_with_observer(&mut observer, &closure.exec_node);
|
||||
final_result = vm.run_with_args_observed(&mut observer, obj.clone(), &[]);
|
||||
}
|
||||
|
||||
self.run_pipeline();
|
||||
|
||||
Ok((result, observer.logs))
|
||||
Ok((final_result, observer.logs))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user