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:
@@ -24,4 +24,4 @@ Der Code enthält einige starke Indikatoren für unscharfe Systemgrenzen ("Leaky
|
|||||||
* ~~**Verwaschene Kompilierungsschritte:**~~ **✓ Behoben (Design korrekt, ein Detail bereinigt).** Die drei Methoden bilden eine saubere Adapter-Hierarchie: `compile_pipeline` (Kern, Diagnostics-Sink) ← `compile` (public API, merged Parse+Compile-Fehler in `CompilationResult`) / `compile_syntax` (privater Adapter für Modul-Loader, konvertiert zu `Result<_, String>`). Das unterschiedliche Error-Reporting ist situations-appropriat, nicht inkonsistent. **Behobener Design-Geruch:** `dump_ast` umging zuvor `compile()` und verschluckte Parse-Fehler still. Behoben: `dump_ast` delegiert jetzt an `compile(source).into_result()?`.
|
* ~~**Verwaschene Kompilierungsschritte:**~~ **✓ Behoben (Design korrekt, ein Detail bereinigt).** Die drei Methoden bilden eine saubere Adapter-Hierarchie: `compile_pipeline` (Kern, Diagnostics-Sink) ← `compile` (public API, merged Parse+Compile-Fehler in `CompilationResult`) / `compile_syntax` (privater Adapter für Modul-Loader, konvertiert zu `Result<_, String>`). Das unterschiedliche Error-Reporting ist situations-appropriat, nicht inkonsistent. **Behobener Design-Geruch:** `dump_ast` umging zuvor `compile()` und verschluckte Parse-Fehler still. Behoben: `dump_ast` delegiert jetzt an `compile(source).into_result()?`.
|
||||||
* ~~**Die `specialize_node`-Closure:**~~ **✓ Kein Problem (Design korrekt verstanden).** Die `compiler`-Closure ist kein Zeichen unsauberer Kapselung — im Gegenteil. `Specializer` ist bewusst von TypeChecker, Optimizer und VM entkoppelt; der `CompileFunc`-Typ (`specializer.rs:15`) ist der explizite **Dependency-Injection-Punkt** (Strategy Pattern). Die innere Pipeline (TypeCheck → Analyze → sub-Specialize → Optimize → Lower → VM.run) **dupliziert nicht** die äußere — ihr Zweck ist fundamental verschieden: sie erzeugt einen gecachten `Value` (compile-and-evaluate), während die äußere Pipeline einen `ExecNode` für die Skriptausführung erzeugt. Die Closure captured 6 `Rc<RefCell<T>>`-Handles statt `&self`, weil Rust keine `self`-Referenz in eine `'static`-Closure erlaubt — das ist der korrekte Rust-Weg. **Design-Limitation (dokumentiert, kein Bug):** Der `sub_specializer` innerhalb der Closure hat `compiler: None`, was Endlos-Rekursion verhindert, aber bedeutet, dass nur eine Spezialisierungsebene pro Aufruf expandiert wird.
|
* ~~**Die `specialize_node`-Closure:**~~ **✓ Kein Problem (Design korrekt verstanden).** Die `compiler`-Closure ist kein Zeichen unsauberer Kapselung — im Gegenteil. `Specializer` ist bewusst von TypeChecker, Optimizer und VM entkoppelt; der `CompileFunc`-Typ (`specializer.rs:15`) ist der explizite **Dependency-Injection-Punkt** (Strategy Pattern). Die innere Pipeline (TypeCheck → Analyze → sub-Specialize → Optimize → Lower → VM.run) **dupliziert nicht** die äußere — ihr Zweck ist fundamental verschieden: sie erzeugt einen gecachten `Value` (compile-and-evaluate), während die äußere Pipeline einen `ExecNode` für die Skriptausführung erzeugt. Die Closure captured 6 `Rc<RefCell<T>>`-Handles statt `&self`, weil Rust keine `self`-Referenz in eine `'static`-Closure erlaubt — das ist der korrekte Rust-Weg. **Design-Limitation (dokumentiert, kein Bug):** Der `sub_specializer` innerhalb der Closure hat `compiler: None`, was Endlos-Rekursion verhindert, aber bedeutet, dass nur eine Spezialisierungsebene pro Aufruf expandiert wird.
|
||||||
* ~~**Type-Checker Hack:**~~ **✓ Kein Problem (Kommentar bereinigt).** Der ursprüngliche Kommentar war veraltet und irreführend. Das `BoundLike`-Trait vereinigt alle Phasen mit identischer Binding-Struktur (`BoundPhase`, `TypedPhase`, `AnalyzedPhase`). `TypeChecker::check_node_as_bound<P: BoundLike>()` ist korrekte Generic-Programmierung — kein Hack. Die Phasen passen sauber ineinander. Der Kommentar wurde durch eine korrekte Erklärung des Monomorphisierungs-Designs ersetzt.
|
* ~~**Type-Checker Hack:**~~ **✓ Kein Problem (Kommentar bereinigt).** Der ursprüngliche Kommentar war veraltet und irreführend. Das `BoundLike`-Trait vereinigt alle Phasen mit identischer Binding-Struktur (`BoundPhase`, `TypedPhase`, `AnalyzedPhase`). `TypeChecker::check_node_as_bound<P: BoundLike>()` ist korrekte Generic-Programmierung — kein Hack. Die Phasen passen sauber ineinander. Der Kommentar wurde durch eine korrekte Erklärung des Monomorphisierungs-Designs ersetzt.
|
||||||
* **Konzeptioneller Bruch bei `instantiate`:** Diese Methode nimmt einen fertig kompilierten Ast (`ExecNode`) und wickelt ihn in eine `NativeFunction` ein. Dadurch wird ein in Myc geschriebenes Skript in der Registry strukturell als "native Rust-Funktion" getarnt. Das ist pragmatisch, verwischt aber die semantische Grenze zwischen User-Code (Closures) und echten Runtime-Intrinsics.
|
* ~~**Konzeptioneller Bruch bei `instantiate`:**~~ **✓ Behoben.** `instantiate` gibt jetzt `Result<Rc<Closure>, String>` zurück statt `Rc<NativeFunction>`. Die VM-Erzeugung und -Ausführung liegt beim Aufrufer (`run_script_compiled`, Benchmark-Runner), der die passende Strategie kennt (einmaliger Run vs. N Iterationen mit VM-Reuse). `NativeFunction` bleibt ausschließlich für echte Rust-Intrinsics (RTL). Bonus: der Benchmark-Runner erstellt jetzt eine VM für N Iterationen statt N VMs — `run_with_args` resettet `stack` und `frames` am Anfang jedes Aufrufs, VM-Reuse ist sicher.
|
||||||
|
|||||||
+32
-40
@@ -633,55 +633,46 @@ impl Environment {
|
|||||||
Lowering::lower(optimized)
|
Lowering::lower(optimized)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn instantiate(&self, node: ExecNode) -> Rc<NativeFunction> {
|
/// Converts a linked `ExecNode` into a callable `Closure`.
|
||||||
let root_values = self.root_values.clone();
|
///
|
||||||
if let NodeKind::Lambda {
|
/// Fast path: if the node is already a top-level lambda without upvalues,
|
||||||
params,
|
/// the `Closure` is constructed directly without running the VM.
|
||||||
body,
|
///
|
||||||
info,
|
/// Slow path: the node is executed once to obtain its resulting `Closure`
|
||||||
} = &node.kind
|
/// 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()
|
&& info.upvalues.is_empty()
|
||||||
{
|
{
|
||||||
let closure = Rc::new(Closure::new(
|
return Ok(Rc::new(Closure::new(
|
||||||
params.clone(),
|
params.clone(),
|
||||||
body.ty.original.clone(),
|
body.ty.original.clone(),
|
||||||
body.clone(),
|
body.clone(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
info.positional_count,
|
info.positional_count,
|
||||||
node.ty.stack_size,
|
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);
|
// Slow path: run the node once to extract the resulting Closure.
|
||||||
Rc::new(NativeFunction {
|
let mut vm = VM::new(self.root_values.clone());
|
||||||
purity: Purity::Impure,
|
let res = vm.run(&node)?;
|
||||||
func: Rc::new(move |args| {
|
if let Value::Closure(obj) = res {
|
||||||
let mut vm = VM::new(root_values.clone());
|
Ok(obj)
|
||||||
let res = match vm.run(&exec_node) {
|
} else {
|
||||||
Ok(v) => v,
|
Err("Script did not produce a callable closure".to_string())
|
||||||
Err(e) => panic!("Myc Runtime Error: {}", e),
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
if let Value::Closure(obj) = &res {
|
/// Creates a new `VM` connected to this environment's global scope.
|
||||||
match vm.run_with_args(obj.clone(), args) {
|
pub fn create_vm(&self) -> VM {
|
||||||
Ok(v) => v,
|
VM::new(self.root_values.clone())
|
||||||
Err(e) => panic!("Myc Runtime Error (Closure): {}", e),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
|
fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
|
||||||
@@ -780,8 +771,9 @@ impl Environment {
|
|||||||
|
|
||||||
pub fn run_script_compiled(&self, compiled: TypedNode) -> Result<Value, String> {
|
pub fn run_script_compiled(&self, compiled: TypedNode) -> Result<Value, String> {
|
||||||
let linked = self.link(compiled);
|
let linked = self.link(compiled);
|
||||||
let func = self.instantiate(linked);
|
let closure = self.instantiate(linked)?;
|
||||||
let res = (func.func)(&[]);
|
let mut vm = self.create_vm();
|
||||||
|
let res = vm.run_with_args(closure, &[])?;
|
||||||
self.run_pipeline();
|
self.run_pipeline();
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -55,7 +55,7 @@ pub fn run_functional_tests_with_optimization(enabled: bool) -> Vec<TestResult>
|
|||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_benchmarks(update: bool, filter: Option<&str>) -> Vec<BenchmarkResult> {
|
pub fn ca(update: bool, filter: Option<&str>) -> Vec<BenchmarkResult> {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
let entries = fs::read_dir("examples").unwrap();
|
let entries = fs::read_dir("examples").unwrap();
|
||||||
let is_release = !cfg!(debug_assertions);
|
let is_release = !cfg!(debug_assertions);
|
||||||
@@ -104,18 +104,19 @@ pub fn run_benchmarks(update: bool, filter: Option<&str>) -> Vec<BenchmarkResult
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to measure sum of VM execution times over N executions in one environment
|
// Helper to measure sum of VM execution times over N executions in one environment.
|
||||||
|
// A single VM is reused across iterations: run_with_args resets stack/frames
|
||||||
|
// on each call, so no re-allocation is needed between runs.
|
||||||
let measure_sum =
|
let measure_sum =
|
||||||
|n: u32, node: &TypedNode| -> Result<Duration, String> {
|
|n: u32, node: &TypedNode| -> Result<Duration, String> {
|
||||||
let env = Environment::new();
|
let env = Environment::new();
|
||||||
|
|
||||||
// Link once per sample
|
|
||||||
let linked = env.link(node.clone());
|
let linked = env.link(node.clone());
|
||||||
let func = env.instantiate(linked);
|
let closure = env.instantiate(linked)?;
|
||||||
|
let mut vm = env.create_vm();
|
||||||
let mut total = Duration::ZERO;
|
let mut total = Duration::ZERO;
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let _ = (func.func)(&[]);
|
let _ = vm.run_with_args(closure.clone(), &[])?;
|
||||||
total += start.elapsed();
|
total += start.elapsed();
|
||||||
}
|
}
|
||||||
Ok(total)
|
Ok(total)
|
||||||
|
|||||||
+2
-2
@@ -18,8 +18,8 @@ fn test_closure_modification_from_source() {
|
|||||||
.into_result()
|
.into_result()
|
||||||
.expect("Failed to compile");
|
.expect("Failed to compile");
|
||||||
let linked = env.link(compiled);
|
let linked = env.link(compiled);
|
||||||
let func = env.instantiate(linked);
|
let closure = env.instantiate(linked).expect("Failed to instantiate");
|
||||||
let result: Result<Value, String> = Ok((func.func)(&[]));
|
let result = env.create_vm().run_with_args(closure, &[]);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(Value::Int(20)) => (),
|
Ok(Value::Int(20)) => (),
|
||||||
|
|||||||
Reference in New Issue
Block a user