Refactor instantiate to return Closure

The `instantiate` method in `Environment` has been refactored to return
`Result<Rc<Closure>, String>` instead of `Rc<NativeFunction>`. This
change separates the creation of a `Closure` from its execution,
allowing the caller to manage the `VM` lifecycle and choose the
appropriate execution strategy (e.g., single run vs. repeated benchmark
iterations).

The `NativeFunction` type is now exclusively used for true Rust
intrinsics. The benchmark runner has also been updated to reuse a single
`VM` across iterations, improving performance by avoiding repeated VM
allocations.
This commit is contained in:
2026-03-27 22:04:20 +01:00
parent 3d0ea094b0
commit b6dcdbde8d
4 changed files with 42 additions and 49 deletions
+32 -40
View File
@@ -633,55 +633,46 @@ impl Environment {
Lowering::lower(optimized)
}
pub fn instantiate(&self, node: ExecNode) -> Rc<NativeFunction> {
let root_values = self.root_values.clone();
if let NodeKind::Lambda {
params,
body,
info,
} = &node.kind
/// Converts a linked `ExecNode` into a callable `Closure`.
///
/// Fast path: if the node is already a top-level lambda without upvalues,
/// the `Closure` is constructed directly without running the VM.
///
/// Slow path: the node is executed once to obtain its resulting `Closure`
/// value (e.g. a `do`-block that returns a lambda).
///
/// The caller is responsible for creating a `VM` and invoking the closure
/// via `vm.run_with_args`. This keeps VM lifecycle and error handling at
/// the call site, where the required strategy (single run vs. repeated
/// benchmark iterations) is known.
pub fn instantiate(&self, node: ExecNode) -> Result<Rc<Closure>, String> {
// Fast path: top-level lambda without upvalues — build Closure directly.
if let NodeKind::Lambda { params, body, info } = &node.kind
&& info.upvalues.is_empty()
{
let closure = Rc::new(Closure::new(
return Ok(Rc::new(Closure::new(
params.clone(),
body.ty.original.clone(),
body.clone(),
Vec::new(),
info.positional_count,
node.ty.stack_size,
));
return Rc::new(NativeFunction {
purity: Purity::Impure,
func: Rc::new(move |args| {
let mut vm = VM::new(root_values.clone());
match vm.run_with_args(closure.clone(), args) {
Ok(v) => v,
Err(e) => panic!("Myc Runtime Error: {}", e),
}
}),
});
)));
}
let exec_node = Rc::new(node);
Rc::new(NativeFunction {
purity: Purity::Impure,
func: Rc::new(move |args| {
let mut vm = VM::new(root_values.clone());
let res = match vm.run(&exec_node) {
Ok(v) => v,
Err(e) => panic!("Myc Runtime Error: {}", e),
};
// Slow path: run the node once to extract the resulting Closure.
let mut vm = VM::new(self.root_values.clone());
let res = vm.run(&node)?;
if let Value::Closure(obj) = res {
Ok(obj)
} else {
Err("Script did not produce a callable closure".to_string())
}
}
if let Value::Closure(obj) = &res {
match vm.run_with_args(obj.clone(), args) {
Ok(v) => v,
Err(e) => panic!("Myc Runtime Error (Closure): {}", e),
}
} else {
res
}
}),
})
/// Creates a new `VM` connected to this environment's global scope.
pub fn create_vm(&self) -> VM {
VM::new(self.root_values.clone())
}
fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
@@ -780,8 +771,9 @@ impl Environment {
pub fn run_script_compiled(&self, compiled: TypedNode) -> Result<Value, String> {
let linked = self.link(compiled);
let func = self.instantiate(linked);
let res = (func.func)(&[]);
let closure = self.instantiate(linked)?;
let mut vm = self.create_vm();
let res = vm.run_with_args(closure, &[])?;
self.run_pipeline();
Ok(res)
}