From 44b5997a55073c40ca0cb2367bc88ffb4ad6a76b Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Tue, 8 Sep 2026 15:20:30 +0200 Subject: [PATCH] =?UTF-8?q?fix(interpreter):=20optimize=20file=20execution?= =?UTF-8?q?=20=E2=9A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ndc_bin/src/main.rs | 2 +- ndc_interpreter/src/lib.rs | 79 +++++++++++++++++++++++++++++++++++- ndc_vm/src/value/function.rs | 5 +++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/ndc_bin/src/main.rs b/ndc_bin/src/main.rs index f0b3edc1..0a1ac157 100644 --- a/ndc_bin/src/main.rs +++ b/ndc_bin/src/main.rs @@ -164,7 +164,7 @@ fn main() -> anyhow::Result<()> { let string = fs::read_to_string(path)?; - let mut interpreter = Interpreter::new(); + let mut interpreter = Interpreter::one_shot(); interpreter.configure(ndc_stdlib::register); #[cfg(feature = "trace")] diff --git a/ndc_interpreter/src/lib.rs b/ndc_interpreter/src/lib.rs index 6727c2f7..5b735254 100644 --- a/ndc_interpreter/src/lib.rs +++ b/ndc_interpreter/src/lib.rs @@ -28,6 +28,7 @@ pub struct Interpreter { /// `None` until the first `eval` call; kept alive afterwards so that /// variables declared on one line are visible on subsequent lines. repl_state: Option<(Vm, Compiler)>, + resumable: bool, #[cfg(feature = "trace")] tracer: Option>, } @@ -58,11 +59,24 @@ impl Interpreter { ), source_db: SourceDb::new(), repl_state: None, + resumable: true, #[cfg(feature = "trace")] tracer: None, } } + /// Create an interpreter for executing a complete program once. + /// + /// Unlike the default resumable interpreter used by the REPL, this uses + /// the optimizing compiler and does not retain state after execution. + #[must_use] + pub fn one_shot() -> Self { + Self { + resumable: false, + ..Self::from_capturing(false) + } + } + pub fn configure>)>(&mut self, f: F) { f(&mut self.function_registry); let functions = self @@ -190,7 +204,7 @@ impl Interpreter { let analyser_checkpoint = self.analyser.checkpoint(); let (expressions, _, mut timings) = self.parse_and_analyse(input, source_id)?; let vm_result = self.interpret_vm(input, expressions.into_iter()); - if vm_result.is_err() { + if vm_result.is_err() || !self.resumable { self.analyser.restore(analyser_checkpoint); } let (value, vm_timings) = vm_result?; @@ -259,6 +273,28 @@ impl Interpreter { .collect(); let mut timings = ExecutionTimings::default(); + if !self.resumable { + let code = measure(&mut timings, Phase::Compiling, || { + self.compile_one_shot(expressions) + })?; + let num_locals = code.num_locals(); + let output = if self.capturing { + OutputSink::Buffer(Vec::new()) + } else { + OutputSink::Stdout + }; + let mut vm = Vm::new(code, globals).with_output(output); + #[cfg(feature = "trace")] + { + vm = vm.with_source(input); + if let Some(tracer) = self.tracer.take() { + vm = vm.with_tracer(tracer); + } + } + measure(&mut timings, Phase::Running, || vm.run())?; + return Ok((vm.last_value(num_locals), timings)); + } + let result = match self.repl_state.take() { None => { let output = if self.capturing { @@ -311,6 +347,13 @@ impl Interpreter { Ok((result, timings)) } + + fn compile_one_shot( + &self, + expressions: impl Iterator, + ) -> Result { + Compiler::compile(expressions, Rc::clone(&self.struct_registry)) + } } impl Default for Interpreter { @@ -343,3 +386,37 @@ pub enum InterpreterError { #[error("{0}")] Vm(#[from] ndc_vm::VmError), } + +#[cfg(test)] +mod tests { + use super::*; + use ndc_vm::chunk::OpCode; + + #[test] + fn one_shot_compilation_runs_the_optimizer() { + let mut interpreter = Interpreter::one_shot(); + let source_id = interpreter.source_db.add("", "1;"); + let (expressions, _, _) = interpreter + .parse_and_analyse("1;", source_id) + .expect("analysis should succeed"); + + let compiled = interpreter + .compile_one_shot(expressions.into_iter()) + .expect("compilation should succeed"); + + assert_eq!(compiled.opcodes(), [OpCode::Halt]); + } + + #[test] + fn one_shot_execution_does_not_retain_declarations() { + let mut interpreter = Interpreter::one_shot(); + interpreter + .eval("let value = 1;") + .expect("first program should succeed"); + + assert!( + interpreter.eval("value;").is_err(), + "a one-shot interpreter must treat each program as isolated", + ); + } +} diff --git a/ndc_vm/src/value/function.rs b/ndc_vm/src/value/function.rs index e1b3829a..1b7748f4 100644 --- a/ndc_vm/src/value/function.rs +++ b/ndc_vm/src/value/function.rs @@ -62,6 +62,11 @@ pub enum UpvalueCell { } impl CompiledFunction { + #[must_use] + pub fn num_locals(&self) -> usize { + self.num_locals + } + pub fn opcodes(&self) -> &[OpCode] { self.body.opcodes() }