Repository: microsoft/qsharp
Pull request #452 (2023-07-12 18:13:02)

Tracking execution context for stateless interpreter


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 270b9bba..976d6481 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -970,9 +970,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.23"
+version = "1.0.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
 dependencies = [
  "proc-macro2",
 ]
diff --git a/compiler/qsc/benches/eval.rs b/compiler/qsc/benches/eval.rs
index 492c6596..2e2ac729 100644
--- a/compiler/qsc/benches/eval.rs
+++ b/compiler/qsc/benches/eval.rs
@@ -13,11 +13,12 @@ const LARGE: &str = include_str!("./large.qs");
 pub fn teleport(c: &mut Criterion) {
     c.bench_function("Teleport evaluation", |b| {
         let sources = SourceMap::new([("Teleportation.qs".into(), TELEPORT.into())], None);
-        let evaluator = stateless::Context::new(true, sources).expect("code should compile");
+        let evaluator = stateless::Interpreter::new(true, sources).expect("code should compile");
         b.iter(move || {
             let mut out = Vec::new();
             let mut rec = GenericReceiver::new(&mut out);
-            assert!(evaluator.eval(&mut rec).is_ok());
+            let mut eval_ctx = evaluator.new_eval_context();
+            assert!(eval_ctx.eval_entry(&mut rec).is_ok());
         })
     });
 }
@@ -25,11 +26,12 @@ pub fn teleport(c: &mut Criterion) {
 pub fn deutsch_jozsa(c: &mut Criterion) {
     c.bench_function("Deutsch-Jozsa evaluation", |b| {
         let sources = SourceMap::new([("DeutschJozsa.qs".into(), DEUTSCHJOZSA.into())], None);
-        let evaluator = stateless::Context::new(true, sources).expect("code should compile");
+        let evaluator = stateless::Interpreter::new(true, sources).expect("code should compile");
         b.iter(move || {
             let mut out = Vec::new();
             let mut rec = GenericReceiver::new(&mut out);
-            assert!(evaluator.eval(&mut rec).is_ok());
+            let mut eval_ctx = evaluator.new_eval_context();
+            assert!(eval_ctx.eval_entry(&mut rec).is_ok());
         })
     });
 }
@@ -37,11 +39,12 @@ pub fn deutsch_jozsa(c: &mut Criterion) {
 pub fn large_file(c: &mut Criterion) {
     c.bench_function("Large file parity evaluation", |b| {
         let sources = SourceMap::new([("large.qs".into(), LARGE.into())], None);
-        let evaluator = stateless::Context::new(true, sources).expect("code should compile");
+        let evaluator = stateless::Interpreter::new(true, sources).expect("code should compile");
         b.iter(move || {
             let mut out = Vec::new();
             let mut rec = GenericReceiver::new(&mut out);
-            assert!(evaluator.eval(&mut rec).is_ok());
+            let mut eval_ctx = evaluator.new_eval_context();
+            assert!(eval_ctx.eval_entry(&mut rec).is_ok());
         })
     });
 }
diff --git a/compiler/qsc/src/bin/qsi.rs b/compiler/qsc/src/bin/qsi.rs
index bebb243b..e3254096 100644
--- a/compiler/qsc/src/bin/qsi.rs
+++ b/compiler/qsc/src/bin/qsi.rs
@@ -78,11 +78,11 @@ fn main() -> miette::Result<ExitCode> {
         .collect::<miette::Result<Vec<_>>>()?;
 
     if cli.exec {
-        let context = match stateless::Context::new(
+        let interpreter = match stateless::Interpreter::new(
             !cli.nostdlib,
             SourceMap::new(sources, cli.entry.map(std::convert::Into::into)),
         ) {
-            Ok(context) => context,
+            Ok(interpreter) => interpreter,
             Err(errors) => {
                 for error in errors {
                     eprintln!("error: {:?}", Report::new(error));
@@ -90,7 +90,10 @@ fn main() -> miette::Result<ExitCode> {
                 return Ok(ExitCode::FAILURE);
             }
         };
-        return Ok(print_exec_result(context.eval(&mut TerminalReceiver)));
+        let mut eval_ctx = interpreter.new_eval_context();
+        return Ok(print_exec_result(
+            eval_ctx.eval_entry(&mut TerminalReceiver),
+        ));
     }
 
     let mut interpreter = match Interpreter::new(!cli.nostdlib, SourceMap::new(sources, None)) {
@@ -115,7 +118,7 @@ fn main() -> miette::Result<ExitCode> {
     Ok(ExitCode::SUCCESS)
 }
 
-fn repl(interpreter: &mut Interpreter, receiver: &mut dyn Receiver) -> io::Result<()> {
+fn repl(interpreter: &mut Interpreter, receiver: &mut impl Receiver) -> io::Result<()> {
     print_prompt(false);
 
     let mut lines = io::BufReader::new(io::stdin()).lines();
diff --git a/compiler/qsc/src/interpret/debug.rs b/compiler/qsc/src/interpret/debug.rs
index 6de65982..84af9bd9 100644
--- a/compiler/qsc/src/interpret/debug.rs
+++ b/compiler/qsc/src/interpret/debug.rs
@@ -12,7 +12,7 @@ use qsc_hir::hir::{Item, ItemKind};
 #[must_use]
 pub(crate) fn format_call_stack<'a>(
     store: &PackageStore,
-    globals: &'a impl GlobalLookup<'a>,
+    globals: &impl GlobalLookup<'a>,
     call_stack: &CallStack,
     error: &dyn std::error::Error,
 ) -> String {
diff --git a/compiler/qsc/src/interpret/stateful.rs b/compiler/qsc/src/interpret/stateful.rs
index f441cb62..847d0f62 100644
--- a/compiler/qsc/src/interpret/stateful.rs
+++ b/compiler/qsc/src/interpret/stateful.rs
@@ -13,9 +13,10 @@ use qsc_data_structures::index_map::IndexMap;
 use qsc_eval::{
     backend::SparseSim,
     debug::CallStack,
+    eval_stmt,
     output::Receiver,
     val::{GlobalId, Value},
-    Env, Global,
+    Env, Global, GlobalLookup,
 };
 use qsc_frontend::{
     compile::{CompileUnit, PackageStore, Source, SourceMap},
@@ -61,6 +62,19 @@ pub enum LineErrorKind {
     Eval(#[from] qsc_eval::Error),
 }
 
+struct Lookup<'a> {
+    store: &'a PackageStore,
+    package: PackageId,
+    udts: &'a HashSet<LocalItemId>,
+    callables: &'a IndexMap<LocalItemId, CallableDecl>,
+}
+
+impl<'a> GlobalLookup<'a> for Lookup<'a> {
+    fn get(&self, id: GlobalId) -> Option<Global<'a>> {
+        get_global(self.store, self.udts, self.callables, self.package, id)
+    }
+}
+
 pub struct Interpreter {
     store: PackageStore,
     package: PackageId,
@@ -110,7 +124,7 @@ impl Interpreter {
     /// If there is a runtime error when interpreting the line, an error is returned.
     pub fn interpret_line(
         &mut self,
-        receiver: &mut dyn Receiver,
+        receiver: &mut impl Receiver,
         line: &str,
     ) -> Result<Value, Vec<LineError>> {
         let mut result = Value::unit();
@@ -171,26 +185,34 @@ impl Interpreter {
 
     fn eval_stmt(
         &mut self,
-        receiver: &mut dyn Receiver,
+        receiver: &mut impl Receiver,
         stmt: &Stmt,
     ) -> Result<Value, (qsc_eval::Error, CallStack)> {
-        qsc_eval::eval_stmt(
+        let globals = Lookup {
+            store: &self.store,
+            package: self.package,
+            udts: &self.udts,
+            callables: &self.callables,
+        };
+
+        eval_stmt(
             stmt,
-            &|id| get_global(&self.store, &self.udts, &self.callables, self.package, id),
-            self.package,
+            &globals,
             &mut self.env,
             &mut self.sim,
+            self.package,
             receiver,
         )
     }
 
     fn render_call_stack(&self, call_stack: &CallStack, error: &dyn std::error::Error) -> String {
-        format_call_stack(
-            &self.store,
-            &|id| get_global(&self.store, &self.udts, &self.callables, self.package, id),
-            call_stack,
-            error,
-        )
+        let globals = Lookup {
+            store: &self.store,
+            package: self.package,
+            udts: &self.udts,
+            callables: &self.callables,
+        };
+        format_call_stack(&self.store, &globals, call_stack, error)
     }
 }
 
diff --git a/compiler/qsc/src/interpret/stateless.rs b/compiler/qsc/src/interpret/stateless.rs
index 5e1cad3f..ecbee3e2 100644
--- a/compiler/qsc/src/interpret/stateless.rs
+++ b/compiler/qsc/src/interpret/stateless.rs
@@ -12,11 +12,10 @@ use qsc_eval::{
     eval_expr,
     output::Receiver,
     val::{GlobalId, Value},
-    Env, Global,
+    Env, Global, GlobalLookup, State,
 };
 use qsc_frontend::compile::{PackageStore, Source, SourceMap};
 use qsc_hir::hir::{Expr, ItemKind, PackageId};
-use qsc_passes::entry_point::extract_entry;
 use thiserror::Error;
 
 use super::debug::format_call_stack;
@@ -34,22 +33,45 @@ impl Error {
 }
 
 #[derive(Clone, Debug, Diagnostic, Error)]
-#[diagnostic(transparent)]
 enum ErrorKind {
     #[error(transparent)]
+    #[diagnostic(transparent)]
     Compile(#[from] compile::Error),
     #[error(transparent)]
+    #[diagnostic(transparent)]
     Pass(#[from] qsc_passes::Error),
     #[error("runtime error")]
+    #[diagnostic(transparent)]
     Eval(#[from] qsc_eval::Error),
+    #[error("entry point not found")]
+    #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
+    NoEntryPoint,
 }
 
-pub struct Context {
+pub struct Interpreter {
     store: PackageStore,
     package: PackageId,
 }
 
-impl Context {
+pub struct EvalContext<'a> {
+    interpreter: &'a Interpreter,
+    env: Env,
+    sim: SparseSim,
+    lookup: Lookup<'a>,
+    state: State<'a>,
+}
+
+struct Lookup<'a> {
+    store: &'a PackageStore,
+}
+
+impl<'a> GlobalLookup<'a> for Lookup<'a> {
+    fn get(&self, id: GlobalId) -> Option<Global<'a>> {
+        get_global(self.store, id)
+    }
+}
+
+impl Interpreter {
     /// # Errors
     ///
     /// Returns a vector of errors if compiling the given sources fails.
@@ -72,28 +94,50 @@ impl Context {
         }
     }
 
+    #[must_use]
+    pub fn new_eval_context(&self) -> EvalContext {
+        EvalContext {
+            interpreter: self,
+            env: Env::with_empty_scope(),
+            sim: SparseSim::new(),
+            lookup: Lookup { store: &self.store },
+            state: State::new(self.package),
+        }
+    }
+}
+
+impl<'a> EvalContext<'a> {
     /// # Errors
     ///
     /// Returns a vector of errors if evaluating the entry point fails.
-    pub fn eval(&self, receiver: &mut dyn Receiver) -> Result<Value, Vec<Error>> {
+    pub fn eval_entry(&mut self, receiver: &mut impl Receiver) -> Result<Value, Vec<Error>> {
+        let expr = get_entry_expr(&self.interpreter.store, self.interpreter.package)?;
         eval_expr(
-            &get_entry_expr(&self.store, self.package)?,
-            &|id| get_global(&self.store, id),
-            self.package,
-            &mut Env::with_empty_scope(),
-            &mut SparseSim::new(),
+            &mut self.state,
+            expr,
+            &self.lookup,
+            &mut self.env,
+            &mut self.sim,
             receiver,
         )
         .map_err(|(error, call_stack)| {
             let package = self
+                .interpreter
                 .store
-                .get(self.package)
+                .get(self.interpreter.package)
                 .expect("package should be in store");
 
             let stack_trace = if call_stack.is_empty() {
                 None
             } else {
-                Some(render_call_stack(&self.store, &call_stack, &error))
+                Some(render_call_stack(
+                    &self.interpreter.store,
+                    &Lookup {
+                        store: &self.interpreter.store,
+                    },
+                    &call_stack,
+                    &error,
+                ))
             };
 
             vec![Error(WithSource::from_map(
@@ -105,25 +149,25 @@ impl Context {
     }
 }
 
-fn render_call_stack(
+fn render_call_stack<'a>(
     store: &PackageStore,
+    globals: &impl GlobalLookup<'a>,
     call_stack: &CallStack,
     error: &dyn std::error::Error,
 ) -> String {
-    format_call_stack(store, &|id| get_global(store, id), call_stack, error)
+    format_call_stack(store, globals, call_stack, error)
 }
 
-fn get_entry_expr(store: &PackageStore, package: PackageId) -> Result<Expr, Vec<Error>> {
+fn get_entry_expr(store: &PackageStore, package: PackageId) -> Result<&Expr, Vec<Error>> {
     let unit = store.get(package).expect("store should have package");
-    match &unit.package.entry {
-        Some(entry) => Ok(entry.clone()),
-        None => extract_entry(&unit.package).map_err(|errors| {
-            errors
-                .into_iter()
-                .map(|error| Error(WithSource::from_map(&unit.sources, error.into(), None)))
-                .collect()
-        }),
-    }
+    if let Some(entry) = unit.package.entry.as_ref() {
+        return Ok(entry);
+    };
+    Err(vec![Error(WithSource::from_map(
+        &unit.sources,
+        ErrorKind::NoEntryPoint,
+        None,
+    ))])
 }
 
 pub(super) fn get_global(store: &PackageStore, id: GlobalId) -> Option<Global> {
diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs
index cd5563b9..db471b63 100644
--- a/compiler/qsc_eval/src/lib.rs
+++ b/compiler/qsc_eval/src/lib.rs
@@ -125,36 +125,36 @@ impl Display for Spec {
     }
 }
 
-/// Evaluates the given statement with the given context.
+/// Evaluates the given stmt with the given context.
 /// # Errors
 /// Returns the first error encountered during execution.
 pub fn eval_stmt<'a>(
     stmt: &'a Stmt,
-    globals: &'a impl GlobalLookup<'a>,
+    globals: &impl GlobalLookup<'a>,
+    env: &mut Env,
+    sim: &mut impl Backend,
     package: PackageId,
-    env: &'a mut Env,
-    sim: &'a mut dyn Backend,
-    out: &'a mut dyn Receiver,
+    receiver: &mut impl Receiver,
 ) -> Result<Value, (Error, CallStack)> {
-    let mut state = State::new(globals, package, env, sim, out);
+    let mut state = State::new(package);
     state.push_stmt(stmt);
-    state.eval()
+    state.eval(globals, env, sim, receiver)
 }
 
-/// Evaluates the given expression with the given context.
+/// Evaluates the given expr with the given context.
 /// # Errors
 /// Returns the first error encountered during execution.
-pub fn eval_expr<'a>(
+pub fn eval_expr<'a, 'receiver>(
+    state: &mut State<'a>,
     expr: &'a Expr,
-    globals: &'a impl GlobalLookup<'a>,
-    package: PackageId,
-    env: &'a mut Env,
-    sim: &'a mut dyn Backend,
-    out: &'a mut dyn Receiver,
+    globals: &impl GlobalLookup<'a>,
+    env: &mut Env,
+    sim: &mut impl Backend,
+
+    out: &'receiver mut impl Receiver,
 ) -> Result<Value, (Error, CallStack)> {
-    let mut state = State::new(globals, package, env, sim, out);
     state.push_expr(expr);
-    state.eval()
+    state.eval(globals, env, sim, out)
 }
 
 trait AsIndex {
@@ -225,16 +225,11 @@ pub trait GlobalLookup<'a> {
     fn get(&self, id: GlobalId) -> Option<Global<'a>>;
 }
 
-impl<'a, F: Fn(GlobalId) -> Option<Global<'a>>> GlobalLookup<'a> for F {
-    fn get(&self, id: GlobalId) -> Option<Global<'a>> {
-        self(id)
-    }
-}
-
 #[derive(Default)]
 pub struct Env(Vec<Scope>);
 
 impl Env {
+    #[must_use]
     fn get(&self, id: NodeId) -> Option<&Variable> {
         self.0
             .iter()
@@ -248,6 +243,16 @@ impl Env {
             .rev()
             .find_map(|scope| scope.bindings.get_mut(&id))
     }
+
+    fn push_scope(&mut self) {
+        self.0.push(Scope::default());
+    }
+
+    fn leave_scope(&mut self) {
+        self.0
+            .pop()
+            .expect("scope should be entered first before leaving");
+    }
 }
 
 #[derive(Default)]
@@ -294,33 +299,20 @@ enum Action<'a> {
     While(&'a Expr, &'a Block),
 }
 
-pub(crate) struct State<'a, G> {
+pub struct State<'a> {
     stack: Vec<Cont<'a>>,
     vals: Vec<Value>,
     package: PackageId,
-    globals: &'a G,
-    env: &'a mut Env,
-    sim: &'a mut dyn Backend,
-    out: &'a mut dyn Receiver,
     call_stack: CallStack,
 }
 
-impl<'a, G: GlobalLookup<'a>> State<'a, G> {
-    fn new(
-        globals: &'a G,
-        package: PackageId,
-        env: &'a mut Env,
-        sim: &'a mut dyn Backend,
-        out: &'a mut dyn Receiver,
-    ) -> Self {
+impl<'a> State<'a> {
+    #[must_use]
+    pub fn new(package: PackageId) -> Self {
         Self {
             stack: Vec::new(),
             vals: Vec::new(),
             package,
-            globals,
-            env,
-            sim,
-            out,
             call_stack: CallStack::default(),
         }
     }
@@ -359,24 +351,17 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         self.push_val(frame_val);
     }
 
-    fn push_scope(&mut self) {
-        self.env.0.push(Scope::default());
+    fn push_scope(&mut self, env: &mut Env) {
+        env.push_scope();
         self.stack.push(Cont::Scope);
     }
 
-    fn leave_scope(&mut self) {
-        self.env
-            .0
-            .pop()
-            .expect("scope should be entered first before leaving");
-    }
-
     fn push_stmt(&mut self, stmt: &'a Stmt) {
         self.stack.push(Cont::Stmt(stmt));
     }
 
-    fn push_block(&mut self, block: &'a Block) {
-        self.push_scope();
+    fn push_block(&mut self, env: &mut Env, block: &'a Block) {
+        self.push_scope(env);
         for stmt in block.stmts.iter().rev() {
             self.push_stmt(stmt);
             self.push_action(Action::Consume);
@@ -400,17 +385,25 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         self.vals.push(val);
     }
 
-    pub(crate) fn eval(&mut self) -> Result<Value, (Error, CallStack)> {
+    /// # Errors
+    /// Returns the first error encountered during execution.
+    pub fn eval(
+        &mut self,
+        globals: &impl GlobalLookup<'a>,
+        env: &mut Env,
+        sim: &mut impl Backend,
+        out: &mut impl Receiver,
+    ) -> Result<Value, (Error, CallStack)> {
         while let Some(cont) = self.pop_cont() {
             let res = match cont {
-                Cont::Action(action) => self.cont_action(action),
-                Cont::Expr(expr) => self.cont_expr(expr),
+                Cont::Action(action) => self.cont_action(env, sim, globals, action, out),
+                Cont::Expr(expr) => self.cont_expr(env, expr),
                 Cont::Frame(len) => {
                     self.leave_frame(len);
                     Ok(())
                 }
                 Cont::Scope => {
-                    self.leave_scope();
+                    env.leave_scope();
                     Ok(())
                 }
                 Cont::Stmt(stmt) => {
@@ -426,7 +419,8 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         Ok(self.pop_val())
     }
 
-    fn cont_expr(&mut self, expr: &'a Expr) -> Result<(), Error> {
+    #[allow(clippy::similar_names)]
+    fn cont_expr(&mut self, env: &mut Env, expr: &'a Expr) -> Result<(), Error> {
         match &expr.kind {
             ExprKind::Array(arr) => self.cont_arr(arr),
             ExprKind::ArrayRepeat(item, size) => self.cont_arr_repeat(item, size),
@@ -437,10 +431,10 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
             }
             ExprKind::AssignIndex(lhs, mid, rhs) => self.cont_assign_index(lhs, mid, rhs),
             ExprKind::BinOp(op, lhs, rhs) => self.cont_binop(*op, rhs, lhs),
-            ExprKind::Block(block) => self.push_block(block),
+            ExprKind::Block(block) => self.push_block(env, block),
             ExprKind::Call(callee_expr, args_expr) => self.cont_call(callee_expr, args_expr),
             ExprKind::Closure(args, callable) => {
-                let closure = resolve_closure(self.package, self.env, expr.span, args, *callable)?;
+                let closure = resolve_closure(env, self.package, expr.span, args, *callable)?;
                 self.push_val(closure);
             }
             ExprKind::Conjugate(..) => panic!("conjugate should be eliminated by passes"),
@@ -465,7 +459,7 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
                 self.cont_update_field(record, field, replace);
             }
             ExprKind::Var(res, _) => {
-                self.push_val(resolve_binding(self.env, self.package, *res, expr.span)?);
+                self.push_val(resolve_binding(env, self.package, *res, expr.span)?);
             }
             ExprKind::While(cond_expr, block) => self.cont_while(cond_expr, block),
         }
@@ -657,14 +651,23 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         }
     }
 
-    fn cont_action(&mut self, action: Action<'a>) -> Result<(), Error> {
+    fn cont_action(
+        &mut self,
+        env: &mut Env,
+        sim: &mut impl Backend,
+        globals: &impl GlobalLookup<'a>,
+        action: Action<'a>,
+        out: &mut impl Receiver,
+    ) -> Result<(), Error> {
         match action {
             Action::Array(len) => self.eval_arr(len),
             Action::ArrayRepeat(span) => self.eval_arr_repeat(span)?,
-            Action::Assign(lhs) => self.eval_assign(lhs)?,
+            Action::Assign(lhs) => self.eval_assign(env, lhs)?,
             Action::BinOp(op, span, rhs) => self.eval_binop(op, span, rhs)?,
-            Action::Bind(pat, mutability) => self.eval_bind(pat, mutability),
-            Action::Call(callee_span, args_span) => self.eval_call(callee_span, args_span)?,
+            Action::Bind(pat, mutability) => self.eval_bind(env, pat, mutability),
+            Action::Call(callee_span, args_span) => {
+                self.eval_call(env, sim, globals, callee_span, args_span, out)?;
+            }
             Action::Consume => {
                 self.pop_val();
             }
@@ -680,14 +683,14 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
             Action::Range(has_start, has_step, has_end) => {
                 self.eval_range(has_start, has_step, has_end);
             }
-            Action::Return => self.eval_ret(),
+            Action::Return => self.eval_ret(env),
             Action::StringConcat(len) => self.eval_string_concat(len),
             Action::StringLit(str) => self.push_val(Value::String(Rc::clone(str))),
             Action::UpdateIndex(span) => self.eval_update_index(span)?,
             Action::Tuple(len) => self.eval_tup(len),
             Action::UnOp(op) => self.eval_unop(op),
             Action::UpdateField(field) => self.eval_update_field(field),
-            Action::While(cond_expr, block) => self.eval_while(cond_expr, block),
+            Action::While(cond_expr, block) => self.eval_while(env, cond_expr, block),
         }
         Ok(())
     }
@@ -708,14 +711,14 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         Ok(())
     }
 
-    fn eval_assign(&mut self, lhs: &'a Expr) -> Result<(), Error> {
+    fn eval_assign(&mut self, env: &mut Env, lhs: &'a Expr) -> Result<(), Error> {
         let rhs = self.pop_val();
-        update_binding(self.env, lhs, rhs)
+        update_binding(env, lhs, rhs)
     }
 
-    fn eval_bind(&mut self, pat: &'a Pat, mutability: Mutability) {
+    fn eval_bind(&mut self, env: &mut Env, pat: &'a Pat, mutability: Mutability) {
         let val = self.pop_val();
-        bind_value(self.env, pat, val, mutability);
+        bind_value(env, pat, val, mutability);
     }
 
     fn eval_binop(&mut self, op: BinOp, span: Span, rhs: Option<&'a Expr>) -> Result<(), Error> {
@@ -780,7 +783,15 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         Ok(())
     }
 
-    fn eval_call(&mut self, callee_span: Span, arg_span: Span) -> Result<(), Error> {
+    fn eval_call(
+        &mut self,
+        env: &mut Env,
+        sim: &mut impl Backend,
+        globals: &impl GlobalLookup<'a>,
+        callee_span: Span,
+        arg_span: Span,
+        out: &mut impl Receiver,
+    ) -> Result<(), Error> {
         let arg = self.pop_val();
         let (callee_id, functor, fixed_args) = match self.pop_val() {
             Value::Closure(fixed_args, id, functor) => (id, functor, Some(fixed_args)),
@@ -794,7 +805,7 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
             arg
         };
 
-        let callee = match self.globals.get(callee_id) {
+        let callee = match globals.get(callee_id) {
             Some(Global::Callable(callable)) => callable,
             Some(Global::Udt) => {
                 self.push_val(arg);
@@ -805,7 +816,7 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
 
         let spec = spec_from_functor_app(functor);
         self.push_frame(Some(callee_span), callee_id, functor);
-        self.push_scope();
+        self.push_scope(env);
         let block_body = &match spec {
             Spec::Body => Some(&callee.body),
             Spec::Adj => callee.adj.as_ref(),
@@ -816,13 +827,13 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         .body;
         match block_body {
             SpecBody::Impl(input, body_block) => {
-                bind_args_for_spec(self.env, &callee.input, input, arg, functor.controlled);
-                self.push_block(body_block);
+                bind_args_for_spec(env, &callee.input, input, arg, functor.controlled);
+                self.push_block(env, body_block);
                 Ok(())
             }
             SpecBody::Gen(SpecGen::Intrinsic) => {
                 let name = &callee.name.name;
-                let val = intrinsic::call(name, callee_span, arg, arg_span, self.sim, self.out)?;
+                let val = intrinsic::call(name, callee_span, arg, arg_span, sim, out)?;
                 self.push_val(val);
                 Ok(())
             }
@@ -886,14 +897,14 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         self.push_val(Value::Range(start, step, end));
     }
 
-    fn eval_ret(&mut self) {
+    fn eval_ret(&mut self, env: &mut Env) {
         while let Some(cont) = self.pop_cont() {
             match cont {
                 Cont::Frame(len) => {
                     self.leave_frame(len);
                     break;
                 }
-                Cont::Scope => self.leave_scope(),
+                Cont::Scope => env.leave_scope(),
                 _ => {}
             }
         }
@@ -986,12 +997,12 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> {
         self.push_val(update);
     }
 
-    fn eval_while(&mut self, cond_expr: &'a Expr, block: &'a Block) {
+    fn eval_while(&mut self, env: &mut Env, cond_expr: &'a Expr, block: &'a Block) {
         if self.pop_val().unwrap_bool() {
             self.cont_while(cond_expr, block);
             self.push_action(Action::Consume);
             self.push_val(Value::unit());
-            self.push_block(block);
+            self.push_block(env, block);
         } else {
             self.push_val(Value::unit());
         }
@@ -1101,8 +1112,8 @@ fn spec_from_functor_app(functor: FunctorApp) -> Spec {
 }
 
 fn resolve_closure(
-    package: PackageId,
     env: &Env,
+    package: PackageId,
     span: Span,
     args: &[NodeId],
     callable: LocalItemId,
diff --git a/compiler/qsc_passes/src/entry_point.rs b/compiler/qsc_passes/src/entry_point.rs
index b787aaee..48826f2e 100644
--- a/compiler/qsc_passes/src/entry_point.rs
+++ b/compiler/qsc_passes/src/entry_point.rs
@@ -7,8 +7,13 @@ mod tests;
 use super::Error as PassErr;
 use miette::Diagnostic;
 use qsc_data_structures::span::Span;
+use qsc_frontend::compile::CompileUnit;
 use qsc_hir::{
-    hir::{Attr, CallableDecl, Expr, ExprKind, Item, ItemKind, NodeId, Package, PatKind},
+    hir::{
+        Attr, CallableDecl, Expr, ExprKind, Item, ItemId, ItemKind, LocalItemId, NodeId, Package,
+        PatKind, Res,
+    },
+    ty::Ty,
     visit::Visitor,
 };
 use thiserror::Error;
@@ -34,18 +39,32 @@ pub enum Error {
     NotFound,
 }
 
-/// Extracts a single entry point callable declaration, if found.
-/// # Errors
-/// Returns an error if a single entry point with no parameters cannot be found.
-pub fn extract_entry(package: &Package) -> Result<Expr, Vec<super::Error>> {
-    let mut finder = EntryPointFinder {
-        callables: Vec::new(),
-    };
-    finder.visit_package(package);
-    let entry_points = finder.callables;
+// If no entry expression is provided, generate one from the entry point callable.
+// Only one callable should be annotated with the entry point attribute.
+// If more than one callable is annotated, or none are annotated, we skip this pass.
+pub(super) fn generate_entry_expr(unit: &mut CompileUnit) -> Vec<super::Error> {
+    if unit.package.entry.is_some() {
+        return vec![];
+    }
+    let callables = get_callables(&unit.package);
+    if callables.len() != 1 {
+        return vec![];
+    }
 
-    if entry_points.len() == 1 {
-        let ep = entry_points[0];
+    match create_entry_from_callables(callables) {
+        Ok(expr) => {
+            unit.package.entry = Some(expr);
+            vec![]
+        }
+        Err(errs) => errs,
+    }
+}
+
+fn create_entry_from_callables(
+    callables: Vec<(&CallableDecl, LocalItemId)>,
+) -> Result<Expr, Vec<super::Error>> {
+    if callables.len() == 1 {
+        let ep = callables[0].0;
         let arg_count = if let PatKind::Tuple(args) = &ep.input.kind {
             args.len()
         } else {
@@ -59,36 +78,66 @@ pub fn extract_entry(package: &Package) -> Result<Expr, Vec<super::Error>> {
                     qsc_hir::hir::SpecBody::Gen(_) => {
                         Err(vec![PassErr::EntryPoint(Error::BodyMissing(ep.span))])
                     }
-                    qsc_hir::hir::SpecBody::Impl(_, block) => Ok(Expr {
-                        id: NodeId::default(),
-                        span: Span::default(),
-                        ty: ep.output.clone(),
-                        kind: ExprKind::Block(block.clone()),
-                    }),
+                    qsc_hir::hir::SpecBody::Impl(_, block) => {
+                        let arg = Expr {
+                            id: NodeId::default(),
+                            span: ep.span,
+                            ty: Ty::UNIT,
+                            kind: ExprKind::Tuple(Vec::new()),
+                        };
+                        let item = callables[0].1;
+                        let item_id = ItemId {
+                            package: None,
+                            item,
+                        };
+                        let callee = Expr {
+                            id: NodeId::default(),
+                            span: ep.span,
+                            ty: block.ty.clone(),
+                            kind: ExprKind::Var(Res::Item(item_id), Vec::new()),
+                        };
+                        let call = Expr {
+                            id: NodeId::default(),
+                            span: ep.span,
+                            ty: block.ty.clone(),
+                            kind: ExprKind::Call(Box::new(callee), Box::new(arg)),
+                        };
+                        Ok(call)
+                    }
                 }
             }
         } else {
             Err(vec![PassErr::EntryPoint(Error::Args(ep.input.span))])
         }
-    } else if entry_points.is_empty() {
+    } else if callables.is_empty() {
         Err(vec![PassErr::EntryPoint(Error::NotFound)])
     } else {
-        Err(entry_points
+        Err(callables
             .into_iter()
-            .map(|ep| PassErr::EntryPoint(Error::Duplicate(ep.name.name.to_string(), ep.name.span)))
+            .map(|ep| {
+                PassErr::EntryPoint(Error::Duplicate(ep.0.name.name.to_string(), ep.0.name.span))
+            })
             .collect())
     }
 }
 
+fn get_callables(package: &Package) -> Vec<(&CallableDecl, LocalItemId)> {
+    let mut finder = EntryPointFinder {
+        callables: Vec::new(),
+    };
+    finder.visit_package(package);
+    finder.callables
+}
+
 struct EntryPointFinder<'a> {
-    callables: Vec<&'a CallableDecl>,
+    callables: Vec<(&'a CallableDecl, LocalItemId)>,
 }
 
 impl<'a> Visitor<'a> for EntryPointFinder<'a> {
     fn visit_item(&mut self, item: &'a Item) {
         if let ItemKind::Callable(callable) = &item.kind {
             if item.attrs.iter().any(|a| a == &Attr::EntryPoint) {
-                self.callables.push(callable);
+                self.callables.push((callable, item.id));
             }
         }
     }
diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs
index baf57b43..d92e268d 100644
--- a/compiler/qsc_passes/src/lib.rs
+++ b/compiler/qsc_passes/src/lib.rs
@@ -15,6 +15,7 @@ mod replace_qubit_allocation;
 mod spec_gen;
 
 use callable_limits::CallableLimits;
+use entry_point::generate_entry_expr;
 use loop_unification::LoopUni;
 use miette::Diagnostic;
 use qsc_frontend::{compile::CompileUnit, incremental::Fragment};
@@ -53,6 +54,8 @@ pub fn run_default_passes(core: &Table, unit: &mut CompileUnit) -> Vec<Error> {
 
     let conjugate_errors = conjugate_invert::invert_conjugate_exprs(core, unit);
 
+    let entry_point_errors = generate_entry_expr(unit);
+
     LoopUni {
         core,
         assigner: &mut unit.assigner,
@@ -66,6 +69,7 @@ pub fn run_default_passes(core: &Table, unit: &mut CompileUnit) -> Vec<Error> {
         .chain(borrow_errors.into_iter().map(Error::BorrowCk))
         .chain(spec_errors.into_iter().map(Error::SpecGen))
         .chain(conjugate_errors.into_iter().map(Error::ConjInvert))
+        .chain(entry_point_errors.into_iter())
         .collect()
 }
 
diff --git a/katas/src/lib.rs b/katas/src/lib.rs
index 85f82d02..5264e456 100644
--- a/katas/src/lib.rs
+++ b/katas/src/lib.rs
@@ -7,7 +7,11 @@
 mod tests;
 
 use qsc::{
-    interpret::{output::Receiver, stateless, Value},
+    interpret::{
+        output::Receiver,
+        stateless::{self, Interpreter},
+        Value,
+    },
     SourceContents, SourceMap, SourceName,
 };
 
@@ -27,8 +31,9 @@ pub fn check_solution(
     receiver: &mut impl Receiver,
 ) -> Result<bool, Vec<stateless::Error>> {
     let source_map = SourceMap::new(exercise_sources, Some(EXERCISE_ENTRY.into()));
-    let context = stateless::Context::new(true, source_map)?;
-    context.eval(receiver).map(|value| {
+    let interpreter: Interpreter = Interpreter::new(true, source_map)?;
+    let mut eval_ctx = interpreter.new_eval_context();
+    eval_ctx.eval_entry(receiver).map(|value| {
         if let Value::Bool(success) = value {
             success
         } else {
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index 760b9d25..50031ab4 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -116,8 +116,8 @@ where
 {
     let mut out = CallbackReceiver { event_cb };
     let sources = SourceMap::new([("code".into(), code.into())], Some(expr.into()));
-    let context = stateless::Context::new(true, sources);
-    if let Err(err) = context {
+    let interpreter = stateless::Interpreter::new(true, sources);
+    if let Err(err) = interpreter {
         // TODO: handle multiple errors
         // https://github.com/microsoft/qsharp/issues/149
         let e = err[0].clone();
@@ -127,9 +127,10 @@ where
         (out.event_cb)(&msg.to_string());
         return Err(e);
     }
-    let context = context.expect("context should be valid");
+    let interpreter = interpreter.expect("context should be valid");
     for _ in 0..shots {
-        let result = context.eval(&mut out);
+        let mut eval_ctx = interpreter.new_eval_context();
+        let result = eval_ctx.eval_entry(&mut out);
         let mut success = true;
         let msg: serde_json::Value = match result {
             Ok(value) => serde_json::Value::String(value.to_string()),


Test patch:
diff --git a/compiler/qsc/src/interpret/debug/tests.rs b/compiler/qsc/src/interpret/debug/tests.rs
index c04ed4c6..ac4e6d37 100644
--- a/compiler/qsc/src/interpret/debug/tests.rs
+++ b/compiler/qsc/src/interpret/debug/tests.rs
@@ -9,7 +9,7 @@ use std::io::Cursor;
 
 use crate::interpret::{
     stateful::{Interpreter, LineError},
-    stateless::{self, Context},
+    stateless::{self},
 };
 
 fn line(
@@ -24,10 +24,11 @@ fn line(
     )
 }
 
-fn eval(context: &Context) -> (Result<Value, Vec<stateless::Error>>, String) {
+fn eval(interpreter: &stateless::Interpreter) -> (Result<Value, Vec<stateless::Error>>, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
-    (context.eval(&mut receiver), receiver.dump())
+    let mut eval_ctx = interpreter.new_eval_context();
+    (eval_ctx.eval_entry(&mut receiver), receiver.dump())
 }
 
 #[test]
@@ -137,10 +138,10 @@ fn stack_traces_can_cross_file_and_entry_boundaries() {
         ],
         Some("Adjoint Test2.A(0);".into()),
     );
-    let context =
-        stateless::Context::new(true, source_map).expect("Failed to compile base environment.");
+    let interpreter =
+        stateless::Interpreter::new(true, source_map).expect("Failed to compile base environment.");
 
-    let (result, _) = eval(&context);
+    let (result, _) = eval(&interpreter);
 
     match result {
         Ok(_) => panic!("Expected error"),
diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs
index 1fd1caea..f825fa30 100644
--- a/compiler/qsc_eval/src/intrinsic/tests.rs
+++ b/compiler/qsc_eval/src/intrinsic/tests.rs
@@ -3,22 +3,30 @@
 
 use std::f64::consts;
 
+use crate::tests::eval_expr;
+use crate::{
+    output::{GenericReceiver, Receiver},
+    tests::get_global,
+    val::{GlobalId, Value},
+    Error, GlobalLookup,
+};
 use expect_test::{expect, Expect};
 use indoc::indoc;
 use num_bigint::BigInt;
 use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_passes::{run_core_passes, run_default_passes};
 
-use crate::{
-    backend::SparseSim,
-    eval_expr,
-    output::{GenericReceiver, Receiver},
-    tests::get_global,
-    val::Value,
-    Env, Error,
-};
+struct Lookup<'a> {
+    store: &'a PackageStore,
+}
+
+impl<'a> GlobalLookup<'a> for Lookup<'a> {
+    fn get(&self, id: GlobalId) -> Option<crate::Global<'a>> {
+        get_global(self.store, id)
+    }
+}
 
-fn check_intrinsic(file: &str, expr: &str, out: &mut dyn Receiver) -> Result<Value, Error> {
+fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result<Value, Error> {
     let mut core = compile::core();
     run_core_passes(&mut core);
     let mut store = PackageStore::new(core);
@@ -37,16 +45,8 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut dyn Receiver) -> Result<Val
         .get(id)
         .and_then(|unit| unit.package.entry.as_ref())
         .expect("package should have entry");
-
-    eval_expr(
-        entry,
-        &|id| get_global(&store, id),
-        id,
-        &mut Env::default(),
-        &mut SparseSim::new(),
-        out,
-    )
-    .map_err(|e| e.0)
+    let lookup = Lookup { store: &store };
+    eval_expr(entry, &lookup, id, out).map_err(|e| e.0)
 }
 
 fn check_intrinsic_result(file: &str, expr: &str, expect: &Expect) {
diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs
index e384a4ed..4ddff3e1 100644
--- a/compiler/qsc_eval/src/tests.rs
+++ b/compiler/qsc_eval/src/tests.rs
@@ -1,12 +1,47 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::{backend::SparseSim, eval_expr, output::GenericReceiver, val::GlobalId, Env, Global};
+use crate::{
+    backend::SparseSim,
+    debug::CallStack,
+    output::{GenericReceiver, Receiver},
+    val::GlobalId,
+    Env, Error, Global, GlobalLookup, State, Value,
+};
 use expect_test::{expect, Expect};
 use indoc::indoc;
 use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
+use qsc_hir::hir::Expr;
 use qsc_hir::hir::ItemKind;
+use qsc_hir::hir::PackageId;
+
 use qsc_passes::{run_core_passes, run_default_passes};
+/// Evaluates the given expression with the given context.
+/// Creates a new environment and simulator.
+/// # Errors
+/// Returns the first error encountered during execution.
+pub(super) fn eval_expr<'a>(
+    expr: &'a Expr,
+    globals: &impl GlobalLookup<'a>,
+    package: PackageId,
+    out: &mut impl Receiver,
+) -> Result<Value, (Error, CallStack)> {
+    let mut state = State::new(package);
+    state.push_expr(expr);
+    let mut env = Env::with_empty_scope();
+    let mut sim = SparseSim::new();
+    state.eval(globals, &mut env, &mut sim, out)
+}
+
+struct Lookup<'a> {
+    store: &'a PackageStore,
+}
+
+impl<'a> GlobalLookup<'a> for Lookup<'a> {
+    fn get(&self, id: GlobalId) -> Option<crate::Global<'a>> {
+        get_global(self.store, id)
+    }
+}
 
 fn check_expr(file: &str, expr: &str, expect: &Expect) {
     let mut core = compile::core();
@@ -31,14 +66,8 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) {
         .expect("package should have entry");
 
     let mut out = Vec::new();
-    match eval_expr(
-        entry,
-        &|id| get_global(&store, id),
-        id,
-        &mut Env::default(),
-        &mut SparseSim::new(),
-        &mut GenericReceiver::new(&mut out),
-    ) {
+    let lookup = Lookup { store: &store };
+    match eval_expr(entry, &lookup, id, &mut GenericReceiver::new(&mut out)) {
         Ok(value) => expect.assert_eq(&value.to_string()),
         Err(err) => expect.assert_debug_eq(&err),
     }
diff --git a/compiler/qsc_passes/src/entry_point/tests.rs b/compiler/qsc_passes/src/entry_point/tests.rs
index 9dfc11dd..2202fe4a 100644
--- a/compiler/qsc_passes/src/entry_point/tests.rs
+++ b/compiler/qsc_passes/src/entry_point/tests.rs
@@ -4,8 +4,15 @@
 use expect_test::{expect, Expect};
 use indoc::indoc;
 use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
+use qsc_hir::hir::{Expr, Package};
 
-use crate::entry_point::extract_entry;
+/// Extracts a single entry point callable declaration, if found.
+/// # Errors
+/// Returns an error if a single entry point with no parameters cannot be found.
+fn extract_entry(package: &Package) -> Result<Expr, Vec<crate::Error>> {
+    let callables = super::get_callables(package);
+    super::create_entry_from_callables(callables)
+}
 
 fn check(file: &str, expr: &str, expect: &Expect) {
     let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into()));
@@ -28,10 +35,9 @@ fn test_entry_point_attr_to_expr() {
             }"},
         "",
         &expect![[r#"
-            Expr _id_ [0-0] [Type Int]: Expr Block: Block 4 [62-72] [Type Int]:
-                Stmt 5 [64-70]: Expr: Expr 6 [64-70] [Type Int]: BinOp (Add):
-                    Expr 7 [64-66] [Type Int]: Lit: Int(41)
-                    Expr 8 [69-70] [Type Int]: Lit: Int(1)"#]],
+            Expr _id_ [39-72] [Type Int]: Call:
+                Expr _id_ [39-72] [Type Int]: Var: Item 1
+                Expr _id_ [39-72] [Type Unit]: Unit"#]],
     );
 }
 
diff --git a/library/tests/src/lib.rs b/library/tests/src/lib.rs
index ac63a07f..44471544 100644
--- a/library/tests/src/lib.rs
+++ b/library/tests/src/lib.rs
@@ -26,9 +26,10 @@ pub fn test_expression(expr: &str, expected: &Value) {
 
     let sources = SourceMap::new([("test".into(), "".into())], Some(expr.into()));
 
-    let context = stateless::Context::new(true, sources).expect("test should compile");
-    let result = context
-        .eval(&mut out)
+    let interpreter = stateless::Interpreter::new(true, sources).expect("test should compile");
+    let mut eval_ctx = interpreter.new_eval_context();
+    let result = eval_ctx
+        .eval_entry(&mut out)
         .expect("test should run successfully");
 
     assert_eq!(expected, &result);