From 2597e6c550a1bdaa4ecb3e549c952446efa08ce6 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sun, 17 Mar 2024 08:05:49 -0700 Subject: [PATCH 01/16] Adjust mimalloc to allow wasi benchmarks --- allocator/Cargo.toml | 2 +- compiler/qsc/Cargo.toml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/allocator/Cargo.toml b/allocator/Cargo.toml index e75459172f..26a440adcb 100644 --- a/allocator/Cargo.toml +++ b/allocator/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true license.workspace = true version.workspace = true -[dependencies] +[target.'cfg(not(target_family = "wasm"))'.dependencies] mimalloc-sys = { path = "./mimalloc-sys" } [lints] diff --git a/compiler/qsc/Cargo.toml b/compiler/qsc/Cargo.toml index 37698069f5..e4cc829628 100644 --- a/compiler/qsc/Cargo.toml +++ b/compiler/qsc/Cargo.toml @@ -28,8 +28,6 @@ qsc_passes = { path = "../qsc_passes" } qsc_project = { path = "../qsc_project", features = ["fs"] } rustc-hash = { workspace = true } thiserror = { workspace = true } - -[target.'cfg(not(any(target_family = "wasm")))'.dependencies] allocator = { path = "../../allocator" } [dev-dependencies] From 12b507894e44a8fd6d2895671545a8e7c0f3c51c Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Fri, 8 Mar 2024 12:36:36 -0800 Subject: [PATCH 02/16] Reduce size of `Value` enum with boxing --- compiler/qsc/src/interpret.rs | 2 + compiler/qsc_eval/src/lib.rs | 75 ++++++++++++++++++++++++----------- compiler/qsc_eval/src/val.rs | 24 ++++++++--- library/src/tests/core.rs | 35 ++++++++++++++-- 4 files changed, 104 insertions(+), 32 deletions(-) diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs index 7d0819cf31..6e828c21fa 100644 --- a/compiler/qsc/src/interpret.rs +++ b/compiler/qsc/src/interpret.rs @@ -12,6 +12,8 @@ mod debugger_tests; pub use qsc_eval::{ debug::Frame, output::{self, GenericReceiver}, + val::Closure, + val::Range as EvalRange, val::Result, val::Value, StepAction, StepResult, diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 2afff705b2..ebd0fc3538 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -1081,7 +1081,7 @@ impl State { ) -> 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)), + Value::Closure(inner) => (inner.id, inner.functor, Some(inner.fixed_args)), Value::Global(id, functor) => (id, functor, None), _ => panic!("value is not callable"), }; @@ -1149,9 +1149,17 @@ impl State { fn eval_field(&mut self, field: Field) { let record = self.pop_val(); let val = match (record, field) { - (Value::Range(Some(start), _, _), Field::Prim(PrimField::Start)) => Value::Int(start), - (Value::Range(_, step, _), Field::Prim(PrimField::Step)) => Value::Int(step), - (Value::Range(_, _, Some(end)), Field::Prim(PrimField::End)) => Value::Int(end), + (Value::Range(inner), Field::Prim(PrimField::Start)) => Value::Int( + inner + .start + .expect("range access should be validated by compiler"), + ), + (Value::Range(inner), Field::Prim(PrimField::Step)) => Value::Int(inner.step), + (Value::Range(inner), Field::Prim(PrimField::End)) => Value::Int( + inner + .end + .expect("range access should be validated by compiler"), + ), (record, Field::Path(path)) => { follow_field_path(record, &path.indices).expect("field path should be valid") } @@ -1175,12 +1183,12 @@ impl State { let arr = self.pop_val().unwrap_array(); match &index_val { Value::Int(i) => self.push_val(index_array(&arr, *i, self.to_global_span(span))?), - &Value::Range(start, step, end) => { + Value::Range(inner) => { self.push_val(slice_array( &arr, - start, - step, - end, + inner.start, + inner.step, + inner.end, self.to_global_span(span), )?); } @@ -1205,7 +1213,7 @@ impl State { } else { None }; - self.push_val(Value::Range(start, step, end)); + self.push_val(Value::Range(val::Range { start, step, end }.into())); } fn eval_ret(&mut self, env: &mut Env) { @@ -1239,9 +1247,14 @@ impl State { let span = self.to_global_span(span); match index { Value::Int(index) => self.eval_update_index_single(&values, index, update, span), - Value::Range(start, step, end) => { - self.eval_update_index_range(&values, start, step, end, update, span) - } + Value::Range(inner) => self.eval_update_index_range( + &values, + inner.start, + inner.step, + inner.end, + update, + span, + ), _ => unreachable!("array should only be indexed by Int or Range"), } } @@ -1327,8 +1340,14 @@ impl State { let val = self.pop_val(); match op { UnOp::Functor(functor) => match val { - Value::Closure(args, id, app) => { - self.push_val(Value::Closure(args, id, update_functor_app(functor, app))); + Value::Closure(inner) => { + self.push_val(Value::Closure( + val::Closure { + functor: update_functor_app(functor, inner.functor), + ..*inner + } + .into(), + )); } Value::Global(id, app) => { self.push_val(Value::Global(id, update_functor_app(functor, app))); @@ -1362,14 +1381,17 @@ impl State { let value = self.pop_val(); let record = self.pop_val(); let update = match (record, field) { - (Value::Range(_, step, end), Field::Prim(PrimField::Start)) => { - Value::Range(Some(value.unwrap_int()), step, end) + (Value::Range(mut inner), Field::Prim(PrimField::Start)) => { + inner.start = Some(value.unwrap_int()); + Value::Range(inner) } - (Value::Range(start, _, end), Field::Prim(PrimField::Step)) => { - Value::Range(start, value.unwrap_int(), end) + (Value::Range(mut inner), Field::Prim(PrimField::Step)) => { + inner.step = value.unwrap_int(); + Value::Range(inner) } - (Value::Range(start, step, _), Field::Prim(PrimField::End)) => { - Value::Range(start, step, Some(value.unwrap_int())) + (Value::Range(mut inner), Field::Prim(PrimField::End)) => { + inner.end = Some(value.unwrap_int()); + Value::Range(inner) } (record, Field::Path(path)) => update_field_path(&record, &path.indices, &value) .expect("field path should be valid"), @@ -1502,10 +1524,10 @@ impl State { let Value::Array(arr) = &mut var.value else { panic!("variable should be an array"); }; - let Value::Range(start, step, end) = range else { + let Value::Range(inner) = range else { unreachable!("range should be a Value::Range"); }; - let range = make_range(arr, *start, *step, *end, range_span)?; + let range = make_range(arr, inner.start, inner.step, inner.end, range_span)?; for (idx, rhs) in range.into_iter().zip(rhs.iter()) { if idx < 0 { return Err(Error::InvalidNegativeInt(idx, range_span)); @@ -1647,7 +1669,14 @@ fn resolve_closure( package, item: callable, }; - Ok(Value::Closure(args.into(), callable, FunctorApp::default())) + Ok(Value::Closure( + val::Closure { + fixed_args: args.into(), + id: callable, + functor: FunctorApp::default(), + } + .into(), + )) } fn lit_to_val(lit: &Lit) -> Value { diff --git a/compiler/qsc_eval/src/val.rs b/compiler/qsc_eval/src/val.rs index 563f5a9f66..b022a13b54 100644 --- a/compiler/qsc_eval/src/val.rs +++ b/compiler/qsc_eval/src/val.rs @@ -16,18 +16,32 @@ pub enum Value { Array(Rc>), BigInt(BigInt), Bool(bool), - Closure(Rc<[Value]>, StoreItemId, FunctorApp), + Closure(Box), Double(f64), Global(StoreItemId, FunctorApp), Int(i64), Pauli(Pauli), Qubit(Qubit), - Range(Option, i64, Option), + Range(Box), Result(Result), String(Rc), Tuple(Rc<[Value]>), } +#[derive(Clone, Debug, PartialEq)] +pub struct Closure { + pub fixed_args: Rc<[Value]>, + pub id: StoreItemId, + pub functor: FunctorApp, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Range { + pub start: Option, + pub step: i64, + pub end: Option, +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum Result { Val(bool), @@ -103,7 +117,7 @@ impl Display for Value { Pauli::Y => write!(f, "PauliY"), }, Value::Qubit(v) => write!(f, "Qubit{}", (v.0)), - &Value::Range(start, step, end) => match (start, step, end) { + Value::Range(inner) => match (inner.start, inner.step, inner.end) { (Some(start), DEFAULT_RANGE_STEP, Some(end)) => write!(f, "{start}..{end}"), (Some(start), DEFAULT_RANGE_STEP, None) => write!(f, "{start}..."), (Some(start), step, Some(end)) => write!(f, "{start}..{step}..{end}"), @@ -268,10 +282,10 @@ impl Value { /// This will panic if the [Value] is not a [`Value::Range`]. #[must_use] pub fn unwrap_range(self) -> (Option, i64, Option) { - let Value::Range(start, step, end) = self else { + let Value::Range(inner) = self else { panic!("value should be Range, got {}", self.type_name()); }; - (start, step, end) + (inner.start, inner.step, inner.end) } /// Convert the [Value] into a measurement result diff --git a/library/src/tests/core.rs b/library/src/tests/core.rs index 26565c10e0..539e447aa7 100644 --- a/library/src/tests/core.rs +++ b/library/src/tests/core.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use super::test_expression; -use qsc::interpret::Value; +use qsc::interpret::{EvalRange, Value}; // Tests for Microsoft.Quantum.Core namespace @@ -73,18 +73,45 @@ fn check_range_empty_n2_n1_n3() { #[test] fn check_range_reverse_1_5() { - test_expression("RangeReverse(1..5)", &Value::Range(Some(5), -1, Some(1))); + test_expression( + "RangeReverse(1..5)", + &Value::Range( + EvalRange { + start: Some(5), + step: -1, + end: Some(1), + } + .into(), + ), + ); } #[test] fn check_range_reverse_1_n1_5() { - test_expression("RangeReverse(1..-1..5)", &Value::Range(Some(5), 1, Some(1))); + test_expression( + "RangeReverse(1..-1..5)", + &Value::Range( + EvalRange { + start: Some(5), + step: 1, + end: Some(1), + } + .into(), + ), + ); } #[test] fn check_range_reverse_1_7_10() { test_expression( "RangeReverse(1..7..10)", - &Value::Range(Some(8), -7, Some(1)), + &Value::Range( + EvalRange { + start: Some(8), + step: -7, + end: Some(1), + } + .into(), + ), ); } From c8c62a953c103870cf8c327df5fed12dde7f4f13 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Mon, 11 Mar 2024 01:07:34 -0700 Subject: [PATCH 03/16] Include control flow graph in FIR for evaluator --- compiler/qsc/src/interpret.rs | 92 +-- compiler/qsc/src/interpret/debugger_tests.rs | 4 - compiler/qsc_codegen/src/qir_base.rs | 3 +- compiler/qsc_eval/src/intrinsic/tests.rs | 2 +- compiler/qsc_eval/src/lib.rs | 826 ++++++------------- compiler/qsc_eval/src/lower.rs | 239 +++++- compiler/qsc_eval/src/tests.rs | 26 +- compiler/qsc_fir/src/fir.rs | 34 +- wasm/src/debug_service.rs | 5 +- 9 files changed, 546 insertions(+), 685 deletions(-) diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs index 6e828c21fa..56ca5c0109 100644 --- a/compiler/qsc/src/interpret.rs +++ b/compiler/qsc/src/interpret.rs @@ -9,6 +9,8 @@ mod tests; #[cfg(test)] mod debugger_tests; +use std::rc::Rc; + pub use qsc_eval::{ debug::Frame, output::{self, GenericReceiver}, @@ -39,9 +41,9 @@ use qsc_eval::{ debug::{map_fir_package_to_hir, map_hir_package_to_fir}, output::Receiver, val::{self}, - Env, EvalId, State, VariableInfo, + Env, State, VariableInfo, }; -use qsc_fir::fir::{self, Global, PackageStoreLookup}; +use qsc_fir::fir::{self, CfgNode, Global, PackageStoreLookup}; use qsc_fir::{ fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId}, visit::{self, Visitor}, @@ -175,11 +177,11 @@ impl Interpreter { &mut self, receiver: &mut impl Receiver, ) -> std::result::Result> { - let expr = self.get_entry_expr()?; + let cfg = self.get_entry_cfg()?; eval( self.source_package, self.classical_seed, - expr.into(), + cfg, self.compiler.package_store(), &self.fir_store, &mut Env::default(), @@ -195,14 +197,14 @@ impl Interpreter { sim: &mut impl Backend>, receiver: &mut impl Receiver, ) -> std::result::Result> { - let expr = self.get_entry_expr()?; + let cfg = self.get_entry_cfg()?; if self.quantum_seed.is_some() { sim.set_seed(self.quantum_seed); } eval( self.source_package, self.classical_seed, - expr.into(), + cfg, self.compiler.package_store(), &self.fir_store, &mut Env::default(), @@ -211,10 +213,10 @@ impl Interpreter { ) } - fn get_entry_expr(&self) -> std::result::Result> { + fn get_entry_cfg(&self) -> std::result::Result, Vec> { let unit = self.fir_store.get(self.source_package); - if let Some(entry) = unit.entry { - return Ok(entry); + if unit.entry.is_some() { + return Ok(unit.entry_cfg.clone()); }; Err(vec![Error::NoEntryPoint]) } @@ -235,7 +237,7 @@ impl Interpreter { .compile_fragments_fail_fast(&label, fragments) .map_err(into_errors)?; - let stmts = self.lower(&increment); + let (_, cfg) = self.lower(&increment); // Updating the compiler state with the new AST/HIR nodes // is not necessary for the interpreter to function, as all @@ -245,22 +247,16 @@ impl Interpreter { // here to keep the package stores consistent. self.compiler.update(increment); - let mut result = Value::unit(); - - for stmt_id in stmts { - result = eval( - self.package, - self.classical_seed, - stmt_id.into(), - self.compiler.package_store(), - &self.fir_store, - &mut self.env, - &mut self.sim, - receiver, - )?; - } - - Ok(result) + eval( + self.package, + self.classical_seed, + cfg.into(), + self.compiler.package_store(), + &self.fir_store, + &mut self.env, + &mut self.sim, + receiver, + ) } /// Runs the given entry expression on a new instance of the environment and simulator, @@ -302,7 +298,7 @@ impl Interpreter { receiver: &mut impl Receiver, expr: &str, ) -> std::result::Result> { - let expr_id = self.compile_entry_expr(expr)?; + let cfg = self.compile_entry_expr(expr)?; if self.quantum_seed.is_some() { sim.set_seed(self.quantum_seed); @@ -311,7 +307,7 @@ impl Interpreter { Ok(eval( self.package, self.classical_seed, - expr_id.into(), + cfg.into(), self.compiler.package_store(), &self.fir_store, &mut Env::default(), @@ -320,7 +316,7 @@ impl Interpreter { )) } - fn compile_entry_expr(&mut self, expr: &str) -> std::result::Result> { + fn compile_entry_expr(&mut self, expr: &str) -> std::result::Result, Vec> { let increment = self .compiler .compile_entry_expr(expr) @@ -328,7 +324,7 @@ impl Interpreter { // `lower` will update the entry expression in the FIR store, // and it will always return an empty list of statements. - let _ = self.lower(&increment); + let (_, cfg) = self.lower(&increment); // The AST and HIR packages in `increment` only contain an entry // expression and no statements. The HIR *can* contain items if the entry @@ -344,16 +340,19 @@ impl Interpreter { // here to keep the package stores consistent. self.compiler.update(increment); - let unit = self.fir_store.get(self.package); - let entry = unit.entry.expect("package should have an entry expression"); - - Ok(entry) + Ok(cfg) } - fn lower(&mut self, unit_addition: &qsc_frontend::incremental::Increment) -> Vec { + fn lower( + &mut self, + unit_addition: &qsc_frontend::incremental::Increment, + ) -> (Vec, Vec) { let fir_package = self.fir_store.get_mut(self.package); - self.lowerer - .lower_and_update_package(fir_package, &unit_addition.hir) + ( + self.lowerer + .lower_and_update_package(fir_package, &unit_addition.hir), + self.lowerer.cfg(), + ) } fn next_line_label(&mut self) -> String { @@ -389,24 +388,15 @@ impl Debugger { language_features, )?; let source_package_id = interpreter.source_package; + let unit = interpreter.fir_store.get(source_package_id); + let entry_cfg = unit.entry_cfg.clone(); Ok(Self { interpreter, position_encoding, - state: State::new(source_package_id, None), + state: State::new(source_package_id, entry_cfg, None), }) } - /// Loads the entry expression to the top of the evaluation stack. - /// This is needed for debugging so that when begging to debug with - /// a step action the system is already in the correct state. - /// # Errors - /// Returns a vector of errors if loading the entry point fails. - pub fn set_entry(&mut self) -> std::result::Result<(), Vec> { - let expr = self.interpreter.get_entry_expr()?; - qsc_eval::eval_push_expr(&mut self.state, expr); - Ok(()) - } - /// Resumes execution with specified `StepAction`. /// # Errors /// Returns a vector of errors if evaluating the entry point fails. @@ -529,14 +519,14 @@ impl Debugger { fn eval( package: PackageId, classical_seed: Option, - id: EvalId, + cfg: Rc<[CfgNode]>, package_store: &PackageStore, fir_store: &fir::PackageStore, env: &mut Env, sim: &mut impl Backend>, receiver: &mut impl Receiver, ) -> InterpretResult { - qsc_eval::eval(package, classical_seed, id, fir_store, env, sim, receiver) + qsc_eval::eval(package, classical_seed, cfg, fir_store, env, sim, receiver) .map_err(|(error, call_stack)| eval_error(package_store, fir_store, call_stack, error)) } diff --git a/compiler/qsc/src/interpret/debugger_tests.rs b/compiler/qsc/src/interpret/debugger_tests.rs index 254f7e4ea8..33a9a2758c 100644 --- a/compiler/qsc/src/interpret/debugger_tests.rs +++ b/compiler/qsc/src/interpret/debugger_tests.rs @@ -135,7 +135,6 @@ mod given_debugger { Encoding::Utf8, LanguageFeatures::default(), )?; - debugger.set_entry()?; let ids = get_breakpoint_ids(&debugger, "test"); let expected_id = ids[0]; expect_bp(&mut debugger, &ids, expected_id); @@ -159,7 +158,6 @@ mod given_debugger { Encoding::Utf8, LanguageFeatures::default(), )?; - debugger.set_entry()?; let ids = get_breakpoint_ids(&debugger, "test"); let expected_id = ids[0]; expect_bp(&mut debugger, &ids, expected_id); @@ -179,7 +177,6 @@ mod given_debugger { Encoding::Utf8, LanguageFeatures::default(), )?; - debugger.set_entry()?; let ids = get_breakpoint_ids(&debugger, "test"); let expected_id = ids[0]; expect_bp(&mut debugger, &ids, expected_id); @@ -206,7 +203,6 @@ mod given_debugger { Encoding::Utf8, LanguageFeatures::default(), )?; - debugger.set_entry()?; let ids = get_breakpoint_ids(&debugger, "test"); let expected_id = ids[0]; expect_bp(&mut debugger, &ids, expected_id); diff --git a/compiler/qsc_codegen/src/qir_base.rs b/compiler/qsc_codegen/src/qir_base.rs index af4dfda68a..bd753871f3 100644 --- a/compiler/qsc_codegen/src/qir_base.rs +++ b/compiler/qsc_codegen/src/qir_base.rs @@ -42,7 +42,6 @@ pub fn generate_qir( let package = map_hir_package_to_fir(package); let unit = fir_store.get(package); - let entry_expr = unit.entry.expect("package should have entry"); let mut sim = BaseProfSim::default(); let mut stdout = std::io::sink(); @@ -50,7 +49,7 @@ pub fn generate_qir( let result = eval( package, None, - entry_expr.into(), + unit.entry_cfg.clone(), &fir_store, &mut Env::default(), &mut sim, diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 727b0c31be..82a4908e80 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -180,7 +180,7 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result for EvalId { - fn from(expr: ExprId) -> Self { - Self::Expr(expr) - } -} - -impl From for EvalId { - fn from(stmt: StmtId) -> Self { - Self::Stmt(stmt) - } -} - /// Evaluates the given code with the given context. /// # Errors /// Returns the first error encountered during execution. @@ -199,17 +180,13 @@ impl From for EvalId { pub fn eval( package: PackageId, seed: Option, - id: EvalId, + cfg: Rc<[CfgNode]>, globals: &impl PackageStoreLookup, env: &mut Env, sim: &mut impl Backend>, receiver: &mut impl Receiver, ) -> Result)> { - let mut state = State::new(package, seed); - match id { - EvalId::Expr(expr) => state.push_expr(expr), - EvalId::Stmt(stmt) => state.push_stmt(stmt), - } + let mut state = State::new(package, cfg, seed); let res = state.eval(globals, env, sim, receiver, &[], StepAction::Continue)?; let StepResult::Return(value) = res else { panic!("eval should always return a value"); @@ -236,10 +213,6 @@ pub enum StepResult { Return(Value), } -pub fn eval_push_expr(state: &mut State, expr: ExprId) { - state.push_expr(expr); -} - trait AsIndex { type Output; @@ -261,7 +234,6 @@ impl AsIndex for i64 { struct Variable { name: Rc, value: Value, - mutability: Mutability, span: Span, } @@ -270,16 +242,9 @@ pub struct VariableInfo { pub value: Value, pub name: Rc, pub type_name: String, - pub mutability: Mutability, pub span: Span, } -impl Variable { - fn is_mutable(&self) -> bool { - self.mutability == Mutability::Mutable - } -} - struct Range { step: i64, end: i64, @@ -334,9 +299,13 @@ impl Env { } fn leave_scope(&mut self) { - self.0 - .pop() - .expect("scope should be entered first before leaving"); + // Only pop the scope if there is more than one scope in the stack, + // because the global/top-level scope cannot be exited. + if self.0.len() > 1 { + self.0 + .pop() + .expect("scope should have more than one entry."); + } } #[must_use] @@ -365,7 +334,6 @@ impl Env { name: var.name.clone(), type_name: var.value.type_name().to_string(), value: var.value.clone(), - mutability: var.mutability, span: var.span, }) .collect() @@ -388,44 +356,11 @@ impl Default for Env { } } -enum Cont { - Action, - Expr(ExprId), - Frame(usize), - Scope, - Stmt(StmtId), -} - -#[derive(Clone)] -enum Action { - Array(usize), - ArrayRepeat(Span), - ArrayAppendInPlace(ExprId), - Assign(ExprId), - Bind(PatId, Mutability), - BinOp(BinOp, Span, Option), - Call(Span, Span), - Consume, - Fail(Span), - Field(Field), - If(ExprId, Option), - Index(Span), - Range(bool, bool, bool), - Return, - StringConcat(usize), - StringLit(Rc), - UpdateIndex(Span), - UpdateIndexInPlace(ExprId, Span), - Tuple(usize), - UnOp(UnOp), - UpdateField(Field), - While(ExprId, BlockId), -} - pub struct State { - cont_stack: Vec, - action_stack: Vec, - vals: Vec, + cfg_stack: Vec>, + idx: u32, + idx_stack: Vec, + vals: Vec>, package: PackageId, call_stack: CallStack, current_span: Span, @@ -434,15 +369,16 @@ pub struct State { impl State { #[must_use] - pub fn new(package: PackageId, classical_seed: Option) -> Self { + pub fn new(package: PackageId, cfg: Rc<[CfgNode]>, classical_seed: Option) -> Self { let rng = match classical_seed { Some(seed) => RefCell::new(StdRng::seed_from_u64(seed)), None => RefCell::new(StdRng::from_entropy()), }; Self { - cont_stack: Vec::new(), - action_stack: Vec::new(), - vals: Vec::new(), + cfg_stack: vec![cfg], + idx: 0, + idx_stack: Vec::new(), + vals: vec![Vec::new()], package, call_stack: CallStack::default(), current_span: Span::default(), @@ -450,79 +386,59 @@ impl State { } } - fn pop_cont(&mut self) -> Option { - self.cont_stack.pop() - } - - fn push_action(&mut self, action: Action) { - self.cont_stack.push(Cont::Action); - self.action_stack.push(action); - } - - fn push_expr(&mut self, expr: ExprId) { - self.cont_stack.push(Cont::Expr(expr)); - } - - fn push_exprs(&mut self, exprs: &[ExprId]) { - self.cont_stack - .extend(exprs.iter().rev().map(|expr| Cont::Expr(*expr))); - } - - fn push_frame(&mut self, id: StoreItemId, functor: FunctorApp) { + fn push_frame(&mut self, cfg: Rc<[CfgNode]>, id: StoreItemId, functor: FunctorApp) { self.call_stack.push_frame(Frame { span: self.current_span, id, caller: self.package, functor, }); - self.cont_stack.push(Cont::Frame(self.vals.len())); + self.cfg_stack.push(cfg); + self.vals.push(Vec::new()); + self.idx_stack.push(self.idx); + self.idx = 0; self.package = id.package; } - fn leave_frame(&mut self, len: usize) { - let frame = self - .call_stack - .pop_frame() - .expect("frame should be present"); - self.package = frame.caller; - let frame_val = self.pop_val(); - self.vals.drain(len..); - self.push_val(frame_val); + fn leave_frame(&mut self) { + if let Some(frame) = self.call_stack.pop_frame() { + self.package = frame.caller; + let frame_val = self.pop_val(); + self.vals.pop(); + self.push_val(frame_val); + self.idx = self + .idx_stack + .pop() + .expect("should have at least one index"); + } + self.cfg_stack.pop(); } fn push_scope(&mut self, env: &mut Env) { env.push_scope(self.call_stack.len()); - self.cont_stack.push(Cont::Scope); - } - - fn push_stmt(&mut self, stmt: StmtId) { - self.cont_stack.push(Cont::Stmt(stmt)); - } - - fn push_block(&mut self, env: &mut Env, globals: &impl PackageStoreLookup, block: BlockId) { - let block = globals.get_block((self.package, block).into()); - self.push_scope(env); - for stmt in block.stmts.iter().rev() { - self.push_stmt(*stmt); - self.push_action(Action::Consume); - } - if block.stmts.is_empty() { - self.push_val(Value::unit()); - } else if let Some(Cont::Action) = self.pop_cont() { - self.action_stack.pop(); - } } fn pop_val(&mut self) -> Value { - self.vals.pop().expect("value should be present") + self.vals + .last_mut() + .expect("should have at least one value frame") + .pop() + .expect("value should be present") } fn pop_vals(&mut self, len: usize) -> Vec { - self.vals.drain(self.vals.len() - len..).collect() + let last = self + .vals + .last_mut() + .expect("should have at least one value frame"); + last.drain(last.len() - len..).collect() } fn push_val(&mut self, val: Value) { - self.vals.push(val); + self.vals + .last_mut() + .expect("should have at least one value frame") + .push(val); } #[must_use] @@ -551,30 +467,32 @@ impl State { ) -> Result)> { let current_frame = self.call_stack.len(); - while let Some(cont) = self.pop_cont() { - let res = match cont { - Cont::Action => { - let action = self.action_stack.pop().expect("action should be present"); - self.cont_action(env, sim, globals, action, out) - .map_err(|e| (e, self.get_stack_frames()))?; + while !self.cfg_stack.is_empty() { + let cfg = self + .cfg_stack + .last() + .expect("should have at least one stack frame") + .clone(); + let res = match cfg + .get(self.idx as usize) + .expect("should have next node in CFG") + { + CfgNode::Bind(pat) => { + self.idx += 1; + self.eval_bind(env, globals, *pat); continue; } - Cont::Expr(expr) => { - self.cont_expr(env, globals, expr) + CfgNode::Expr(expr) => { + self.idx += 1; + self.eval_expr(env, sim, globals, out, *expr) .map_err(|e| (e, self.get_stack_frames()))?; continue; } - Cont::Frame(len) => { - self.leave_frame(len); - continue; - } - Cont::Scope => { - env.leave_scope(); - continue; - } - Cont::Stmt(stmt) => { - self.cont_stmt(globals, stmt); - if let Some(bp) = breakpoints.iter().find(|&bp| *bp == stmt) { + CfgNode::Stmt(stmt) => { + self.idx += 1; + self.current_span = globals.get_stmt((self.package, *stmt).into()).span; + + if let Some(bp) = breakpoints.iter().find(|&bp| *bp == *stmt) { StepResult::BreakpointHit(*bp) } else { if self.current_span == Span::default() { @@ -594,6 +512,54 @@ impl State { } } } + CfgNode::Jump(idx) => { + self.idx = *idx; + continue; + } + CfgNode::JumpIf(idx) => { + let cond = self.pop_val(); + if cond.unwrap_bool() { + self.push_val(Value::Bool(true)); + self.idx = *idx; + } else { + self.idx += 1; + } + continue; + } + CfgNode::JumpIfNot(idx) => { + let cond = self.pop_val(); + if cond.unwrap_bool() { + self.idx += 1; + } else { + self.push_val(Value::Bool(false)); + self.idx = *idx; + } + continue; + } + CfgNode::JumpUnless(idx) => { + let cond = self.pop_val(); + if cond.unwrap_bool() { + self.idx += 1; + } else { + self.idx = *idx; + } + continue; + } + CfgNode::Consume => { + self.pop_val(); + self.idx += 1; + continue; + } + CfgNode::Unit => { + self.idx += 1; + self.push_val(Value::unit()); + continue; + } + CfgNode::Ret => { + self.leave_frame(); + env.leave_scope(); + continue; + } }; if let StepResult::Return(_) = res { @@ -607,341 +573,138 @@ impl State { } pub fn get_result(&mut self) -> Value { - self.pop_val() + // Some executions don't have any statements to execute, + // such as a fragment that has only item definitions. + // In that case, the values are empty and the result is unit. + self.vals + .last_mut() + .expect("should have at least one value frame") + .pop() + .unwrap_or_else(Value::unit) } #[allow(clippy::similar_names)] - fn cont_expr( + fn eval_expr( &mut self, env: &mut Env, + sim: &mut impl Backend>, globals: &impl PackageStoreLookup, + out: &mut impl Receiver, expr: ExprId, ) -> Result<(), Error> { let expr = globals.get_expr((self.package, expr).into()); self.current_span = expr.span; match &expr.kind { - ExprKind::Array(arr) => self.cont_arr(arr), - ExprKind::ArrayRepeat(item, size) => self.cont_arr_repeat(globals, *item, *size), - ExprKind::Assign(lhs, rhs) => self.cont_assign(*lhs, *rhs), - ExprKind::AssignOp(op, lhs, rhs) => self.cont_assign_op(env, globals, *op, *lhs, *rhs), - ExprKind::AssignField(record, field, replace) => { - self.cont_assign_field(*record, field, *replace); + ExprKind::Array(arr) => self.eval_arr(arr.len()), + ExprKind::ArrayRepeat(..) => self.eval_arr_repeat(expr.span)?, + ExprKind::Assign(lhs, _) => self.eval_assign(env, globals, *lhs)?, + ExprKind::AssignOp(op, lhs, rhs) => { + let rhs_span = globals.get_expr((self.package, *rhs).into()).span; + let (is_array, is_unique) = + is_updatable_in_place(env, globals.get_expr((self.package, *lhs).into())); + if is_array { + if is_unique { + self.eval_array_append_in_place(env, globals, *lhs)?; + return Ok(()); + } + let rhs_val = self.pop_val(); + self.eval_expr(env, sim, globals, out, *lhs)?; + self.push_val(rhs_val); + } + self.eval_binop(*op, rhs_span)?; + self.eval_assign(env, globals, *lhs)?; + } + ExprKind::AssignField(record, field, _) => { + self.eval_update_field(field.clone()); + self.eval_assign(env, globals, *record)?; + } + ExprKind::AssignIndex(lhs, mid, _) => { + let mid_span = globals.get_expr((self.package, *mid).into()).span; + let (_, is_unique) = + is_updatable_in_place(env, globals.get_expr((self.package, *lhs).into())); + if is_unique { + self.eval_update_index_in_place(env, globals, *lhs, mid_span)?; + return Ok(()); + } + self.eval_expr(env, sim, globals, out, *lhs)?; + self.eval_update_index(mid_span)?; + self.eval_assign(env, globals, *lhs)?; } - ExprKind::AssignIndex(lhs, mid, rhs) => { - self.cont_assign_index(env, globals, *lhs, *mid, *rhs); + ExprKind::BinOp(op, _, rhs) => { + let rhs_span = globals.get_expr((self.package, *rhs).into()).span; + self.eval_binop(*op, rhs_span)?; } - ExprKind::BinOp(op, lhs, rhs) => self.cont_binop(globals, *op, *rhs, *lhs), - ExprKind::Block(block) => self.push_block(env, globals, *block), + ExprKind::Block(..) => panic!("block expr should be handled by control flow"), ExprKind::Call(callee_expr, args_expr) => { - self.cont_call(globals, *callee_expr, *args_expr); + let callable_span = globals.get_expr((self.package, *callee_expr).into()).span; + let args_span = globals.get_expr((self.package, *args_expr).into()).span; + self.eval_call(env, sim, globals, callable_span, args_span, out)?; } ExprKind::Closure(args, callable) => { let closure = resolve_closure(env, self.package, expr.span, args, *callable)?; self.push_val(closure); } - ExprKind::Fail(fail_expr) => self.cont_fail(expr.span, *fail_expr), - ExprKind::Field(expr, field) => self.cont_field(*expr, field), + ExprKind::Fail(..) => { + return Err(Error::UserFail( + self.pop_val().unwrap_string().to_string(), + self.to_global_span(expr.span), + )); + } + ExprKind::Field(_, field) => self.eval_field(field.clone()), ExprKind::Hole => panic!("hole expr should be disallowed by passes"), - ExprKind::If(cond_expr, then_expr, else_expr) => { - self.cont_if(*cond_expr, *then_expr, *else_expr); + ExprKind::If(..) => { + panic!("if expr should be handled by control flow") + } + ExprKind::Index(_, rhs) => { + let rhs_span = globals.get_expr((self.package, *rhs).into()).span; + self.eval_index(rhs_span)?; } - ExprKind::Index(arr, index) => self.cont_index(globals, *arr, *index), ExprKind::Lit(lit) => self.push_val(lit_to_val(lit)), - ExprKind::Range(start, step, end) => self.cont_range(*start, *step, *end), - ExprKind::Return(expr) => self.cont_ret(*expr), - ExprKind::String(components) => self.cont_string(components), - ExprKind::UpdateIndex(lhs, mid, rhs) => self.update_index(globals, *lhs, *mid, *rhs), - ExprKind::Tuple(tup) => self.cont_tup(tup), - ExprKind::UnOp(op, expr) => self.cont_unop(*op, *expr), - ExprKind::UpdateField(record, field, replace) => { - self.cont_update_field(*record, field, *replace); + ExprKind::Range(start, step, end) => { + self.eval_range(start.is_some(), step.is_some(), end.is_some()); + } + ExprKind::Return(..) => panic!("return expr should be handled by control flow"), + ExprKind::String(components) => self.collect_string(components), + ExprKind::UpdateIndex(_, mid, _) => { + let mid_span = globals.get_expr((self.package, *mid).into()).span; + self.eval_update_index(mid_span)?; + } + ExprKind::Tuple(tup) => self.eval_tup(tup.len()), + ExprKind::UnOp(op, _) => self.eval_unop(*op), + ExprKind::UpdateField(_, field, _) => { + self.eval_update_field(field.clone()); } ExprKind::Var(res, _) => { self.push_val(resolve_binding(env, self.package, *res, expr.span)?); } - ExprKind::While(cond_expr, block) => self.cont_while(*cond_expr, *block), - } - - Ok(()) - } - - fn cont_tup(&mut self, tup: &[ExprId]) { - self.push_action(Action::Tuple(tup.len())); - self.push_exprs(tup); - } - - fn cont_arr(&mut self, arr: &[ExprId]) { - self.push_action(Action::Array(arr.len())); - self.push_exprs(arr); - } - - fn cont_arr_repeat(&mut self, globals: &impl PackageStoreLookup, item: ExprId, size: ExprId) { - let size_expr = globals.get_expr((self.package, size).into()); - self.push_action(Action::ArrayRepeat(size_expr.span)); - self.push_expr(size); - self.push_expr(item); - } - - fn cont_ret(&mut self, expr: ExprId) { - self.push_action(Action::Return); - self.push_expr(expr); - } - - fn cont_if(&mut self, cond_expr: ExprId, then_expr: ExprId, else_expr: Option) { - self.push_action(Action::If(then_expr, else_expr)); - self.push_expr(cond_expr); - } - - fn cont_fail(&mut self, span: Span, fail_expr: ExprId) { - self.push_action(Action::Fail(span)); - self.push_expr(fail_expr); - } - - fn cont_assign(&mut self, lhs: ExprId, rhs: ExprId) { - self.push_action(Action::Assign(lhs)); - self.push_expr(rhs); - self.push_val(Value::unit()); - } - - fn cont_assign_op( - &mut self, - env: &Env, - globals: &impl PackageStoreLookup, - op: BinOp, - lhs: ExprId, - rhs: ExprId, - ) { - // If we know the assign op is an array append, as in `set arr += other;`, we should attempt to perform it in-place. - if op == BinOp::Add { - let expr = globals.get_expr((self.package, lhs).into()); - if matches!(expr.ty, Ty::Array(_)) && is_updatable_in_place(env, expr) { - self.push_action(Action::ArrayAppendInPlace(lhs)); - self.push_expr(rhs); - self.push_val(Value::unit()); - return; + ExprKind::While(..) => { + panic!("while expr should be handled by control flow") } } - self.push_action(Action::Assign(lhs)); - self.cont_binop(globals, op, rhs, lhs); - self.push_val(Value::unit()); - } - - fn cont_assign_field(&mut self, record: ExprId, field: &Field, replace: ExprId) { - self.push_action(Action::Assign(record)); - self.cont_update_field(record, field, replace); - self.push_val(Value::unit()); - } - - fn cont_assign_index( - &mut self, - env: &Env, - globals: &impl PackageStoreLookup, - lhs: ExprId, - mid: ExprId, - rhs: ExprId, - ) { - if is_updatable_in_place(env, globals.get_expr((self.package, lhs).into())) { - let span = globals.get_expr((self.package, mid).into()).span; - self.push_action(Action::UpdateIndexInPlace(lhs, span)); - self.push_expr(rhs); - self.push_expr(mid); - self.push_val(Value::unit()); - } else { - self.push_action(Action::Assign(lhs)); - self.update_index(globals, lhs, mid, rhs); - self.push_val(Value::unit()); - } - } - - fn cont_field(&mut self, expr: ExprId, field: &Field) { - self.push_action(Action::Field(field.clone())); - self.push_expr(expr); - } - - fn cont_index(&mut self, globals: &impl PackageStoreLookup, arr: ExprId, index: ExprId) { - let index_expr = globals.get_expr((self.package, index).into()); - self.push_action(Action::Index(index_expr.span)); - self.push_expr(index); - self.push_expr(arr); - } - - fn cont_range(&mut self, start: Option, step: Option, end: Option) { - self.push_action(Action::Range( - start.is_some(), - step.is_some(), - end.is_some(), - )); - if let Some(end) = end { - self.push_expr(end); - } - if let Some(step) = step { - self.push_expr(step); - } - if let Some(start) = start { - self.push_expr(start); - } + Ok(()) } - fn cont_string(&mut self, components: &[StringComponent]) { + fn collect_string(&mut self, components: &[StringComponent]) { if let [StringComponent::Lit(str)] = components { self.push_val(Value::String(Rc::clone(str))); return; } - self.push_action(Action::StringConcat(components.len())); + let mut string = String::new(); for component in components.iter().rev() { match component { - StringComponent::Expr(expr) => self.push_expr(*expr), - StringComponent::Lit(lit) => self.push_action(Action::StringLit(lit.clone())), - } - } - } - - fn cont_while(&mut self, cond_expr: ExprId, block: BlockId) { - self.push_action(Action::While(cond_expr, block)); - self.push_expr(cond_expr); - } - - fn cont_call(&mut self, globals: &impl PackageStoreLookup, callee: ExprId, args: ExprId) { - let callee_expr = globals.get_expr((self.package, callee).into()); - let args_expr = globals.get_expr((self.package, args).into()); - self.push_action(Action::Call(callee_expr.span, args_expr.span)); - self.push_expr(args); - self.push_expr(callee); - } - - fn cont_binop( - &mut self, - globals: &impl PackageStoreLookup, - op: BinOp, - rhs: ExprId, - lhs: ExprId, - ) { - let rhs_expr = globals.get_expr((self.package, rhs).into()); - match op { - BinOp::Add - | BinOp::AndB - | BinOp::Div - | BinOp::Eq - | BinOp::Exp - | BinOp::Gt - | BinOp::Gte - | BinOp::Lt - | BinOp::Lte - | BinOp::Mod - | BinOp::Mul - | BinOp::Neq - | BinOp::OrB - | BinOp::Shl - | BinOp::Shr - | BinOp::Sub - | BinOp::XorB => { - self.push_action(Action::BinOp(op, rhs_expr.span, None)); - self.push_expr(rhs); - self.push_expr(lhs); - } - BinOp::AndL | BinOp::OrL => { - self.push_action(Action::BinOp(op, rhs_expr.span, Some(rhs))); - self.push_expr(lhs); - } - } - } - - fn update_index( - &mut self, - globals: &impl PackageStoreLookup, - lhs: ExprId, - mid: ExprId, - rhs: ExprId, - ) { - let span = globals.get_expr((self.package, mid).into()).span; - self.push_action(Action::UpdateIndex(span)); - self.push_expr(lhs); - self.push_expr(rhs); - self.push_expr(mid); - } - - fn cont_unop(&mut self, op: UnOp, expr: ExprId) { - self.push_action(Action::UnOp(op)); - self.push_expr(expr); - } - - fn cont_update_field(&mut self, record: ExprId, field: &Field, replace: ExprId) { - self.push_action(Action::UpdateField(field.clone())); - self.push_expr(replace); - self.push_expr(record); - } - - fn cont_stmt(&mut self, globals: &impl PackageStoreLookup, stmt: StmtId) { - let stmt = globals.get_stmt((self.package, stmt).into()); - self.current_span = stmt.span; - - match &stmt.kind { - StmtKind::Expr(expr) => self.push_expr(*expr), - StmtKind::Item(..) => self.push_val(Value::unit()), - StmtKind::Local(mutability, pat, expr) => { - self.push_action(Action::Bind(*pat, *mutability)); - self.push_expr(*expr); - self.push_val(Value::unit()); - } - StmtKind::Semi(expr) => { - self.push_action(Action::Consume); - self.push_expr(*expr); - self.push_val(Value::unit()); - } - } - } - - fn cont_action( - &mut self, - env: &mut Env, - sim: &mut impl Backend>, - globals: &impl PackageStoreLookup, - action: Action, - out: &mut impl Receiver, - ) -> Result<(), Error> { - match action { - Action::Array(len) => self.eval_arr(len), - Action::ArrayAppendInPlace(lhs) => { - self.eval_array_append_in_place(env, globals, lhs)?; - } - Action::ArrayRepeat(span) => self.eval_arr_repeat(span)?, - Action::Assign(lhs) => self.eval_assign(env, globals, lhs)?, - Action::BinOp(op, span, rhs) => self.eval_binop(op, span, rhs)?, - Action::Bind(pat, mutability) => self.eval_bind(env, globals, pat, mutability), - Action::Call(callable_span, args_span) => { - self.eval_call(env, sim, globals, callable_span, args_span, out)?; - } - Action::Consume => { - self.pop_val(); - } - Action::Fail(span) => { - return Err(Error::UserFail( - self.pop_val().unwrap_string().to_string(), - self.to_global_span(span), - )); - } - Action::Field(field) => self.eval_field(field), - Action::If(then_expr, else_expr) => self.eval_if(then_expr, else_expr), - Action::Index(span) => self.eval_index(span)?, - Action::Range(has_start, has_step, has_end) => { - self.eval_range(has_start, has_step, has_end); - } - Action::Return => self.eval_ret(env), - Action::StringConcat(len) => self.eval_string_concat(len), - Action::StringLit(str) => self.push_val(Value::String(str)), - Action::UpdateIndex(span) => self.eval_update_index(span)?, - Action::UpdateIndexInPlace(lhs, span) => { - self.eval_update_index_in_place(env, globals, lhs, span)?; + StringComponent::Expr(..) => { + let expr_str = format!("{}", self.pop_val()); + string.insert_str(0, &expr_str); + } + StringComponent::Lit(lit) => { + string.insert_str(0, lit); + } } - 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(env, globals, cond_expr, block), } - Ok(()) + self.push_val(Value::String(Rc::from(string))); } fn eval_arr(&mut self, len: usize) { @@ -959,16 +722,14 @@ impl State { let rhs = self.pop_val(); match (&lhs.kind, rhs) { (&ExprKind::Var(Res::Local(id), _), rhs) => match env.get_mut(id) { - Some(var) if var.is_mutable() => { + Some(var) => { var.value.append_array(rhs); } - Some(_) => { - unreachable!("update of mutable variable should be disallowed by compiler") - } None => return Err(Error::UnboundName(self.to_global_span(lhs.span))), }, _ => unreachable!("unassignable array update pattern should be disallowed by compiler"), } + self.push_val(Value::unit()); Ok(()) } @@ -993,31 +754,20 @@ impl State { lhs: ExprId, ) -> Result<(), Error> { let rhs = self.pop_val(); + self.push_val(Value::unit()); self.update_binding(env, globals, lhs, rhs) } - fn eval_bind( - &mut self, - env: &mut Env, - globals: &impl PackageStoreLookup, - pat: PatId, - mutability: Mutability, - ) { + fn eval_bind(&mut self, env: &mut Env, globals: &impl PackageStoreLookup, pat: PatId) { let val = self.pop_val(); - self.bind_value(env, globals, pat, val, mutability); + self.bind_value(env, globals, pat, val); + self.push_val(Value::unit()); } - fn eval_binop(&mut self, op: BinOp, span: Span, rhs: Option) -> Result<(), Error> { + fn eval_binop(&mut self, op: BinOp, span: Span) -> Result<(), Error> { match op { BinOp::Add => self.eval_binop_simple(eval_binop_add), BinOp::AndB => self.eval_binop_simple(eval_binop_andb), - BinOp::AndL => { - if self.pop_val().unwrap_bool() { - self.push_expr(rhs.expect("rhs should be provided with binop andl")); - } else { - self.push_val(Value::Bool(false)); - } - } BinOp::Div => self.eval_binop_with_error(span, eval_binop_div)?, BinOp::Eq => { let rhs_val = self.pop_val(); @@ -1037,17 +787,13 @@ impl State { self.push_val(Value::Bool(lhs_val != rhs_val)); } BinOp::OrB => self.eval_binop_simple(eval_binop_orb), - BinOp::OrL => { - if self.pop_val().unwrap_bool() { - self.push_val(Value::Bool(true)); - } else { - self.push_expr(rhs.expect("rhs should be provided with binop andl")); - } - } BinOp::Shl => self.eval_binop_with_error(span, eval_binop_shl)?, BinOp::Shr => self.eval_binop_with_error(span, eval_binop_shr)?, BinOp::Sub => self.eval_binop_simple(eval_binop_sub), BinOp::XorB => self.eval_binop_simple(eval_binop_xorb), + + // Logical operators should be handled by control flow + BinOp::AndL | BinOp::OrL => {} } Ok(()) } @@ -1100,10 +846,10 @@ impl State { let callee_span = self.to_global_span(callee.span); let spec = spec_from_functor_app(functor); - self.push_frame(callee_id, functor); - self.push_scope(env); match &callee.implementation { CallableImpl::Intrinsic => { + self.push_frame(Vec::new().into(), callee_id, functor); + let name = &callee.name.name; let val = intrinsic::call( name, @@ -1121,6 +867,7 @@ impl State { )); } self.push_val(val); + self.leave_frame(); Ok(()) } CallableImpl::Spec(specialized_implementation) => { @@ -1131,6 +878,9 @@ impl State { Spec::CtlAdj => specialized_implementation.ctl_adj.as_ref(), } .expect("missing specialization should be a compilation error"); + self.push_frame(spec_decl.cfg.clone(), callee_id, functor); + self.push_scope(env); + self.bind_args_for_spec( env, globals, @@ -1140,7 +890,6 @@ impl State { functor.controlled, fixed_args, ); - self.push_block(env, globals, spec_decl.block); Ok(()) } } @@ -1168,16 +917,6 @@ impl State { self.push_val(val); } - fn eval_if(&mut self, then_expr: ExprId, else_expr: Option) { - if self.pop_val().unwrap_bool() { - self.push_expr(then_expr); - } else if let Some(else_expr) = else_expr { - self.push_expr(else_expr); - } else { - self.push_val(Value::unit()); - } - } - fn eval_index(&mut self, span: Span) -> Result<(), Error> { let index_val = self.pop_val(); let arr = self.pop_val().unwrap_array(); @@ -1216,30 +955,6 @@ impl State { self.push_val(Value::Range(val::Range { start, step, end }.into())); } - 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 => env.leave_scope(), - Cont::Action => { - self.action_stack.pop(); - } - _ => {} - } - } - } - - fn eval_string_concat(&mut self, len: usize) { - let mut string = String::new(); - for component in self.pop_vals(len) { - write!(string, "{component}").expect("string should be writable"); - } - self.push_val(Value::String(string.into())); - } - fn eval_update_index(&mut self, span: Span) -> Result<(), Error> { let values = self.pop_val().unwrap_array(); let update = self.pop_val(); @@ -1378,8 +1093,8 @@ impl State { } fn eval_update_field(&mut self, field: Field) { - let value = self.pop_val(); let record = self.pop_val(); + let value = self.pop_val(); let update = match (record, field) { (Value::Range(mut inner), Field::Prim(PrimField::Start)) => { inner.start = Some(value.unwrap_int()); @@ -1400,31 +1115,7 @@ impl State { self.push_val(update); } - fn eval_while( - &mut self, - env: &mut Env, - globals: &impl PackageStoreLookup, - cond_expr: ExprId, - block: BlockId, - ) { - 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(env, globals, block); - } else { - self.push_val(Value::unit()); - } - } - - fn bind_value( - &self, - env: &mut Env, - globals: &impl PackageStoreLookup, - pat: PatId, - val: Value, - mutability: Mutability, - ) { + fn bind_value(&self, env: &mut Env, globals: &impl PackageStoreLookup, pat: PatId, val: Value) { let pat = globals.get_pat((self.package, pat).into()); match &pat.kind { PatKind::Bind(variable) => { @@ -1434,7 +1125,6 @@ impl State { Variable { name: variable.name.clone(), value: val, - mutability, span: variable.span, }, ); @@ -1443,7 +1133,7 @@ impl State { PatKind::Tuple(tup) => { let val_tup = val.unwrap_tuple(); for (pat, val) in tup.iter().zip(val_tup.iter()) { - self.bind_value(env, globals, *pat, val.clone(), mutability); + self.bind_value(env, globals, *pat, val.clone()); } } } @@ -1461,12 +1151,9 @@ impl State { match (&lhs.kind, rhs) { (ExprKind::Hole, _) => {} (&ExprKind::Var(Res::Local(id), _), rhs) => match env.get_mut(id) { - Some(var) if var.is_mutable() => { + Some(var) => { var.value = rhs; } - Some(_) => { - unreachable!("update of mutable variable should be disallowed by compiler") - } None => return Err(Error::UnboundName(self.to_global_span(lhs.span))), }, (ExprKind::Tuple(var_tup), Value::Tuple(tup)) => { @@ -1480,7 +1167,7 @@ impl State { } fn update_array_index_single( - &self, + &mut self, env: &mut Env, globals: &impl PackageStoreLookup, lhs: ExprId, @@ -1491,24 +1178,22 @@ impl State { let lhs = globals.get_expr((self.package, lhs).into()); match &lhs.kind { &ExprKind::Var(Res::Local(id), _) => match env.get_mut(id) { - Some(var) if var.is_mutable() => { + Some(var) => { var.value.update_array(index, rhs).map_err(|idx| { Error::IndexOutOfRange(idx.try_into().expect("index should be valid"), span) })?; } - Some(_) => { - unreachable!("update of immutable variable should be disallowed by compiler") - } None => return Err(Error::UnboundName(self.to_global_span(lhs.span))), }, _ => unreachable!("unassignable array update pattern should be disallowed by compiler"), } + self.push_val(Value::unit()); Ok(()) } #[allow(clippy::similar_names)] // `env` and `end` are similar but distinct fn update_array_index_range( - &self, + &mut self, env: &mut Env, globals: &impl PackageStoreLookup, lhs: ExprId, @@ -1519,7 +1204,7 @@ impl State { let lhs = globals.get_expr((self.package, lhs).into()); match &lhs.kind { &ExprKind::Var(Res::Local(id), _) => match env.get_mut(id) { - Some(var) if var.is_mutable() => { + Some(var) => { let rhs = update.unwrap_array(); let Value::Array(arr) = &mut var.value else { panic!("variable should be an array"); @@ -1541,13 +1226,11 @@ impl State { })?; } } - Some(_) => { - unreachable!("update of mutable variable should be disallowed by compiler") - } None => return Err(Error::UnboundName(self.to_global_span(lhs.span))), }, _ => unreachable!("unassignable array update pattern should be disallowed by compiler"), } + self.push_val(Value::unit()); Ok(()) } @@ -1579,27 +1262,14 @@ impl State { tup = rest.clone(); } - self.bind_value( - env, - globals, - spec_pat, - Value::Array(ctls.into()), - Mutability::Immutable, - ); - self.bind_value( - env, - globals, - decl_pat, - merge_fixed_args(fixed_args, tup), - Mutability::Immutable, - ); + self.bind_value(env, globals, spec_pat, Value::Array(ctls.into())); + self.bind_value(env, globals, decl_pat, merge_fixed_args(fixed_args, tup)); } None => self.bind_value( env, globals, decl_pat, merge_fixed_args(fixed_args, args_val), - Mutability::Immutable, ), } } @@ -1761,7 +1431,7 @@ fn eval_binop_add(lhs_val: Value, rhs_val: Value) -> Value { let rhs = rhs_val.unwrap_string(); Value::String((val.to_string() + &rhs).into()) } - _ => panic!("value is not addable"), + _ => panic!("value is not addable: {}", lhs_val.type_name()), } } @@ -2106,15 +1776,15 @@ fn update_field_path(record: &Value, path: &[usize], replace: &Value) -> Option< } } -fn is_updatable_in_place(env: &Env, expr: &Expr) -> bool { +fn is_updatable_in_place(env: &Env, expr: &Expr) -> (bool, bool) { match &expr.kind { ExprKind::Var(Res::Local(id), _) => match env.get(*id) { - Some(var) if var.is_mutable() => match &var.value { - Value::Array(var) => Rc::weak_count(var) + Rc::strong_count(var) == 1, - _ => false, + Some(var) => match &var.value { + Value::Array(var) => (true, Rc::weak_count(var) + Rc::strong_count(var) == 1), + _ => (false, false), }, - _ => false, + _ => (false, false), }, - _ => false, + _ => (false, false), } } diff --git a/compiler/qsc_eval/src/lower.rs b/compiler/qsc_eval/src/lower.rs index 4fd96b469b..3fda6d580d 100644 --- a/compiler/qsc_eval/src/lower.rs +++ b/compiler/qsc_eval/src/lower.rs @@ -3,12 +3,13 @@ use qsc_data_structures::index_map::IndexMap; use qsc_fir::assigner::Assigner; -use qsc_fir::fir::{Block, CallableImpl, Expr, Pat, SpecImpl, Stmt}; +use qsc_fir::fir::{Block, CallableImpl, CfgNode, Expr, Pat, SpecImpl, Stmt}; use qsc_fir::{ fir::{self, BlockId, ExprId, LocalItemId, PatId, StmtId}, ty::{Arrow, InferFunctorId, ParamId, Ty}, }; use qsc_hir::hir::{self, SpecBody, SpecGen}; +use std::iter::once; use std::{clone::Clone, rc::Rc}; pub struct Lowerer { @@ -19,6 +20,7 @@ pub struct Lowerer { stmts: IndexMap, blocks: IndexMap, assigner: Assigner, + cfg: Vec, } impl Default for Lowerer { @@ -38,11 +40,17 @@ impl Lowerer { stmts: IndexMap::new(), blocks: IndexMap::new(), assigner: Assigner::new(), + cfg: Vec::new(), } } + pub fn cfg(&mut self) -> Vec { + self.cfg.drain(..).chain(once(CfgNode::Ret)).collect() + } + pub fn lower_package(&mut self, package: &hir::Package) -> fir::Package { let entry = package.entry.as_ref().map(|e| self.lower_expr(e)); + let entry_cfg = self.cfg.drain(..).chain(once(CfgNode::Ret)).collect(); let items: IndexMap = package .items .values() @@ -63,6 +71,7 @@ impl Lowerer { let package = fir::Package { items, entry, + entry_cfg, blocks, exprs, pats, @@ -213,6 +222,7 @@ impl Lowerer { span: decl.span, block, input, + cfg: self.cfg.drain(..).chain(once(CfgNode::Ret)).collect(), } } @@ -237,30 +247,57 @@ impl Lowerer { fn lower_block(&mut self, block: &hir::Block) -> BlockId { let id = self.assigner.next_block(); + let len = self.cfg.len(); let block = fir::Block { id, span: block.span, ty: self.lower_ty(&block.ty), - stmts: block.stmts.iter().map(|s| self.lower_stmt(s)).collect(), + stmts: block + .stmts + .iter() + .map(|s| { + let is_item = matches!(s.kind, hir::StmtKind::Item(_)); + let s = self.lower_stmt(s); + if !is_item { + self.cfg.push(CfgNode::Consume); + } + s + }) + .collect(), }; + if self.cfg.len() == len { + // There were no statements in the block, so we need to insert a no-op to ensure a + // Unit value is returned for the expr. + self.cfg.push(CfgNode::Unit); + } else { + // Pop the last consume so the final statement can be an implicit return from the block. + self.cfg.pop(); + } self.blocks.insert(id, block); id } fn lower_stmt(&mut self, stmt: &hir::Stmt) -> fir::StmtId { let id = self.assigner.next_stmt(); + self.cfg.push(CfgNode::Stmt(id)); let kind = match &stmt.kind { hir::StmtKind::Expr(expr) => fir::StmtKind::Expr(self.lower_expr(expr)), hir::StmtKind::Item(item) => fir::StmtKind::Item(lower_local_item_id(*item)), - hir::StmtKind::Local(mutability, pat, expr) => fir::StmtKind::Local( - lower_mutability(*mutability), - self.lower_pat(pat), - self.lower_expr(expr), - ), + hir::StmtKind::Local(mutability, pat, expr) => { + let pat = self.lower_pat(pat); + let expr = self.lower_expr(expr); + self.cfg.push(CfgNode::Bind(pat)); + fir::StmtKind::Local(lower_mutability(*mutability), pat, expr) + } hir::StmtKind::Qubit(_, _, _, _) => { panic!("qubit statements should have been eliminated by passes"); } - hir::StmtKind::Semi(expr) => fir::StmtKind::Semi(self.lower_expr(expr)), + hir::StmtKind::Semi(expr) => { + let expr = self.lower_expr(expr); + self.cfg.push(CfgNode::Consume); + self.cfg.push(CfgNode::Unit); + fir::StmtKind::Semi(expr) + } }; let stmt = fir::Stmt { id, @@ -284,26 +321,97 @@ impl Lowerer { fir::ExprKind::ArrayRepeat(self.lower_expr(value), self.lower_expr(size)) } hir::ExprKind::Assign(lhs, rhs) => { - fir::ExprKind::Assign(self.lower_expr(lhs), self.lower_expr(rhs)) + let idx = self.cfg.len(); + let lhs = self.lower_expr(lhs); + // The left-hand side of an assigment is not really an expression to be executed, + // so remove any added nodes from the CFG. + self.cfg.drain(idx..); + fir::ExprKind::Assign(lhs, self.lower_expr(rhs)) + } + hir::ExprKind::AssignOp(op, lhs, rhs) => { + let idx = self.cfg.len(); + let is_array = matches!(lhs.ty, qsc_hir::ty::Ty::Array(..)); + let lhs = self.lower_expr(lhs); + if is_array { + // The left-hand side of an array append is not really an expression to be + // executed, so remove any added nodes from the CFG. + self.cfg.drain(idx..); + } + let idx = self.cfg.len(); + if matches!(op, hir::BinOp::AndL | hir::BinOp::OrL) { + // Put in a placeholder jump for what will be the short-circuit + self.cfg.push(CfgNode::Jump(0)); + } + let rhs = self.lower_expr(rhs); + match op { + hir::BinOp::AndL => { + self.cfg[idx] = CfgNode::JumpIfNot( + self.cfg + .len() + .try_into() + .expect("nodes should fit into u32"), + ); + } + hir::BinOp::OrL => { + self.cfg[idx] = CfgNode::JumpIf( + self.cfg + .len() + .try_into() + .expect("nodes should fit into u32"), + ); + } + _ => {} + } + fir::ExprKind::AssignOp(lower_binop(*op), lhs, rhs) } - hir::ExprKind::AssignOp(op, lhs, rhs) => fir::ExprKind::AssignOp( - lower_binop(*op), - self.lower_expr(lhs), - self.lower_expr(rhs), - ), hir::ExprKind::AssignField(container, field, replace) => { - let container = self.lower_expr(container); let field = lower_field(field); let replace = self.lower_expr(replace); + let container = self.lower_expr(container); fir::ExprKind::AssignField(container, field, replace) } - hir::ExprKind::AssignIndex(container, index, replace) => fir::ExprKind::AssignIndex( - self.lower_expr(container), - self.lower_expr(index), - self.lower_expr(replace), - ), + hir::ExprKind::AssignIndex(container, index, replace) => { + let index = self.lower_expr(index); + let replace = self.lower_expr(replace); + let idx = self.cfg.len(); + let container = self.lower_expr(container); + // The left-hand side of an array index assignment is not really an expression to be + // executed, so remove any added nodes from the CFG. + self.cfg.drain(idx..); + fir::ExprKind::AssignIndex(container, index, replace) + } hir::ExprKind::BinOp(op, lhs, rhs) => { - fir::ExprKind::BinOp(lower_binop(*op), self.lower_expr(lhs), self.lower_expr(rhs)) + let lhs = self.lower_expr(lhs); + let idx = self.cfg.len(); + if matches!(op, hir::BinOp::AndL | hir::BinOp::OrL) { + // Put in a placeholder jump for what will be the short-circuit + self.cfg.push(CfgNode::Jump(0)); + } + let rhs = self.lower_expr(rhs); + match op { + // If the operator is logical AND, update the placeholder to skip the + // right-hand side if the left-hand side is false + hir::BinOp::AndL => { + self.cfg[idx] = CfgNode::JumpIfNot( + self.cfg + .len() + .try_into() + .expect("nodes should fit into u32"), + ); + } + // If the operator is logical OR, update the placeholder to skip the + // right-hand side if the left-hand side is true + hir::BinOp::OrL => { + self.cfg[idx] = CfgNode::JumpIf( + self.cfg + .len() + .try_into() + .expect("nodes should fit into u32"), + ); + } + _ => {} + } + fir::ExprKind::BinOp(lower_binop(*op), lhs, rhs) } hir::ExprKind::Block(block) => fir::ExprKind::Block(self.lower_block(block)), hir::ExprKind::Call(callee, arg) => { @@ -315,11 +423,37 @@ impl Lowerer { let field = lower_field(field); fir::ExprKind::Field(container, field) } - hir::ExprKind::If(cond, if_true, if_false) => fir::ExprKind::If( - self.lower_expr(cond), - self.lower_expr(if_true), - if_false.as_ref().map(|e| self.lower_expr(e)), - ), + hir::ExprKind::If(cond, if_true, if_false) => { + let cond = self.lower_expr(cond); + let branch_idx = self.cfg.len(); + // Put a placeholder in the CFG for the jump past the true branch + self.cfg.push(CfgNode::Jump(0)); + let if_true = self.lower_expr(if_true); + let (if_false, else_idx) = if let Some(if_false) = if_false.as_ref() { + // Put a placeholder in the CFG for the jump past the false branch + let idx = self.cfg.len(); + self.cfg.push(CfgNode::Jump(0)); + let if_false = self.lower_expr(if_false); + // Update the placeholder to skip over the false branch + self.cfg[idx] = CfgNode::Jump( + self.cfg + .len() + .try_into() + .expect("nodes should fit into u32"), + ); + (Some(if_false), idx + 1) + } else { + // An if-expr without an else cannot return a value, so we need to + // insert a no-op to ensure a Unit value is returned for the expr. + let idx = self.cfg.len(); + self.cfg.push(CfgNode::Unit); + (None, idx) + }; + // Update the placeholder to skip the true branch if the condition is false + self.cfg[branch_idx] = + CfgNode::JumpUnless(else_idx.try_into().expect("nodes should fit into u32")); + fir::ExprKind::If(cond, if_true, if_false) + } hir::ExprKind::Index(container, index) => { fir::ExprKind::Index(self.lower_expr(container), self.lower_expr(index)) } @@ -329,7 +463,11 @@ impl Lowerer { step.as_ref().map(|s| self.lower_expr(s)), end.as_ref().map(|e| self.lower_expr(e)), ), - hir::ExprKind::Return(expr) => fir::ExprKind::Return(self.lower_expr(expr)), + hir::ExprKind::Return(expr) => { + let expr = self.lower_expr(expr); + self.cfg.push(CfgNode::Ret); + fir::ExprKind::Return(expr) + } hir::ExprKind::Tuple(items) => { fir::ExprKind::Tuple(items.iter().map(|i| self.lower_expr(i)).collect()) } @@ -337,7 +475,27 @@ impl Lowerer { fir::ExprKind::UnOp(lower_unop(*op), self.lower_expr(operand)) } hir::ExprKind::While(cond, body) => { - fir::ExprKind::While(self.lower_expr(cond), self.lower_block(body)) + let cond_idx = self.cfg.len(); + let cond = self.lower_expr(cond); + let idx = self.cfg.len(); + // Put a placeholder in the CFG for the jump past the loop + self.cfg.push(CfgNode::Jump(0)); + let body = self.lower_block(body); + self.cfg.push(CfgNode::Consume); + self.cfg.push(CfgNode::Jump( + cond_idx.try_into().expect("nodes should fit into u32"), + )); + // Update the placeholder to skip the loop if the condition is false + self.cfg[idx] = CfgNode::JumpUnless( + self.cfg + .len() + .try_into() + .expect("nodes should fit into u32"), + ); + // While-exprs never have a return value, so we need to insert a no-op to ensure + // a Unit value is returned for the expr. + self.cfg.push(CfgNode::Unit); + fir::ExprKind::While(cond, body) } hir::ExprKind::Closure(ids, id) => { let ids = ids.iter().map(|id| self.lower_local_id(*id)).collect(); @@ -349,15 +507,16 @@ impl Lowerer { .map(|c| self.lower_string_component(c)) .collect(), ), - hir::ExprKind::UpdateIndex(expr1, expr2, expr3) => fir::ExprKind::UpdateIndex( - self.lower_expr(expr1), - self.lower_expr(expr2), - self.lower_expr(expr3), - ), + hir::ExprKind::UpdateIndex(lhs, mid, rhs) => { + let mid = self.lower_expr(mid); + let rhs = self.lower_expr(rhs); + let lhs = self.lower_expr(lhs); + fir::ExprKind::UpdateIndex(lhs, mid, rhs) + } hir::ExprKind::UpdateField(record, field, replace) => { - let record = self.lower_expr(record); let field = lower_field(field); let replace = self.lower_expr(replace); + let record = self.lower_expr(record); fir::ExprKind::UpdateField(record, field, replace) } hir::ExprKind::Var(res, args) => { @@ -372,6 +531,18 @@ impl Lowerer { hir::ExprKind::Repeat(..) => panic!("repeat-loop should be eliminated by passes"), }; + match kind { + // These expressions express specific control flow that is handled above. + fir::ExprKind::BinOp(fir::BinOp::AndL | fir::BinOp::OrL, _, _) + | fir::ExprKind::Block(..) + | fir::ExprKind::If(..) + | fir::ExprKind::Return(..) + | fir::ExprKind::While(..) => {} + + // All other expressions should be added to the CFG. + _ => self.cfg.push(CfgNode::Expr(id)), + } + let expr = fir::Expr { id, span: expr.span, diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index 17fcf1a4cd..f0f9a3920c 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3,6 +3,8 @@ #![allow(clippy::needless_raw_string_hashes)] +use std::rc::Rc; + use crate::{ backend::{Backend, SparseSim}, debug::{map_hir_package_to_fir, Frame}, @@ -12,8 +14,8 @@ use crate::{ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_fir::fir; -use qsc_fir::fir::{ExprId, PackageId, PackageStoreLookup}; +use qsc_fir::fir::{self, CfgNode}; +use qsc_fir::fir::{PackageId, PackageStoreLookup}; use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; use qsc_passes::{run_core_passes, run_default_passes, PackageType}; @@ -22,15 +24,14 @@ use qsc_passes::{run_core_passes, run_default_passes, PackageType}; /// # Errors /// Returns the first error encountered during execution. pub(super) fn eval_expr( - expr: ExprId, + cfg: Rc<[CfgNode]>, sim: &mut impl Backend>, globals: &impl PackageStoreLookup, package: PackageId, out: &mut impl Receiver, ) -> Result)> { - let mut state = State::new(package, None); + let mut state = State::new(package, cfg, None); let mut env = Env::default(); - state.push_expr(expr); let StepResult::Return(value) = state.eval(globals, &mut env, sim, out, &[], StepAction::Continue)? else { @@ -75,7 +76,7 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) { ); assert!(pass_errors.is_empty(), "{pass_errors:?}"); let unit_fir = fir_lowerer.lower_package(&unit.package); - let entry = unit_fir.entry.expect("package should have entry"); + let entry = unit_fir.entry_cfg.clone(); let id = store.insert(unit); let mut fir_store = fir::PackageStore::new(); @@ -356,7 +357,7 @@ fn block_qubit_use_array_invalid_count_expr() { [ Frame { span: Span { - lo: 1573, + lo: 1568, hi: 1625, }, id: StoreItemId { @@ -463,6 +464,11 @@ fn binop_andl_false() { check_expr("", "true and false", &expect!["false"]); } +#[test] +fn binop_andl_shortcut() { + check_expr("", r#"false and (fail "Should Fail")"#, &expect!["false"]); +} + #[test] fn binop_andl_no_shortcut() { check_expr( @@ -2958,7 +2964,7 @@ fn call_adjoint_expr() { [ Frame { span: Span { - lo: 190, + lo: 185, hi: 214, }, id: StoreItemId { @@ -3022,7 +3028,7 @@ fn call_adjoint_adjoint_expr() { [ Frame { span: Span { - lo: 124, + lo: 119, hi: 145, }, id: StoreItemId { @@ -3081,7 +3087,7 @@ fn call_adjoint_self_expr() { [ Frame { span: Span { - lo: 116, + lo: 111, hi: 137, }, id: StoreItemId { diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs index 05c518fe31..11d36aca8f 100644 --- a/compiler/qsc_fir/src/fir.rs +++ b/compiler/qsc_fir/src/fir.rs @@ -548,12 +548,14 @@ pub trait PackageLookup { /// within the containing node. Node ids are used to identify nodes within /// the package and require mapping from the HIR node id to the new FIR node id. /// `PackageId`s and `LocalItemId`s are 1:1 from the HIR and are not remapped. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Package { /// The items in the package. pub items: IndexMap, /// The entry expression for an executable package. pub entry: Option, + /// The control flow graph for the entry expression in the package. + pub entry_cfg: Rc<[CfgNode]>, /// The blocks in the package. pub blocks: IndexMap, /// The expressions in the package. @@ -860,6 +862,8 @@ pub struct SpecDecl { pub block: BlockId, /// The input of the specialization. pub input: Option, + /// The flattened control flow graph for the execution of the specialization. + pub cfg: Rc<[CfgNode]>, } impl Display for SpecDecl { @@ -872,6 +876,34 @@ impl Display for SpecDecl { } } +#[derive(Copy, Clone, Debug, PartialEq)] +/// A node within the control flow graph. +pub enum CfgNode { + /// A binding of a value to a variable. + Bind(PatId), + /// An expression to execute. + Expr(ExprId), + /// A statement to track for debugging. + Stmt(StmtId), + /// An unconditional jump with to given location. + Jump(u32), + /// A conditional jump with to given location, where the jump is only taken if the condition is + /// true, and the value is not consumed. + JumpIf(u32), + /// A conditional jump with to given location, where the jump is only taken if the condition is + /// false, and the value is not consumed. + JumpIfNot(u32), + /// A conditional jump with to given location, where the jump is only taken if the condition is + /// false, and the value is consumed. + JumpUnless(u32), + /// An indication that the value on the value stack should be consumed. + Consume, + /// A no-op Unit node that tells execution to insert a unit value into the value stack. + Unit, + /// The end of the control flow graph. + Ret, +} + /// A sequenced block of statements. #[derive(Clone, Debug, PartialEq)] pub struct Block { diff --git a/wasm/src/debug_service.rs b/wasm/src/debug_service.rs index 2fd30c0f38..3e6e26a836 100644 --- a/wasm/src/debug_service.rs +++ b/wasm/src/debug_service.rs @@ -42,10 +42,7 @@ impl DebugService { match Debugger::new(source_map, target.into(), Encoding::Utf16, features) { Ok(debugger) => { self.debugger = Some(debugger); - match self.debugger_mut().set_entry() { - Ok(()) => String::new(), - Err(e) => render_errors(e), - } + String::new() } Err(e) => render_errors(e), } From a75e5a2b7cee85e75737bb72df53332cb3127314 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 12 Mar 2024 19:13:57 -0700 Subject: [PATCH 04/16] Change value stack patterns to avoid pushing unused unit values --- compiler/qsc_eval/src/lib.rs | 169 ++++++++++++++++----------------- compiler/qsc_eval/src/lower.rs | 106 +++++++++++++-------- compiler/qsc_eval/src/tests.rs | 10 ++ compiler/qsc_fir/src/fir.rs | 13 +-- 4 files changed, 163 insertions(+), 135 deletions(-) diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 3ebb8aff21..18cadacabb 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -360,7 +360,8 @@ pub struct State { cfg_stack: Vec>, idx: u32, idx_stack: Vec, - vals: Vec>, + curr_val: Option, + val_stack: Vec>, package: PackageId, call_stack: CallStack, current_span: Span, @@ -378,7 +379,8 @@ impl State { cfg_stack: vec![cfg], idx: 0, idx_stack: Vec::new(), - vals: vec![Vec::new()], + curr_val: None, + val_stack: vec![Vec::new()], package, call_stack: CallStack::default(), current_span: Span::default(), @@ -394,7 +396,7 @@ impl State { functor, }); self.cfg_stack.push(cfg); - self.vals.push(Vec::new()); + self.val_stack.push(Vec::new()); self.idx_stack.push(self.idx); self.idx = 0; self.package = id.package; @@ -403,9 +405,7 @@ impl State { fn leave_frame(&mut self) { if let Some(frame) = self.call_stack.pop_frame() { self.package = frame.caller; - let frame_val = self.pop_val(); - self.vals.pop(); - self.push_val(frame_val); + self.val_stack.pop(); self.idx = self .idx_stack .pop() @@ -418,8 +418,16 @@ impl State { env.push_scope(self.call_stack.len()); } + fn take_curr_val(&mut self) -> Value { + self.curr_val.take().expect("value should be present") + } + + fn set_curr_val(&mut self, val: Value) { + self.curr_val = Some(val); + } + fn pop_val(&mut self) -> Value { - self.vals + self.val_stack .last_mut() .expect("should have at least one value frame") .pop() @@ -428,14 +436,15 @@ impl State { fn pop_vals(&mut self, len: usize) -> Vec { let last = self - .vals + .val_stack .last_mut() .expect("should have at least one value frame"); last.drain(last.len() - len..).collect() } - fn push_val(&mut self, val: Value) { - self.vals + fn push_val(&mut self) { + let val = self.take_curr_val(); + self.val_stack .last_mut() .expect("should have at least one value frame") .push(val); @@ -517,9 +526,8 @@ impl State { continue; } CfgNode::JumpIf(idx) => { - let cond = self.pop_val(); - if cond.unwrap_bool() { - self.push_val(Value::Bool(true)); + let cond = self.curr_val == Some(Value::Bool(true)); + if cond { self.idx = *idx; } else { self.idx += 1; @@ -527,32 +535,22 @@ impl State { continue; } CfgNode::JumpIfNot(idx) => { - let cond = self.pop_val(); - if cond.unwrap_bool() { - self.idx += 1; - } else { - self.push_val(Value::Bool(false)); - self.idx = *idx; - } - continue; - } - CfgNode::JumpUnless(idx) => { - let cond = self.pop_val(); - if cond.unwrap_bool() { + let cond = self.curr_val == Some(Value::Bool(true)); + if cond { self.idx += 1; } else { self.idx = *idx; } continue; } - CfgNode::Consume => { - self.pop_val(); + CfgNode::Store => { + self.push_val(); self.idx += 1; continue; } CfgNode::Unit => { self.idx += 1; - self.push_val(Value::unit()); + self.set_curr_val(Value::unit()); continue; } CfgNode::Ret => { @@ -576,11 +574,7 @@ impl State { // Some executions don't have any statements to execute, // such as a fragment that has only item definitions. // In that case, the values are empty and the result is unit. - self.vals - .last_mut() - .expect("should have at least one value frame") - .pop() - .unwrap_or_else(Value::unit) + self.curr_val.take().unwrap_or_else(Value::unit) } #[allow(clippy::similar_names)] @@ -608,9 +602,10 @@ impl State { self.eval_array_append_in_place(env, globals, *lhs)?; return Ok(()); } - let rhs_val = self.pop_val(); + let rhs_val = self.take_curr_val(); self.eval_expr(env, sim, globals, out, *lhs)?; - self.push_val(rhs_val); + self.push_val(); + self.set_curr_val(rhs_val); } self.eval_binop(*op, rhs_span)?; self.eval_assign(env, globals, *lhs)?; @@ -627,6 +622,7 @@ impl State { self.eval_update_index_in_place(env, globals, *lhs, mid_span)?; return Ok(()); } + self.push_val(); self.eval_expr(env, sim, globals, out, *lhs)?; self.eval_update_index(mid_span)?; self.eval_assign(env, globals, *lhs)?; @@ -643,11 +639,11 @@ impl State { } ExprKind::Closure(args, callable) => { let closure = resolve_closure(env, self.package, expr.span, args, *callable)?; - self.push_val(closure); + self.set_curr_val(closure); } ExprKind::Fail(..) => { return Err(Error::UserFail( - self.pop_val().unwrap_string().to_string(), + self.take_curr_val().unwrap_string().to_string(), self.to_global_span(expr.span), )); } @@ -660,7 +656,9 @@ impl State { let rhs_span = globals.get_expr((self.package, *rhs).into()).span; self.eval_index(rhs_span)?; } - ExprKind::Lit(lit) => self.push_val(lit_to_val(lit)), + ExprKind::Lit(lit) => { + self.set_curr_val(lit_to_val(lit)); + } ExprKind::Range(start, step, end) => { self.eval_range(start.is_some(), step.is_some(), end.is_some()); } @@ -676,7 +674,7 @@ impl State { self.eval_update_field(field.clone()); } ExprKind::Var(res, _) => { - self.push_val(resolve_binding(env, self.package, *res, expr.span)?); + self.set_curr_val(resolve_binding(env, self.package, *res, expr.span)?); } ExprKind::While(..) => { panic!("while expr should be handled by control flow") @@ -688,7 +686,7 @@ impl State { fn collect_string(&mut self, components: &[StringComponent]) { if let [StringComponent::Lit(str)] = components { - self.push_val(Value::String(Rc::clone(str))); + self.set_curr_val(Value::String(Rc::clone(str))); return; } @@ -704,12 +702,12 @@ impl State { } } } - self.push_val(Value::String(Rc::from(string))); + self.set_curr_val(Value::String(Rc::from(string))); } fn eval_arr(&mut self, len: usize) { let arr = self.pop_vals(len); - self.push_val(Value::Array(arr.into())); + self.set_curr_val(Value::Array(arr.into())); } fn eval_array_append_in_place( @@ -719,7 +717,7 @@ impl State { lhs: ExprId, ) -> Result<(), Error> { let lhs = globals.get_expr((self.package, lhs).into()); - let rhs = self.pop_val(); + let rhs = self.take_curr_val(); match (&lhs.kind, rhs) { (&ExprKind::Var(Res::Local(id), _), rhs) => match env.get_mut(id) { Some(var) => { @@ -729,12 +727,11 @@ impl State { }, _ => unreachable!("unassignable array update pattern should be disallowed by compiler"), } - self.push_val(Value::unit()); Ok(()) } fn eval_arr_repeat(&mut self, span: Span) -> Result<(), Error> { - let size_val = self.pop_val().unwrap_int(); + let size_val = self.take_curr_val().unwrap_int(); let item_val = self.pop_val(); let s = match size_val.try_into() { Ok(i) => Ok(i), @@ -743,7 +740,7 @@ impl State { self.to_global_span(span), )), }?; - self.push_val(Value::Array(vec![item_val; s].into())); + self.set_curr_val(Value::Array(vec![item_val; s].into())); Ok(()) } @@ -753,15 +750,13 @@ impl State { globals: &impl PackageStoreLookup, lhs: ExprId, ) -> Result<(), Error> { - let rhs = self.pop_val(); - self.push_val(Value::unit()); + let rhs = self.take_curr_val(); self.update_binding(env, globals, lhs, rhs) } fn eval_bind(&mut self, env: &mut Env, globals: &impl PackageStoreLookup, pat: PatId) { - let val = self.pop_val(); + let val = self.take_curr_val(); self.bind_value(env, globals, pat, val); - self.push_val(Value::unit()); } fn eval_binop(&mut self, op: BinOp, span: Span) -> Result<(), Error> { @@ -770,9 +765,9 @@ impl State { BinOp::AndB => self.eval_binop_simple(eval_binop_andb), BinOp::Div => self.eval_binop_with_error(span, eval_binop_div)?, BinOp::Eq => { - let rhs_val = self.pop_val(); + let rhs_val = self.take_curr_val(); let lhs_val = self.pop_val(); - self.push_val(Value::Bool(lhs_val == rhs_val)); + self.set_curr_val(Value::Bool(lhs_val == rhs_val)); } BinOp::Exp => self.eval_binop_with_error(span, eval_binop_exp)?, BinOp::Gt => self.eval_binop_simple(eval_binop_gt), @@ -782,9 +777,9 @@ impl State { BinOp::Mod => self.eval_binop_with_error(span, eval_binop_mod)?, BinOp::Mul => self.eval_binop_simple(eval_binop_mul), BinOp::Neq => { - let rhs_val = self.pop_val(); + let rhs_val = self.take_curr_val(); let lhs_val = self.pop_val(); - self.push_val(Value::Bool(lhs_val != rhs_val)); + self.set_curr_val(Value::Bool(lhs_val != rhs_val)); } BinOp::OrB => self.eval_binop_simple(eval_binop_orb), BinOp::Shl => self.eval_binop_with_error(span, eval_binop_shl)?, @@ -799,9 +794,9 @@ impl State { } fn eval_binop_simple(&mut self, binop_func: impl FnOnce(Value, Value) -> Value) { - let rhs_val = self.pop_val(); + let rhs_val = self.take_curr_val(); let lhs_val = self.pop_val(); - self.push_val(binop_func(lhs_val, rhs_val)); + self.set_curr_val(binop_func(lhs_val, rhs_val)); } fn eval_binop_with_error( @@ -810,9 +805,9 @@ impl State { binop_func: impl FnOnce(Value, Value, PackageSpan) -> Result, ) -> Result<(), Error> { let span = self.to_global_span(span); - let rhs_val = self.pop_val(); + let rhs_val = self.take_curr_val(); let lhs_val = self.pop_val(); - self.push_val(binop_func(lhs_val, rhs_val, span)?); + self.set_curr_val(binop_func(lhs_val, rhs_val, span)?); Ok(()) } @@ -825,7 +820,7 @@ impl State { arg_span: Span, out: &mut impl Receiver, ) -> Result<(), Error> { - let arg = self.pop_val(); + let arg = self.take_curr_val(); let (callee_id, functor, fixed_args) = match self.pop_val() { Value::Closure(inner) => (inner.id, inner.functor, Some(inner.fixed_args)), Value::Global(id, functor) => (id, functor, None), @@ -837,7 +832,7 @@ impl State { let callee = match globals.get_global(callee_id) { Some(Global::Callable(callable)) => callable, Some(Global::Udt) => { - self.push_val(arg); + self.set_curr_val(arg); return Ok(()); } None => return Err(Error::UnboundName(self.to_global_span(callable_span))), @@ -866,7 +861,7 @@ impl State { callee_span, )); } - self.push_val(val); + self.set_curr_val(val); self.leave_frame(); Ok(()) } @@ -896,7 +891,7 @@ impl State { } fn eval_field(&mut self, field: Field) { - let record = self.pop_val(); + let record = self.take_curr_val(); let val = match (record, field) { (Value::Range(inner), Field::Prim(PrimField::Start)) => Value::Int( inner @@ -914,16 +909,16 @@ impl State { } _ => panic!("invalid field access"), }; - self.push_val(val); + self.set_curr_val(val); } fn eval_index(&mut self, span: Span) -> Result<(), Error> { - let index_val = self.pop_val(); + let index_val = self.take_curr_val(); let arr = self.pop_val().unwrap_array(); match &index_val { - Value::Int(i) => self.push_val(index_array(&arr, *i, self.to_global_span(span))?), + Value::Int(i) => self.set_curr_val(index_array(&arr, *i, self.to_global_span(span))?), Value::Range(inner) => { - self.push_val(slice_array( + self.set_curr_val(slice_array( &arr, inner.start, inner.step, @@ -938,7 +933,7 @@ impl State { fn eval_range(&mut self, has_start: bool, has_step: bool, has_end: bool) { let end = if has_end { - Some(self.pop_val().unwrap_int()) + Some(self.take_curr_val().unwrap_int()) } else { None }; @@ -952,11 +947,11 @@ impl State { } else { None }; - self.push_val(Value::Range(val::Range { start, step, end }.into())); + self.set_curr_val(Value::Range(val::Range { start, step, end }.into())); } fn eval_update_index(&mut self, span: Span) -> Result<(), Error> { - let values = self.pop_val().unwrap_array(); + let values = self.take_curr_val().unwrap_array(); let update = self.pop_val(); let index = self.pop_val(); let span = self.to_global_span(span); @@ -992,7 +987,7 @@ impl State { } None => return Err(Error::IndexOutOfRange(index, span)), } - self.push_val(Value::Array(values.into())); + self.set_curr_val(Value::Array(values.into())); Ok(()) } @@ -1017,7 +1012,7 @@ impl State { None => return Err(Error::IndexOutOfRange(idx, span)), } } - self.push_val(Value::Array(values.into())); + self.set_curr_val(Value::Array(values.into())); Ok(()) } @@ -1028,7 +1023,7 @@ impl State { lhs: ExprId, span: Span, ) -> Result<(), Error> { - let update = self.pop_val(); + let update = self.take_curr_val(); let index = self.pop_val(); let span = self.to_global_span(span); match index { @@ -1048,15 +1043,15 @@ impl State { fn eval_tup(&mut self, len: usize) { let tup = self.pop_vals(len); - self.push_val(Value::Tuple(tup.into())); + self.set_curr_val(Value::Tuple(tup.into())); } fn eval_unop(&mut self, op: UnOp) { - let val = self.pop_val(); + let val = self.take_curr_val(); match op { UnOp::Functor(functor) => match val { Value::Closure(inner) => { - self.push_val(Value::Closure( + self.set_curr_val(Value::Closure( val::Closure { functor: update_functor_app(functor, inner.functor), ..*inner @@ -1065,35 +1060,35 @@ impl State { )); } Value::Global(id, app) => { - self.push_val(Value::Global(id, update_functor_app(functor, app))); + self.set_curr_val(Value::Global(id, update_functor_app(functor, app))); } _ => panic!("value should be callable"), }, UnOp::Neg => match val { - Value::BigInt(v) => self.push_val(Value::BigInt(v.neg())), - Value::Double(v) => self.push_val(Value::Double(v.neg())), - Value::Int(v) => self.push_val(Value::Int(v.wrapping_neg())), + Value::BigInt(v) => self.set_curr_val(Value::BigInt(v.neg())), + Value::Double(v) => self.set_curr_val(Value::Double(v.neg())), + Value::Int(v) => self.set_curr_val(Value::Int(v.wrapping_neg())), _ => panic!("value should be number"), }, UnOp::NotB => match val { - Value::Int(v) => self.push_val(Value::Int(!v)), - Value::BigInt(v) => self.push_val(Value::BigInt(!v)), + Value::Int(v) => self.set_curr_val(Value::Int(!v)), + Value::BigInt(v) => self.set_curr_val(Value::BigInt(!v)), _ => panic!("value should be Int or BigInt"), }, UnOp::NotL => match val { - Value::Bool(b) => self.push_val(Value::Bool(!b)), + Value::Bool(b) => self.set_curr_val(Value::Bool(!b)), _ => panic!("value should be bool"), }, UnOp::Pos => match val { - Value::BigInt(_) | Value::Int(_) | Value::Double(_) => self.push_val(val), + Value::BigInt(_) | Value::Int(_) | Value::Double(_) => self.set_curr_val(val), _ => panic!("value should be number"), }, - UnOp::Unwrap => self.push_val(val), + UnOp::Unwrap => self.set_curr_val(val), } } fn eval_update_field(&mut self, field: Field) { - let record = self.pop_val(); + let record = self.take_curr_val(); let value = self.pop_val(); let update = match (record, field) { (Value::Range(mut inner), Field::Prim(PrimField::Start)) => { @@ -1112,7 +1107,7 @@ impl State { .expect("field path should be valid"), _ => panic!("invalid field access"), }; - self.push_val(update); + self.set_curr_val(update); } fn bind_value(&self, env: &mut Env, globals: &impl PackageStoreLookup, pat: PatId, val: Value) { @@ -1187,7 +1182,6 @@ impl State { }, _ => unreachable!("unassignable array update pattern should be disallowed by compiler"), } - self.push_val(Value::unit()); Ok(()) } @@ -1230,7 +1224,6 @@ impl State { }, _ => unreachable!("unassignable array update pattern should be disallowed by compiler"), } - self.push_val(Value::unit()); Ok(()) } diff --git a/compiler/qsc_eval/src/lower.rs b/compiler/qsc_eval/src/lower.rs index 3fda6d580d..949fa86f36 100644 --- a/compiler/qsc_eval/src/lower.rs +++ b/compiler/qsc_eval/src/lower.rs @@ -247,31 +247,19 @@ impl Lowerer { fn lower_block(&mut self, block: &hir::Block) -> BlockId { let id = self.assigner.next_block(); - let len = self.cfg.len(); + let set_unit = block.stmts.is_empty() + || !matches!( + block.stmts.last().expect("block should be non-empty").kind, + hir::StmtKind::Expr(..) + ); let block = fir::Block { id, span: block.span, ty: self.lower_ty(&block.ty), - stmts: block - .stmts - .iter() - .map(|s| { - let is_item = matches!(s.kind, hir::StmtKind::Item(_)); - let s = self.lower_stmt(s); - if !is_item { - self.cfg.push(CfgNode::Consume); - } - s - }) - .collect(), + stmts: block.stmts.iter().map(|s| self.lower_stmt(s)).collect(), }; - if self.cfg.len() == len { - // There were no statements in the block, so we need to insert a no-op to ensure a - // Unit value is returned for the expr. + if set_unit { self.cfg.push(CfgNode::Unit); - } else { - // Pop the last consume so the final statement can be an implicit return from the block. - self.cfg.pop(); } self.blocks.insert(id, block); id @@ -294,8 +282,6 @@ impl Lowerer { } hir::StmtKind::Semi(expr) => { let expr = self.lower_expr(expr); - self.cfg.push(CfgNode::Consume); - self.cfg.push(CfgNode::Unit); fir::StmtKind::Semi(expr) } }; @@ -314,11 +300,21 @@ impl Lowerer { let ty = self.lower_ty(&expr.ty); let kind = match &expr.kind { - hir::ExprKind::Array(items) => { - fir::ExprKind::Array(items.iter().map(|i| self.lower_expr(i)).collect()) - } + hir::ExprKind::Array(items) => fir::ExprKind::Array( + items + .iter() + .map(|i| { + let i = self.lower_expr(i); + self.cfg.push(CfgNode::Store); + i + }) + .collect(), + ), hir::ExprKind::ArrayRepeat(value, size) => { - fir::ExprKind::ArrayRepeat(self.lower_expr(value), self.lower_expr(size)) + let value = self.lower_expr(value); + self.cfg.push(CfgNode::Store); + let size = self.lower_expr(size); + fir::ExprKind::ArrayRepeat(value, size) } hir::ExprKind::Assign(lhs, rhs) => { let idx = self.cfg.len(); @@ -341,6 +337,8 @@ impl Lowerer { if matches!(op, hir::BinOp::AndL | hir::BinOp::OrL) { // Put in a placeholder jump for what will be the short-circuit self.cfg.push(CfgNode::Jump(0)); + } else if !is_array { + self.cfg.push(CfgNode::Store); } let rhs = self.lower_expr(rhs); match op { @@ -367,11 +365,13 @@ impl Lowerer { hir::ExprKind::AssignField(container, field, replace) => { let field = lower_field(field); let replace = self.lower_expr(replace); + self.cfg.push(CfgNode::Store); let container = self.lower_expr(container); fir::ExprKind::AssignField(container, field, replace) } hir::ExprKind::AssignIndex(container, index, replace) => { let index = self.lower_expr(index); + self.cfg.push(CfgNode::Store); let replace = self.lower_expr(replace); let idx = self.cfg.len(); let container = self.lower_expr(container); @@ -386,6 +386,8 @@ impl Lowerer { if matches!(op, hir::BinOp::AndL | hir::BinOp::OrL) { // Put in a placeholder jump for what will be the short-circuit self.cfg.push(CfgNode::Jump(0)); + } else { + self.cfg.push(CfgNode::Store); } let rhs = self.lower_expr(rhs); match op { @@ -415,7 +417,10 @@ impl Lowerer { } hir::ExprKind::Block(block) => fir::ExprKind::Block(self.lower_block(block)), hir::ExprKind::Call(callee, arg) => { - fir::ExprKind::Call(self.lower_expr(callee), self.lower_expr(arg)) + let call = self.lower_expr(callee); + self.cfg.push(CfgNode::Store); + let arg = self.lower_expr(arg); + fir::ExprKind::Call(call, arg) } hir::ExprKind::Fail(message) => fir::ExprKind::Fail(self.lower_expr(message)), hir::ExprKind::Field(container, field) => { @@ -451,26 +456,43 @@ impl Lowerer { }; // Update the placeholder to skip the true branch if the condition is false self.cfg[branch_idx] = - CfgNode::JumpUnless(else_idx.try_into().expect("nodes should fit into u32")); + CfgNode::JumpIfNot(else_idx.try_into().expect("nodes should fit into u32")); fir::ExprKind::If(cond, if_true, if_false) } hir::ExprKind::Index(container, index) => { - fir::ExprKind::Index(self.lower_expr(container), self.lower_expr(index)) + let container = self.lower_expr(container); + self.cfg.push(CfgNode::Store); + let index = self.lower_expr(index); + fir::ExprKind::Index(container, index) } hir::ExprKind::Lit(lit) => lower_lit(lit), - hir::ExprKind::Range(start, step, end) => fir::ExprKind::Range( - start.as_ref().map(|s| self.lower_expr(s)), - step.as_ref().map(|s| self.lower_expr(s)), - end.as_ref().map(|e| self.lower_expr(e)), - ), + hir::ExprKind::Range(start, step, end) => { + let start = start.as_ref().map(|s| self.lower_expr(s)); + if start.is_some() { + self.cfg.push(CfgNode::Store); + } + let step = step.as_ref().map(|s| self.lower_expr(s)); + if step.is_some() { + self.cfg.push(CfgNode::Store); + } + let end = end.as_ref().map(|e| self.lower_expr(e)); + fir::ExprKind::Range(start, step, end) + } hir::ExprKind::Return(expr) => { let expr = self.lower_expr(expr); self.cfg.push(CfgNode::Ret); fir::ExprKind::Return(expr) } - hir::ExprKind::Tuple(items) => { - fir::ExprKind::Tuple(items.iter().map(|i| self.lower_expr(i)).collect()) - } + hir::ExprKind::Tuple(items) => fir::ExprKind::Tuple( + items + .iter() + .map(|i| { + let i = self.lower_expr(i); + self.cfg.push(CfgNode::Store); + i + }) + .collect(), + ), hir::ExprKind::UnOp(op, operand) => { fir::ExprKind::UnOp(lower_unop(*op), self.lower_expr(operand)) } @@ -481,12 +503,11 @@ impl Lowerer { // Put a placeholder in the CFG for the jump past the loop self.cfg.push(CfgNode::Jump(0)); let body = self.lower_block(body); - self.cfg.push(CfgNode::Consume); self.cfg.push(CfgNode::Jump( cond_idx.try_into().expect("nodes should fit into u32"), )); // Update the placeholder to skip the loop if the condition is false - self.cfg[idx] = CfgNode::JumpUnless( + self.cfg[idx] = CfgNode::JumpIfNot( self.cfg .len() .try_into() @@ -509,13 +530,16 @@ impl Lowerer { ), hir::ExprKind::UpdateIndex(lhs, mid, rhs) => { let mid = self.lower_expr(mid); + self.cfg.push(CfgNode::Store); let rhs = self.lower_expr(rhs); + self.cfg.push(CfgNode::Store); let lhs = self.lower_expr(lhs); fir::ExprKind::UpdateIndex(lhs, mid, rhs) } hir::ExprKind::UpdateField(record, field, replace) => { let field = lower_field(field); let replace = self.lower_expr(replace); + self.cfg.push(CfgNode::Store); let record = self.lower_expr(record); fir::ExprKind::UpdateField(record, field, replace) } @@ -555,7 +579,11 @@ impl Lowerer { fn lower_string_component(&mut self, component: &hir::StringComponent) -> fir::StringComponent { match component { - hir::StringComponent::Expr(expr) => fir::StringComponent::Expr(self.lower_expr(expr)), + hir::StringComponent::Expr(expr) => { + let expr = self.lower_expr(expr); + self.cfg.push(CfgNode::Store); + fir::StringComponent::Expr(expr) + } hir::StringComponent::Lit(str) => fir::StringComponent::Lit(Rc::clone(str)), } } diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index f0f9a3920c..ff1da7ec6e 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -406,6 +406,16 @@ fn block_qubit_use_nested_tuple_expr() { ); } +#[test] +fn block_with_no_stmts_is_unit() { + check_expr("", "{}", &expect!["()"]); +} + +#[test] +fn block_with_semi_is_unit() { + check_expr("", "{4;}", &expect!["()"]); +} + #[test] fn binop_add_array() { check_expr("", "[1, 2] + [3, 4]", &expect!["[1, 2, 3, 4]"]); diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs index 11d36aca8f..3d76537292 100644 --- a/compiler/qsc_fir/src/fir.rs +++ b/compiler/qsc_fir/src/fir.rs @@ -888,17 +888,14 @@ pub enum CfgNode { /// An unconditional jump with to given location. Jump(u32), /// A conditional jump with to given location, where the jump is only taken if the condition is - /// true, and the value is not consumed. + /// true. JumpIf(u32), /// A conditional jump with to given location, where the jump is only taken if the condition is - /// false, and the value is not consumed. + /// false. JumpIfNot(u32), - /// A conditional jump with to given location, where the jump is only taken if the condition is - /// false, and the value is consumed. - JumpUnless(u32), - /// An indication that the value on the value stack should be consumed. - Consume, - /// A no-op Unit node that tells execution to insert a unit value into the value stack. + /// An indication that the current accumulated result value should be stored into the value stack. + Store, + /// A no-op Unit node that tells execution to insert a unit value into the current accumulated result. Unit, /// The end of the control flow graph. Ret, From da0a598353cf1766ac1ed067faf9ae8f7ef5fb47 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 13 Mar 2024 14:36:32 -0700 Subject: [PATCH 05/16] Fix spans in debugger tests --- vscode/test/suites/debugger/debugger.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vscode/test/suites/debugger/debugger.test.ts b/vscode/test/suites/debugger/debugger.test.ts index 2a832845b6..848512bc53 100644 --- a/vscode/test/suites/debugger/debugger.test.ts +++ b/vscode/test/suites/debugger/debugger.test.ts @@ -246,7 +246,7 @@ suite("Q# Debugger Tests", function suite() { adapterData: "qsharp-adapter-data", }, line: 5, - column: 12, + column: 9, name: "Foo ", endLine: 5, endColumn: 14, @@ -314,7 +314,7 @@ suite("Q# Debugger Tests", function suite() { adapterData: "qsharp-adapter-data", }, line: 5, - column: 12, + column: 9, name: "Foo ", endLine: 5, endColumn: 14, @@ -390,10 +390,10 @@ suite("Q# Debugger Tests", function suite() { adapterData: "qsharp-adapter-data", }, line: 8, - column: 11, + column: 9, name: "Foo ", endLine: 8, - endColumn: 12, + endColumn: 13, }, { id: 0, line: 0, column: 0, name: "entry", source: undefined }, ]); From 8550af9c6db7db93fcb016e1e2ad9e580f549591 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Fri, 15 Mar 2024 10:15:08 -0700 Subject: [PATCH 06/16] Support partial evaluation of sub graphs, add tests --- compiler/qsc_eval/src/intrinsic/tests.rs | 6 +- compiler/qsc_eval/src/lib.rs | 56 ++- compiler/qsc_eval/src/lower.rs | 4 + compiler/qsc_eval/src/tests.rs | 580 ++++++++++++++++++++++- compiler/qsc_fir/src/fir.rs | 5 + 5 files changed, 627 insertions(+), 24 deletions(-) diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 82a4908e80..1f633beb34 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -7,7 +7,8 @@ use std::f64::consts; use crate::backend::{Backend, SparseSim}; use crate::debug::map_hir_package_to_fir; -use crate::tests::eval_expr; +use crate::tests::eval_cfg; +use crate::Env; use crate::{ output::{GenericReceiver, Receiver}, val::Value, @@ -192,11 +193,12 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result, range: ops::Range) -> Rc<[CfgNode]> { + let start: u32 = range + .start + .try_into() + .expect("cfg ranges should fit into u32"); + cfg[range] + .iter() + .map(|node| match node { + CfgNode::Jump(idx) => CfgNode::Jump(idx - start), + CfgNode::JumpIf(idx) => CfgNode::JumpIf(idx - start), + CfgNode::JumpIfNot(idx) => CfgNode::JumpIfNot(idx - start), + _ => *node, + }) + .collect::>() + .into() +} + /// Evaluates the given code with the given context. /// # Errors /// Returns the first error encountered during execution. @@ -480,24 +501,20 @@ impl State { let cfg = self .cfg_stack .last() - .expect("should have at least one stack frame") - .clone(); - let res = match cfg - .get(self.idx as usize) - .expect("should have next node in CFG") - { - CfgNode::Bind(pat) => { + .expect("should have at least one stack frame"); + let res = match cfg.get(self.idx as usize) { + Some(CfgNode::Bind(pat)) => { self.idx += 1; self.eval_bind(env, globals, *pat); continue; } - CfgNode::Expr(expr) => { + Some(CfgNode::Expr(expr)) => { self.idx += 1; self.eval_expr(env, sim, globals, out, *expr) .map_err(|e| (e, self.get_stack_frames()))?; continue; } - CfgNode::Stmt(stmt) => { + Some(CfgNode::Stmt(stmt)) => { self.idx += 1; self.current_span = globals.get_stmt((self.package, *stmt).into()).span; @@ -521,11 +538,11 @@ impl State { } } } - CfgNode::Jump(idx) => { + Some(CfgNode::Jump(idx)) => { self.idx = *idx; continue; } - CfgNode::JumpIf(idx) => { + Some(CfgNode::JumpIf(idx)) => { let cond = self.curr_val == Some(Value::Bool(true)); if cond { self.idx = *idx; @@ -534,7 +551,7 @@ impl State { } continue; } - CfgNode::JumpIfNot(idx) => { + Some(CfgNode::JumpIfNot(idx)) => { let cond = self.curr_val == Some(Value::Bool(true)); if cond { self.idx += 1; @@ -543,21 +560,30 @@ impl State { } continue; } - CfgNode::Store => { + Some(CfgNode::Store) => { self.push_val(); self.idx += 1; continue; } - CfgNode::Unit => { + Some(CfgNode::Unit) => { self.idx += 1; self.set_curr_val(Value::unit()); continue; } - CfgNode::Ret => { + Some(CfgNode::Ret) => { self.leave_frame(); env.leave_scope(); continue; } + None => { + // We have reached the end of the current cfg without reaching an explicit return node, + // usually indicating the partial execution of a single sub-expression. + // This means we should leave the current frame but not the current environment scope, + // so bound variables are still accessible after completion. + self.leave_frame(); + assert!(self.cfg_stack.is_empty()); + continue; + } }; if let StepResult::Return(_) = res { diff --git a/compiler/qsc_eval/src/lower.rs b/compiler/qsc_eval/src/lower.rs index 949fa86f36..73350f4838 100644 --- a/compiler/qsc_eval/src/lower.rs +++ b/compiler/qsc_eval/src/lower.rs @@ -267,6 +267,7 @@ impl Lowerer { fn lower_stmt(&mut self, stmt: &hir::Stmt) -> fir::StmtId { let id = self.assigner.next_stmt(); + let cfg_start_idx = self.cfg.len(); self.cfg.push(CfgNode::Stmt(id)); let kind = match &stmt.kind { hir::StmtKind::Expr(expr) => fir::StmtKind::Expr(self.lower_expr(expr)), @@ -289,6 +290,7 @@ impl Lowerer { id, span: stmt.span, kind, + cfg_range: cfg_start_idx..self.cfg.len(), }; self.stmts.insert(id, stmt); id @@ -297,6 +299,7 @@ impl Lowerer { #[allow(clippy::too_many_lines)] fn lower_expr(&mut self, expr: &hir::Expr) -> ExprId { let id = self.assigner.next_expr(); + let cfg_start_idx = self.cfg.len(); let ty = self.lower_ty(&expr.ty); let kind = match &expr.kind { @@ -572,6 +575,7 @@ impl Lowerer { span: expr.span, ty, kind, + cfg_range: cfg_start_idx..self.cfg.len(), }; self.exprs.insert(id, expr); id diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index ff1da7ec6e..cc6dea507c 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3,37 +3,38 @@ #![allow(clippy::needless_raw_string_hashes)] +use core::panic; use std::rc::Rc; use crate::{ backend::{Backend, SparseSim}, debug::{map_hir_package_to_fir, Frame}, output::{GenericReceiver, Receiver}, - val, Env, Error, State, StepAction, StepResult, Value, + sub_cfg, val, Env, Error, State, StepAction, StepResult, Value, }; use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_fir::fir::{self, CfgNode}; +use qsc_fir::fir::{self, CfgNode, StmtId}; use qsc_fir::fir::{PackageId, PackageStoreLookup}; use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; use qsc_passes::{run_core_passes, run_default_passes, PackageType}; -/// Evaluates the given expression with the given context. +/// Evaluates the given control flow graph with the given context. /// Creates a new environment and simulator. /// # Errors /// Returns the first error encountered during execution. -pub(super) fn eval_expr( +pub(super) fn eval_cfg( cfg: Rc<[CfgNode]>, sim: &mut impl Backend>, globals: &impl PackageStoreLookup, package: PackageId, + env: &mut Env, out: &mut impl Receiver, ) -> Result)> { let mut state = State::new(package, cfg, None); - let mut env = Env::default(); let StepResult::Return(value) = - state.eval(globals, &mut env, sim, out, &[], StepAction::Continue)? + state.eval(globals, env, sim, out, &[], StepAction::Continue)? else { unreachable!("eval_expr should always return a value"); }; @@ -88,11 +89,12 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) { fir_store.insert(map_hir_package_to_fir(id), unit_fir); let mut out = Vec::new(); - match eval_expr( + match eval_cfg( entry, &mut SparseSim::new(), &fir_store, map_hir_package_to_fir(id), + &mut Env::default(), &mut GenericReceiver::new(&mut out), ) { Ok(value) => expect.assert_eq(&value.to_string()), @@ -100,6 +102,94 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) { } } +fn check_partial_eval_stmt( + file: &str, + expr: &str, + stmts: &[StmtId], + fir_expect: &Expect, + result_expect: &Expect, +) { + let mut fir_lowerer = crate::lower::Lowerer::new(); + let mut core = compile::core(); + run_core_passes(&mut core); + let core_fir = fir_lowerer.lower_package(&core.package); + let mut store = PackageStore::new(core); + + let mut std = compile::std(&store, RuntimeCapabilityFlags::all()); + assert!(std.errors.is_empty()); + assert!(run_default_passes( + store.core(), + &mut std, + PackageType::Lib, + RuntimeCapabilityFlags::all() + ) + .is_empty()); + let std_fir = fir_lowerer.lower_package(&std.package); + let std_id = store.insert(std); + + let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into())); + let mut unit = compile( + &store, + &[std_id], + sources, + RuntimeCapabilityFlags::all(), + LanguageFeatures::default(), + ); + assert!(unit.errors.is_empty(), "{:?}", unit.errors); + let pass_errors = run_default_passes( + store.core(), + &mut unit, + PackageType::Lib, + RuntimeCapabilityFlags::all(), + ); + assert!(pass_errors.is_empty(), "{pass_errors:?}"); + let unit_fir = fir_lowerer.lower_package(&unit.package); + fir_expect.assert_eq(&unit_fir.to_string()); + + let entry = unit_fir.entry_cfg.clone(); + let id = store.insert(unit); + + let mut fir_store = fir::PackageStore::new(); + fir_store.insert( + map_hir_package_to_fir(qsc_hir::hir::PackageId::CORE), + core_fir, + ); + fir_store.insert(map_hir_package_to_fir(std_id), std_fir); + let id = map_hir_package_to_fir(id); + fir_store.insert(id, unit_fir); + + let mut out = Vec::new(); + let mut env = Env::default(); + let (last_stmt, most_stmts) = stmts.split_last().expect("should have at least one stmt"); + for stmt_id in most_stmts { + let stmt = fir_store.get_stmt((id, *stmt_id).into()); + match eval_cfg( + sub_cfg(&entry, stmt.cfg_range.clone()), + &mut SparseSim::new(), + &fir_store, + id, + &mut env, + &mut GenericReceiver::new(&mut out), + ) { + Ok(_) => {} + Err(err) => panic!("Unexpected error: {:?}", err), + } + } + + let stmt = fir_store.get_stmt((id, *last_stmt).into()); + match eval_cfg( + sub_cfg(&entry, stmt.cfg_range.clone()), + &mut SparseSim::new(), + &fir_store, + id, + &mut env, + &mut GenericReceiver::new(&mut out), + ) { + Ok(value) => result_expect.assert_eq(&value.to_string()), + Err(err) => result_expect.assert_debug_eq(&err), + } +} + #[test] fn array_expr() { check_expr("", "[1, 2, 3]", &expect!["[1, 2, 3]"]); @@ -3588,3 +3678,479 @@ fn partial_app_mutable_arg() { &expect!["((0, 1), (0, 2))"], ); } + +#[test] +fn partial_eval_simple_stmt() { + check_partial_eval_stmt( + "", + "{3; {4} 5;}", + &[5011_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-11] [Type Unit]: + 5009 + 5010 + 5012 + Block 2098 [4-7] [Type Int]: + 5011 + Stmts: + Stmt 5009 [1-3]: Semi: 25961 + Stmt 5010 [4-7]: Expr: 25962 + Stmt 5011 [5-6]: Expr: 25963 + Stmt 5012 [8-10]: Semi: 25964 + Exprs: + Expr 25960 [0-11] [Type Unit]: Expr Block: 2097 + Expr 25961 [1-2] [Type Int]: Lit: Int(3) + Expr 25962 [4-7] [Type Int]: Expr Block: 2098 + Expr 25963 [5-6] [Type Int]: Lit: Int(4) + Expr 25964 [8-9] [Type Int]: Lit: Int(5) + Pats:"#]], + &expect!["4"], + ); +} + +#[test] +fn partial_eval_stmt_with_bound_variable() { + check_partial_eval_stmt( + "", + "{let x = 3; {x} ()}", + &[5009_u32.into(), 5011_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-19] [Type Unit]: + 5009 + 5010 + 5012 + Block 2098 [12-15] [Type Int]: + 5011 + Stmts: + Stmt 5009 [1-11]: Local (Immutable): + 2896 + 25961 + Stmt 5010 [12-15]: Expr: 25962 + Stmt 5011 [13-14]: Expr: 25963 + Stmt 5012 [16-18]: Expr: 25964 + Exprs: + Expr 25960 [0-19] [Type Unit]: Expr Block: 2097 + Expr 25961 [9-10] [Type Int]: Lit: Int(3) + Expr 25962 [12-15] [Type Int]: Expr Block: 2098 + Expr 25963 [13-14] [Type Int]: Var: Local 0 + Expr 25964 [16-18] [Type Unit]: Unit + Pats: + Pat 2896 [5-6] [Type Int]: Bind: Ident 0 [5-6] "x""#]], + &expect!["3"], + ); +} + +#[test] +fn partial_eval_stmt_with_mutable_variable_update() { + check_partial_eval_stmt( + "", + "{mutable x = 0; set x += 1; {x} set x = -1;}", + &[5009_u32.into(), 5010_u32.into(), 5012_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-44] [Type Unit]: + 5009 + 5010 + 5011 + 5013 + Block 2098 [28-31] [Type Int]: + 5012 + Stmts: + Stmt 5009 [1-15]: Local (Mutable): + 2896 + 25961 + Stmt 5010 [16-27]: Semi: 25962 + Stmt 5011 [28-31]: Expr: 25965 + Stmt 5012 [29-30]: Expr: 25966 + Stmt 5013 [32-43]: Semi: 25967 + Exprs: + Expr 25960 [0-44] [Type Unit]: Expr Block: 2097 + Expr 25961 [13-14] [Type Int]: Lit: Int(0) + Expr 25962 [16-26] [Type Unit]: AssignOp (Add): + 25963 + 25964 + Expr 25963 [20-21] [Type Int]: Var: Local 0 + Expr 25964 [25-26] [Type Int]: Lit: Int(1) + Expr 25965 [28-31] [Type Int]: Expr Block: 2098 + Expr 25966 [29-30] [Type Int]: Var: Local 0 + Expr 25967 [32-42] [Type Unit]: Assign: + 25968 + 25969 + Expr 25968 [36-37] [Type Int]: Var: Local 0 + Expr 25969 [40-42] [Type Int]: UnOp (Neg): + 25970 + Expr 25970 [41-42] [Type Int]: Lit: Int(1) + Pats: + Pat 2896 [9-10] [Type Int]: Bind: Ident 0 [9-10] "x""#]], + &expect!["1"], + ); +} + +#[test] +fn partial_eval_stmt_with_mutable_variable_update_out_of_order_works() { + check_partial_eval_stmt( + "", + "{mutable x = 0; set x += 1; {x} set x = -1;}", + &[5009_u32.into(), 5013_u32.into(), 5012_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-44] [Type Unit]: + 5009 + 5010 + 5011 + 5013 + Block 2098 [28-31] [Type Int]: + 5012 + Stmts: + Stmt 5009 [1-15]: Local (Mutable): + 2896 + 25961 + Stmt 5010 [16-27]: Semi: 25962 + Stmt 5011 [28-31]: Expr: 25965 + Stmt 5012 [29-30]: Expr: 25966 + Stmt 5013 [32-43]: Semi: 25967 + Exprs: + Expr 25960 [0-44] [Type Unit]: Expr Block: 2097 + Expr 25961 [13-14] [Type Int]: Lit: Int(0) + Expr 25962 [16-26] [Type Unit]: AssignOp (Add): + 25963 + 25964 + Expr 25963 [20-21] [Type Int]: Var: Local 0 + Expr 25964 [25-26] [Type Int]: Lit: Int(1) + Expr 25965 [28-31] [Type Int]: Expr Block: 2098 + Expr 25966 [29-30] [Type Int]: Var: Local 0 + Expr 25967 [32-42] [Type Unit]: Assign: + 25968 + 25969 + Expr 25968 [36-37] [Type Int]: Var: Local 0 + Expr 25969 [40-42] [Type Int]: UnOp (Neg): + 25970 + Expr 25970 [41-42] [Type Int]: Lit: Int(1) + Pats: + Pat 2896 [9-10] [Type Int]: Bind: Ident 0 [9-10] "x""#]], + &expect!["-1"], + ); +} + +#[test] +fn partial_eval_stmt_with_mutable_variable_update_repeat_stmts_works() { + check_partial_eval_stmt( + "", + "{mutable x = 0; set x += 1; {x} set x = -1;}", + &[ + 5009_u32.into(), + 5010_u32.into(), + 5010_u32.into(), + 5012_u32.into(), + ], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-44] [Type Unit]: + 5009 + 5010 + 5011 + 5013 + Block 2098 [28-31] [Type Int]: + 5012 + Stmts: + Stmt 5009 [1-15]: Local (Mutable): + 2896 + 25961 + Stmt 5010 [16-27]: Semi: 25962 + Stmt 5011 [28-31]: Expr: 25965 + Stmt 5012 [29-30]: Expr: 25966 + Stmt 5013 [32-43]: Semi: 25967 + Exprs: + Expr 25960 [0-44] [Type Unit]: Expr Block: 2097 + Expr 25961 [13-14] [Type Int]: Lit: Int(0) + Expr 25962 [16-26] [Type Unit]: AssignOp (Add): + 25963 + 25964 + Expr 25963 [20-21] [Type Int]: Var: Local 0 + Expr 25964 [25-26] [Type Int]: Lit: Int(1) + Expr 25965 [28-31] [Type Int]: Expr Block: 2098 + Expr 25966 [29-30] [Type Int]: Var: Local 0 + Expr 25967 [32-42] [Type Unit]: Assign: + 25968 + 25969 + Expr 25968 [36-37] [Type Int]: Var: Local 0 + Expr 25969 [40-42] [Type Int]: UnOp (Neg): + 25970 + Expr 25970 [41-42] [Type Int]: Lit: Int(1) + Pats: + Pat 2896 [9-10] [Type Int]: Bind: Ident 0 [9-10] "x""#]], + &expect!["2"], + ); +} + +#[test] +fn partial_eval_stmt_with_bool_short_circuit() { + check_partial_eval_stmt( + "", + "{let x = true; { x or false } ();}", + &[5009_u32.into(), 5011_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-34] [Type Unit]: + 5009 + 5010 + 5012 + Block 2098 [15-29] [Type Bool]: + 5011 + Stmts: + Stmt 5009 [1-14]: Local (Immutable): + 2896 + 25961 + Stmt 5010 [15-29]: Expr: 25962 + Stmt 5011 [17-27]: Expr: 25963 + Stmt 5012 [30-33]: Semi: 25966 + Exprs: + Expr 25960 [0-34] [Type Unit]: Expr Block: 2097 + Expr 25961 [9-13] [Type Bool]: Lit: Bool(true) + Expr 25962 [15-29] [Type Bool]: Expr Block: 2098 + Expr 25963 [17-27] [Type Bool]: BinOp (OrL): + 25964 + 25965 + Expr 25964 [17-18] [Type Bool]: Var: Local 0 + Expr 25965 [22-27] [Type Bool]: Lit: Bool(false) + Expr 25966 [30-32] [Type Unit]: Unit + Pats: + Pat 2896 [5-6] [Type Bool]: Bind: Ident 0 [5-6] "x""#]], + &expect!["true"], + ); +} + +#[test] +fn partial_eval_stmt_with_bool_no_short_circuit() { + check_partial_eval_stmt( + "", + "{let x = false; { x or true } ();}", + &[5009_u32.into(), 5011_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-34] [Type Unit]: + 5009 + 5010 + 5012 + Block 2098 [16-29] [Type Bool]: + 5011 + Stmts: + Stmt 5009 [1-15]: Local (Immutable): + 2896 + 25961 + Stmt 5010 [16-29]: Expr: 25962 + Stmt 5011 [18-27]: Expr: 25963 + Stmt 5012 [30-33]: Semi: 25966 + Exprs: + Expr 25960 [0-34] [Type Unit]: Expr Block: 2097 + Expr 25961 [9-14] [Type Bool]: Lit: Bool(false) + Expr 25962 [16-29] [Type Bool]: Expr Block: 2098 + Expr 25963 [18-27] [Type Bool]: BinOp (OrL): + 25964 + 25965 + Expr 25964 [18-19] [Type Bool]: Var: Local 0 + Expr 25965 [23-27] [Type Bool]: Lit: Bool(true) + Expr 25966 [30-32] [Type Unit]: Unit + Pats: + Pat 2896 [5-6] [Type Bool]: Bind: Ident 0 [5-6] "x""#]], + &expect!["true"], + ); +} + +#[test] +fn partial_eval_stmt_with_loop() { + check_partial_eval_stmt( + "", + "{mutable x = 0; while x < 3 { set x += 1; } {x} ();}", + &[5009_u32.into(), 5010_u32.into(), 5013_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-52] [Type Unit]: + 5009 + 5010 + 5012 + 5014 + Block 2098 [28-43] [Type Unit]: + 5011 + Block 2099 [44-47] [Type Int]: + 5013 + Stmts: + Stmt 5009 [1-15]: Local (Mutable): + 2896 + 25961 + Stmt 5010 [16-43]: Expr: 25962 + Stmt 5011 [30-41]: Semi: 25966 + Stmt 5012 [44-47]: Expr: 25969 + Stmt 5013 [45-46]: Expr: 25970 + Stmt 5014 [48-51]: Semi: 25971 + Exprs: + Expr 25960 [0-52] [Type Unit]: Expr Block: 2097 + Expr 25961 [13-14] [Type Int]: Lit: Int(0) + Expr 25962 [16-43] [Type Unit]: While: + 25963 + 2098 + Expr 25963 [22-27] [Type Bool]: BinOp (Lt): + 25964 + 25965 + Expr 25964 [22-23] [Type Int]: Var: Local 0 + Expr 25965 [26-27] [Type Int]: Lit: Int(3) + Expr 25966 [30-40] [Type Unit]: AssignOp (Add): + 25967 + 25968 + Expr 25967 [34-35] [Type Int]: Var: Local 0 + Expr 25968 [39-40] [Type Int]: Lit: Int(1) + Expr 25969 [44-47] [Type Int]: Expr Block: 2099 + Expr 25970 [45-46] [Type Int]: Var: Local 0 + Expr 25971 [48-50] [Type Unit]: Unit + Pats: + Pat 2896 [9-10] [Type Int]: Bind: Ident 0 [9-10] "x""#]], + &expect!["3"], + ); +} + +#[test] +fn partial_eval_stmt_function_calls() { + check_partial_eval_stmt( + indoc! {" + namespace Test { + function Add1(x : Int) : Int { x + 1 } + } + "}, + "{let x = Test.Add1(4); {x} Test.Add1(3)}", + &[5009_u32.into(), 5011_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Item 0 [41-102] (Public): + Namespace (Ident 1 [51-55] "Test"): Item 1 + Item 1 [62-100] (Public): + Parent: 0 + Callable 0 [62-100] (function): + name: Ident 2 [71-75] "Add1" + input: 2897 + output: Int + functors: empty set + implementation: Spec: + SpecImpl: + body: SpecDecl 942 [62-100]: None 2099 + adj: + ctl: + ctl-adj: + Blocks: + Block 2097 [0-40] [Type Int]: + 5009 + 5010 + 5012 + Block 2098 [23-26] [Type Int]: + 5011 + Block 2099 [91-100] [Type Int]: + 5013 + Stmts: + Stmt 5009 [1-22]: Local (Immutable): + 2896 + 25961 + Stmt 5010 [23-26]: Expr: 25964 + Stmt 5011 [24-25]: Expr: 25965 + Stmt 5012 [27-39]: Expr: 25966 + Stmt 5013 [93-98]: Expr: 25969 + Exprs: + Expr 25960 [0-40] [Type Int]: Expr Block: 2097 + Expr 25961 [9-21] [Type Int]: Call: + 25962 + 25963 + Expr 25962 [9-18] [Type (Int -> Int)]: Var: Item 1 + Expr 25963 [19-20] [Type Int]: Lit: Int(4) + Expr 25964 [23-26] [Type Int]: Expr Block: 2098 + Expr 25965 [24-25] [Type Int]: Var: Local 0 + Expr 25966 [27-39] [Type Int]: Call: + 25967 + 25968 + Expr 25967 [27-36] [Type (Int -> Int)]: Var: Item 1 + Expr 25968 [37-38] [Type Int]: Lit: Int(3) + Expr 25969 [93-98] [Type Int]: BinOp (Add): + 25970 + 25971 + Expr 25970 [93-94] [Type Int]: Var: Local 3 + Expr 25971 [97-98] [Type Int]: Lit: Int(1) + Pats: + Pat 2896 [5-6] [Type Int]: Bind: Ident 0 [5-6] "x" + Pat 2897 [76-83] [Type Int]: Bind: Ident 3 [76-77] "x""#]], + &expect!["5"], + ); +} + +#[test] +fn partial_eval_stmt_function_calls_from_library() { + check_partial_eval_stmt( + "", + "{let x = [1, 2, 3]; {Length(x)} 3}", + &[5009_u32.into(), 5011_u32.into()], + &expect![[r#" + Package: + Entry Expression: 25960 + Items: + Blocks: + Block 2097 [0-34] [Type Int]: + 5009 + 5010 + 5012 + Block 2098 [20-31] [Type Int]: + 5011 + Stmts: + Stmt 5009 [1-19]: Local (Immutable): + 2896 + 25961 + Stmt 5010 [20-31]: Expr: 25965 + Stmt 5011 [21-30]: Expr: 25966 + Stmt 5012 [32-33]: Expr: 25969 + Exprs: + Expr 25960 [0-34] [Type Int]: Expr Block: 2097 + Expr 25961 [9-18] [Type (Int)[]]: Array: + 25962 + 25963 + 25964 + Expr 25962 [10-11] [Type Int]: Lit: Int(1) + Expr 25963 [13-14] [Type Int]: Lit: Int(2) + Expr 25964 [16-17] [Type Int]: Lit: Int(3) + Expr 25965 [20-31] [Type Int]: Expr Block: 2098 + Expr 25966 [21-30] [Type Int]: Call: + 25967 + 25968 + Expr 25967 [21-27] [Type ((Int)[] -> Int)]: Var: + res: Item 1 (Package 0) + generics: + Int + Expr 25968 [28-29] [Type (Int)[]]: Var: Local 0 + Expr 25969 [32-33] [Type Int]: Lit: Int(3) + Pats: + Pat 2896 [5-6] [Type (Int)[]]: Bind: Ident 0 [5-6] "x""#]], + &expect!["3"], + ); +} diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs index 3d76537292..46289e2508 100644 --- a/compiler/qsc_fir/src/fir.rs +++ b/compiler/qsc_fir/src/fir.rs @@ -19,6 +19,7 @@ use std::{ cmp::Ordering, fmt::{self, Debug, Display, Formatter, Write}, hash::{Hash, Hasher}, + ops, rc::Rc, result, str::FromStr, @@ -943,6 +944,8 @@ pub struct Stmt { pub span: Span, /// The statement kind. pub kind: StmtKind, + /// The locations within the containing control flow graph for the current statement. + pub cfg_range: ops::Range, } impl Display for Stmt { @@ -993,6 +996,8 @@ pub struct Expr { pub ty: Ty, /// The expression kind. pub kind: ExprKind, + /// The locations within the containing control flow graph for the current expression. + pub cfg_range: ops::Range, } impl Display for Expr { From c34f6776ccd6e6a3fb0c00aef718da4110fb7570 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sun, 17 Mar 2024 08:06:09 -0700 Subject: [PATCH 07/16] Optimize array of literals --- compiler/qsc_eval/src/lib.rs | 14 ++++++++++++ compiler/qsc_eval/src/lower.rs | 36 +++++++++++++++++++++++-------- compiler/qsc_fir/src/fir.rs | 3 +++ compiler/qsc_fir/src/mut_visit.rs | 4 +++- compiler/qsc_fir/src/visit.rs | 4 +++- 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 36595286aa..86484a87fd 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -617,6 +617,7 @@ impl State { match &expr.kind { ExprKind::Array(arr) => self.eval_arr(arr.len()), + ExprKind::ArrayLit(arr) => self.eval_arr_lit(arr, globals), ExprKind::ArrayRepeat(..) => self.eval_arr_repeat(expr.span)?, ExprKind::Assign(lhs, _) => self.eval_assign(env, globals, *lhs)?, ExprKind::AssignOp(op, lhs, rhs) => { @@ -736,6 +737,19 @@ impl State { self.set_curr_val(Value::Array(arr.into())); } + fn eval_arr_lit(&mut self, arr: &Vec, globals: &impl PackageStoreLookup) { + let mut new_arr: Rc> = Rc::new(Vec::with_capacity(arr.len())); + for id in arr { + let ExprKind::Lit(lit) = &globals.get_expr((self.package, *id).into()).kind else { + panic!("expr kind should be lit") + }; + Rc::get_mut(&mut new_arr) + .expect("array should be uniquely referenced") + .push(lit_to_val(lit)); + } + self.curr_val = Some(Value::Array(new_arr)); + } + fn eval_array_append_in_place( &mut self, env: &mut Env, diff --git a/compiler/qsc_eval/src/lower.rs b/compiler/qsc_eval/src/lower.rs index 73350f4838..1a76dd27bb 100644 --- a/compiler/qsc_eval/src/lower.rs +++ b/compiler/qsc_eval/src/lower.rs @@ -303,16 +303,34 @@ impl Lowerer { let ty = self.lower_ty(&expr.ty); let kind = match &expr.kind { - hir::ExprKind::Array(items) => fir::ExprKind::Array( - items + hir::ExprKind::Array(items) => { + if items .iter() - .map(|i| { - let i = self.lower_expr(i); - self.cfg.push(CfgNode::Store); - i - }) - .collect(), - ), + .all(|i| matches!(i.kind, hir::ExprKind::Lit(..))) + { + fir::ExprKind::ArrayLit( + items + .iter() + .map(|i| { + let i = self.lower_expr(i); + self.cfg.pop(); + i + }) + .collect(), + ) + } else { + fir::ExprKind::Array( + items + .iter() + .map(|i| { + let i = self.lower_expr(i); + self.cfg.push(CfgNode::Store); + i + }) + .collect(), + ) + } + } hir::ExprKind::ArrayRepeat(value, size) => { let value = self.lower_expr(value); self.cfg.push(CfgNode::Store); diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs index 46289e2508..41eac08fef 100644 --- a/compiler/qsc_fir/src/fir.rs +++ b/compiler/qsc_fir/src/fir.rs @@ -1015,6 +1015,8 @@ impl Display for Expr { pub enum ExprKind { /// An array: `[a, b, c]`. Array(Vec), + /// An array of literal values, ie: `[1, 2, 3]`. + ArrayLit(Vec), /// An array constructed by repeating a value: `[a, size = b]`. ArrayRepeat(ExprId, ExprId), /// An assignment: `set a = b`. @@ -1074,6 +1076,7 @@ impl Display for ExprKind { let mut indent = set_indentation(indented(f), 0); match self { ExprKind::Array(exprs) => display_array(indent, exprs)?, + ExprKind::ArrayLit(exprs) => display_array(indent, exprs)?, ExprKind::ArrayRepeat(val, size) => display_array_repeat(indent, *val, *size)?, ExprKind::Assign(lhs, rhs) => display_assign(indent, *lhs, *rhs)?, ExprKind::AssignOp(op, lhs, rhs) => display_assign_op(indent, *op, *lhs, *rhs)?, diff --git a/compiler/qsc_fir/src/mut_visit.rs b/compiler/qsc_fir/src/mut_visit.rs index 1e03d03863..1b144a9f6d 100644 --- a/compiler/qsc_fir/src/mut_visit.rs +++ b/compiler/qsc_fir/src/mut_visit.rs @@ -123,7 +123,9 @@ pub fn walk_stmt<'a>(vis: &mut impl MutVisitor<'a>, id: StmtId) { pub fn walk_expr<'a>(vis: &mut impl MutVisitor<'a>, expr: ExprId) { let expr = vis.get_expr(expr); match &expr.kind { - ExprKind::Array(exprs) => exprs.iter().for_each(|e| vis.visit_expr(*e)), + ExprKind::Array(exprs) | ExprKind::ArrayLit(exprs) => { + exprs.iter().for_each(|e| vis.visit_expr(*e)); + } ExprKind::ArrayRepeat(item, size) => { vis.visit_expr(*item); vis.visit_expr(*size); diff --git a/compiler/qsc_fir/src/visit.rs b/compiler/qsc_fir/src/visit.rs index 37edaa3a67..81cb167c47 100644 --- a/compiler/qsc_fir/src/visit.rs +++ b/compiler/qsc_fir/src/visit.rs @@ -123,7 +123,9 @@ pub fn walk_stmt<'a>(vis: &mut impl Visitor<'a>, id: StmtId) { pub fn walk_expr<'a>(vis: &mut impl Visitor<'a>, expr: ExprId) { let expr = vis.get_expr(expr); match &expr.kind { - ExprKind::Array(exprs) => exprs.iter().for_each(|e| vis.visit_expr(*e)), + ExprKind::Array(exprs) | ExprKind::ArrayLit(exprs) => { + exprs.iter().for_each(|e| vis.visit_expr(*e)); + } ExprKind::ArrayRepeat(item, size) => { vis.visit_expr(*item); vis.visit_expr(*size); From 309335b5681b05c62317a6d397630fcef8dea5d5 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sun, 17 Mar 2024 08:16:34 -0700 Subject: [PATCH 08/16] Use utility function --- compiler/qsc_eval/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 86484a87fd..dba782c96e 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -747,7 +747,7 @@ impl State { .expect("array should be uniquely referenced") .push(lit_to_val(lit)); } - self.curr_val = Some(Value::Array(new_arr)); + self.set_curr_val(Value::Array(new_arr)); } fn eval_array_append_in_place( From e24338dc561a1b81a26593ace01cac2bca28d5ba Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Mon, 18 Mar 2024 09:09:53 -0700 Subject: [PATCH 09/16] Update names --- compiler/qsc/src/interpret.rs | 53 +++++--- compiler/qsc_codegen/src/qir_base.rs | 2 +- compiler/qsc_eval/src/intrinsic/tests.rs | 6 +- compiler/qsc_eval/src/lib.rs | 74 ++++++----- compiler/qsc_eval/src/lower.rs | 162 ++++++++++++----------- compiler/qsc_eval/src/tests.rs | 25 ++-- compiler/qsc_fir/src/fir.rs | 10 +- 7 files changed, 184 insertions(+), 148 deletions(-) diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs index 56ca5c0109..882a54bad6 100644 --- a/compiler/qsc/src/interpret.rs +++ b/compiler/qsc/src/interpret.rs @@ -43,7 +43,7 @@ use qsc_eval::{ val::{self}, Env, State, VariableInfo, }; -use qsc_fir::fir::{self, CfgNode, Global, PackageStoreLookup}; +use qsc_fir::fir::{self, ExecGraphNode, Global, PackageStoreLookup}; use qsc_fir::{ fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId}, visit::{self, Visitor}, @@ -177,11 +177,11 @@ impl Interpreter { &mut self, receiver: &mut impl Receiver, ) -> std::result::Result> { - let cfg = self.get_entry_cfg()?; + let graph = self.get_entry_exec_graph()?; eval( self.source_package, self.classical_seed, - cfg, + graph, self.compiler.package_store(), &self.fir_store, &mut Env::default(), @@ -197,14 +197,14 @@ impl Interpreter { sim: &mut impl Backend>, receiver: &mut impl Receiver, ) -> std::result::Result> { - let cfg = self.get_entry_cfg()?; + let graph = self.get_entry_exec_graph()?; if self.quantum_seed.is_some() { sim.set_seed(self.quantum_seed); } eval( self.source_package, self.classical_seed, - cfg, + graph, self.compiler.package_store(), &self.fir_store, &mut Env::default(), @@ -213,10 +213,10 @@ impl Interpreter { ) } - fn get_entry_cfg(&self) -> std::result::Result, Vec> { + fn get_entry_exec_graph(&self) -> std::result::Result, Vec> { let unit = self.fir_store.get(self.source_package); if unit.entry.is_some() { - return Ok(unit.entry_cfg.clone()); + return Ok(unit.entry_exec_graph.clone()); }; Err(vec![Error::NoEntryPoint]) } @@ -237,7 +237,7 @@ impl Interpreter { .compile_fragments_fail_fast(&label, fragments) .map_err(into_errors)?; - let (_, cfg) = self.lower(&increment); + let (_, graph) = self.lower(&increment); // Updating the compiler state with the new AST/HIR nodes // is not necessary for the interpreter to function, as all @@ -250,7 +250,7 @@ impl Interpreter { eval( self.package, self.classical_seed, - cfg.into(), + graph.into(), self.compiler.package_store(), &self.fir_store, &mut self.env, @@ -298,7 +298,7 @@ impl Interpreter { receiver: &mut impl Receiver, expr: &str, ) -> std::result::Result> { - let cfg = self.compile_entry_expr(expr)?; + let graph = self.compile_entry_expr(expr)?; if self.quantum_seed.is_some() { sim.set_seed(self.quantum_seed); @@ -307,7 +307,7 @@ impl Interpreter { Ok(eval( self.package, self.classical_seed, - cfg.into(), + graph.into(), self.compiler.package_store(), &self.fir_store, &mut Env::default(), @@ -316,7 +316,10 @@ impl Interpreter { )) } - fn compile_entry_expr(&mut self, expr: &str) -> std::result::Result, Vec> { + fn compile_entry_expr( + &mut self, + expr: &str, + ) -> std::result::Result, Vec> { let increment = self .compiler .compile_entry_expr(expr) @@ -324,7 +327,7 @@ impl Interpreter { // `lower` will update the entry expression in the FIR store, // and it will always return an empty list of statements. - let (_, cfg) = self.lower(&increment); + let (_, graph) = self.lower(&increment); // The AST and HIR packages in `increment` only contain an entry // expression and no statements. The HIR *can* contain items if the entry @@ -340,18 +343,18 @@ impl Interpreter { // here to keep the package stores consistent. self.compiler.update(increment); - Ok(cfg) + Ok(graph) } fn lower( &mut self, unit_addition: &qsc_frontend::incremental::Increment, - ) -> (Vec, Vec) { + ) -> (Vec, Vec) { let fir_package = self.fir_store.get_mut(self.package); ( self.lowerer .lower_and_update_package(fir_package, &unit_addition.hir), - self.lowerer.cfg(), + self.lowerer.take_exec_graph(), ) } @@ -389,11 +392,11 @@ impl Debugger { )?; let source_package_id = interpreter.source_package; let unit = interpreter.fir_store.get(source_package_id); - let entry_cfg = unit.entry_cfg.clone(); + let entry_exec_graph = unit.entry_exec_graph.clone(); Ok(Self { interpreter, position_encoding, - state: State::new(source_package_id, entry_cfg, None), + state: State::new(source_package_id, entry_exec_graph, None), }) } @@ -519,15 +522,23 @@ impl Debugger { fn eval( package: PackageId, classical_seed: Option, - cfg: Rc<[CfgNode]>, + exec_graph: Rc<[ExecGraphNode]>, package_store: &PackageStore, fir_store: &fir::PackageStore, env: &mut Env, sim: &mut impl Backend>, receiver: &mut impl Receiver, ) -> InterpretResult { - qsc_eval::eval(package, classical_seed, cfg, fir_store, env, sim, receiver) - .map_err(|(error, call_stack)| eval_error(package_store, fir_store, call_stack, error)) + qsc_eval::eval( + package, + classical_seed, + exec_graph, + fir_store, + env, + sim, + receiver, + ) + .map_err(|(error, call_stack)| eval_error(package_store, fir_store, call_stack, error)) } /// Represents a stack frame for debugging. diff --git a/compiler/qsc_codegen/src/qir_base.rs b/compiler/qsc_codegen/src/qir_base.rs index bd753871f3..4d4894d700 100644 --- a/compiler/qsc_codegen/src/qir_base.rs +++ b/compiler/qsc_codegen/src/qir_base.rs @@ -49,7 +49,7 @@ pub fn generate_qir( let result = eval( package, None, - unit.entry_cfg.clone(), + unit.entry_exec_graph.clone(), &fir_store, &mut Env::default(), &mut sim, diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 1f633beb34..2b0544bb99 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -7,7 +7,7 @@ use std::f64::consts; use crate::backend::{Backend, SparseSim}; use crate::debug::map_hir_package_to_fir; -use crate::tests::eval_cfg; +use crate::tests::eval_graph; use crate::Env; use crate::{ output::{GenericReceiver, Receiver}, @@ -181,7 +181,7 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result Result, range: ops::Range) -> Rc<[CfgNode]> { +pub fn exec_graph_section( + graph: &Rc<[ExecGraphNode]>, + range: ops::Range, +) -> Rc<[ExecGraphNode]> { let start: u32 = range .start .try_into() - .expect("cfg ranges should fit into u32"); - cfg[range] + .expect("exec graph ranges should fit into u32"); + graph[range] .iter() .map(|node| match node { - CfgNode::Jump(idx) => CfgNode::Jump(idx - start), - CfgNode::JumpIf(idx) => CfgNode::JumpIf(idx - start), - CfgNode::JumpIfNot(idx) => CfgNode::JumpIfNot(idx - start), + ExecGraphNode::Jump(idx) => ExecGraphNode::Jump(idx - start), + ExecGraphNode::JumpIf(idx) => ExecGraphNode::JumpIf(idx - start), + ExecGraphNode::JumpIfNot(idx) => ExecGraphNode::JumpIfNot(idx - start), _ => *node, }) .collect::>() @@ -201,13 +204,13 @@ pub fn sub_cfg(cfg: &Rc<[CfgNode]>, range: ops::Range) -> Rc<[CfgNode]> { pub fn eval( package: PackageId, seed: Option, - cfg: Rc<[CfgNode]>, + exec_graph: Rc<[ExecGraphNode]>, globals: &impl PackageStoreLookup, env: &mut Env, sim: &mut impl Backend>, receiver: &mut impl Receiver, ) -> Result)> { - let mut state = State::new(package, cfg, seed); + let mut state = State::new(package, exec_graph, seed); let res = state.eval(globals, env, sim, receiver, &[], StepAction::Continue)?; let StepResult::Return(value) = res else { panic!("eval should always return a value"); @@ -378,7 +381,7 @@ impl Default for Env { } pub struct State { - cfg_stack: Vec>, + exec_graph_stack: Vec>, idx: u32, idx_stack: Vec, curr_val: Option, @@ -391,13 +394,17 @@ pub struct State { impl State { #[must_use] - pub fn new(package: PackageId, cfg: Rc<[CfgNode]>, classical_seed: Option) -> Self { + pub fn new( + package: PackageId, + exec_graph: Rc<[ExecGraphNode]>, + classical_seed: Option, + ) -> Self { let rng = match classical_seed { Some(seed) => RefCell::new(StdRng::seed_from_u64(seed)), None => RefCell::new(StdRng::from_entropy()), }; Self { - cfg_stack: vec![cfg], + exec_graph_stack: vec![exec_graph], idx: 0, idx_stack: Vec::new(), curr_val: None, @@ -409,14 +416,19 @@ impl State { } } - fn push_frame(&mut self, cfg: Rc<[CfgNode]>, id: StoreItemId, functor: FunctorApp) { + fn push_frame( + &mut self, + exec_graph: Rc<[ExecGraphNode]>, + id: StoreItemId, + functor: FunctorApp, + ) { self.call_stack.push_frame(Frame { span: self.current_span, id, caller: self.package, functor, }); - self.cfg_stack.push(cfg); + self.exec_graph_stack.push(exec_graph); self.val_stack.push(Vec::new()); self.idx_stack.push(self.idx); self.idx = 0; @@ -432,7 +444,7 @@ impl State { .pop() .expect("should have at least one index"); } - self.cfg_stack.pop(); + self.exec_graph_stack.pop(); } fn push_scope(&mut self, env: &mut Env) { @@ -497,24 +509,24 @@ impl State { ) -> Result)> { let current_frame = self.call_stack.len(); - while !self.cfg_stack.is_empty() { - let cfg = self - .cfg_stack + while !self.exec_graph_stack.is_empty() { + let exec_graph = self + .exec_graph_stack .last() .expect("should have at least one stack frame"); - let res = match cfg.get(self.idx as usize) { - Some(CfgNode::Bind(pat)) => { + let res = match exec_graph.get(self.idx as usize) { + Some(ExecGraphNode::Bind(pat)) => { self.idx += 1; self.eval_bind(env, globals, *pat); continue; } - Some(CfgNode::Expr(expr)) => { + Some(ExecGraphNode::Expr(expr)) => { self.idx += 1; self.eval_expr(env, sim, globals, out, *expr) .map_err(|e| (e, self.get_stack_frames()))?; continue; } - Some(CfgNode::Stmt(stmt)) => { + Some(ExecGraphNode::Stmt(stmt)) => { self.idx += 1; self.current_span = globals.get_stmt((self.package, *stmt).into()).span; @@ -538,11 +550,11 @@ impl State { } } } - Some(CfgNode::Jump(idx)) => { + Some(ExecGraphNode::Jump(idx)) => { self.idx = *idx; continue; } - Some(CfgNode::JumpIf(idx)) => { + Some(ExecGraphNode::JumpIf(idx)) => { let cond = self.curr_val == Some(Value::Bool(true)); if cond { self.idx = *idx; @@ -551,7 +563,7 @@ impl State { } continue; } - Some(CfgNode::JumpIfNot(idx)) => { + Some(ExecGraphNode::JumpIfNot(idx)) => { let cond = self.curr_val == Some(Value::Bool(true)); if cond { self.idx += 1; @@ -560,28 +572,28 @@ impl State { } continue; } - Some(CfgNode::Store) => { + Some(ExecGraphNode::Store) => { self.push_val(); self.idx += 1; continue; } - Some(CfgNode::Unit) => { + Some(ExecGraphNode::Unit) => { self.idx += 1; self.set_curr_val(Value::unit()); continue; } - Some(CfgNode::Ret) => { + Some(ExecGraphNode::Ret) => { self.leave_frame(); env.leave_scope(); continue; } None => { - // We have reached the end of the current cfg without reaching an explicit return node, + // We have reached the end of the current graph without reaching an explicit return node, // usually indicating the partial execution of a single sub-expression. // This means we should leave the current frame but not the current environment scope, // so bound variables are still accessible after completion. self.leave_frame(); - assert!(self.cfg_stack.is_empty()); + assert!(self.exec_graph_stack.is_empty()); continue; } }; @@ -913,7 +925,7 @@ impl State { Spec::CtlAdj => specialized_implementation.ctl_adj.as_ref(), } .expect("missing specialization should be a compilation error"); - self.push_frame(spec_decl.cfg.clone(), callee_id, functor); + self.push_frame(spec_decl.exec_graph.clone(), callee_id, functor); self.push_scope(env); self.bind_args_for_spec( diff --git a/compiler/qsc_eval/src/lower.rs b/compiler/qsc_eval/src/lower.rs index 1a76dd27bb..4d9af8e076 100644 --- a/compiler/qsc_eval/src/lower.rs +++ b/compiler/qsc_eval/src/lower.rs @@ -3,7 +3,7 @@ use qsc_data_structures::index_map::IndexMap; use qsc_fir::assigner::Assigner; -use qsc_fir::fir::{Block, CallableImpl, CfgNode, Expr, Pat, SpecImpl, Stmt}; +use qsc_fir::fir::{Block, CallableImpl, ExecGraphNode, Expr, Pat, SpecImpl, Stmt}; use qsc_fir::{ fir::{self, BlockId, ExprId, LocalItemId, PatId, StmtId}, ty::{Arrow, InferFunctorId, ParamId, Ty}, @@ -20,7 +20,7 @@ pub struct Lowerer { stmts: IndexMap, blocks: IndexMap, assigner: Assigner, - cfg: Vec, + exec_graph: Vec, } impl Default for Lowerer { @@ -40,17 +40,24 @@ impl Lowerer { stmts: IndexMap::new(), blocks: IndexMap::new(), assigner: Assigner::new(), - cfg: Vec::new(), + exec_graph: Vec::new(), } } - pub fn cfg(&mut self) -> Vec { - self.cfg.drain(..).chain(once(CfgNode::Ret)).collect() + pub fn take_exec_graph(&mut self) -> Vec { + self.exec_graph + .drain(..) + .chain(once(ExecGraphNode::Ret)) + .collect() } pub fn lower_package(&mut self, package: &hir::Package) -> fir::Package { let entry = package.entry.as_ref().map(|e| self.lower_expr(e)); - let entry_cfg = self.cfg.drain(..).chain(once(CfgNode::Ret)).collect(); + let entry_exec_graph = self + .exec_graph + .drain(..) + .chain(once(ExecGraphNode::Ret)) + .collect(); let items: IndexMap = package .items .values() @@ -71,7 +78,7 @@ impl Lowerer { let package = fir::Package { items, entry, - entry_cfg, + entry_exec_graph, blocks, exprs, pats, @@ -222,7 +229,11 @@ impl Lowerer { span: decl.span, block, input, - cfg: self.cfg.drain(..).chain(once(CfgNode::Ret)).collect(), + exec_graph: self + .exec_graph + .drain(..) + .chain(once(ExecGraphNode::Ret)) + .collect(), } } @@ -259,7 +270,7 @@ impl Lowerer { stmts: block.stmts.iter().map(|s| self.lower_stmt(s)).collect(), }; if set_unit { - self.cfg.push(CfgNode::Unit); + self.exec_graph.push(ExecGraphNode::Unit); } self.blocks.insert(id, block); id @@ -267,15 +278,15 @@ impl Lowerer { fn lower_stmt(&mut self, stmt: &hir::Stmt) -> fir::StmtId { let id = self.assigner.next_stmt(); - let cfg_start_idx = self.cfg.len(); - self.cfg.push(CfgNode::Stmt(id)); + let graph_start_idx = self.exec_graph.len(); + self.exec_graph.push(ExecGraphNode::Stmt(id)); let kind = match &stmt.kind { hir::StmtKind::Expr(expr) => fir::StmtKind::Expr(self.lower_expr(expr)), hir::StmtKind::Item(item) => fir::StmtKind::Item(lower_local_item_id(*item)), hir::StmtKind::Local(mutability, pat, expr) => { let pat = self.lower_pat(pat); let expr = self.lower_expr(expr); - self.cfg.push(CfgNode::Bind(pat)); + self.exec_graph.push(ExecGraphNode::Bind(pat)); fir::StmtKind::Local(lower_mutability(*mutability), pat, expr) } hir::StmtKind::Qubit(_, _, _, _) => { @@ -290,7 +301,7 @@ impl Lowerer { id, span: stmt.span, kind, - cfg_range: cfg_start_idx..self.cfg.len(), + exec_graph_range: graph_start_idx..self.exec_graph.len(), }; self.stmts.insert(id, stmt); id @@ -299,7 +310,7 @@ impl Lowerer { #[allow(clippy::too_many_lines)] fn lower_expr(&mut self, expr: &hir::Expr) -> ExprId { let id = self.assigner.next_expr(); - let cfg_start_idx = self.cfg.len(); + let graph_start_idx = self.exec_graph.len(); let ty = self.lower_ty(&expr.ty); let kind = match &expr.kind { @@ -313,7 +324,7 @@ impl Lowerer { .iter() .map(|i| { let i = self.lower_expr(i); - self.cfg.pop(); + self.exec_graph.pop(); i }) .collect(), @@ -324,7 +335,7 @@ impl Lowerer { .iter() .map(|i| { let i = self.lower_expr(i); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); i }) .collect(), @@ -333,47 +344,47 @@ impl Lowerer { } hir::ExprKind::ArrayRepeat(value, size) => { let value = self.lower_expr(value); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); let size = self.lower_expr(size); fir::ExprKind::ArrayRepeat(value, size) } hir::ExprKind::Assign(lhs, rhs) => { - let idx = self.cfg.len(); + let idx = self.exec_graph.len(); let lhs = self.lower_expr(lhs); // The left-hand side of an assigment is not really an expression to be executed, - // so remove any added nodes from the CFG. - self.cfg.drain(idx..); + // so remove any added nodes from the execution graph. + self.exec_graph.drain(idx..); fir::ExprKind::Assign(lhs, self.lower_expr(rhs)) } hir::ExprKind::AssignOp(op, lhs, rhs) => { - let idx = self.cfg.len(); + let idx = self.exec_graph.len(); let is_array = matches!(lhs.ty, qsc_hir::ty::Ty::Array(..)); let lhs = self.lower_expr(lhs); if is_array { // The left-hand side of an array append is not really an expression to be - // executed, so remove any added nodes from the CFG. - self.cfg.drain(idx..); + // executed, so remove any added nodes from the execution graph. + self.exec_graph.drain(idx..); } - let idx = self.cfg.len(); + let idx = self.exec_graph.len(); if matches!(op, hir::BinOp::AndL | hir::BinOp::OrL) { // Put in a placeholder jump for what will be the short-circuit - self.cfg.push(CfgNode::Jump(0)); + self.exec_graph.push(ExecGraphNode::Jump(0)); } else if !is_array { - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); } let rhs = self.lower_expr(rhs); match op { hir::BinOp::AndL => { - self.cfg[idx] = CfgNode::JumpIfNot( - self.cfg + self.exec_graph[idx] = ExecGraphNode::JumpIfNot( + self.exec_graph .len() .try_into() .expect("nodes should fit into u32"), ); } hir::BinOp::OrL => { - self.cfg[idx] = CfgNode::JumpIf( - self.cfg + self.exec_graph[idx] = ExecGraphNode::JumpIf( + self.exec_graph .len() .try_into() .expect("nodes should fit into u32"), @@ -386,37 +397,37 @@ impl Lowerer { hir::ExprKind::AssignField(container, field, replace) => { let field = lower_field(field); let replace = self.lower_expr(replace); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); let container = self.lower_expr(container); fir::ExprKind::AssignField(container, field, replace) } hir::ExprKind::AssignIndex(container, index, replace) => { let index = self.lower_expr(index); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); let replace = self.lower_expr(replace); - let idx = self.cfg.len(); + let idx = self.exec_graph.len(); let container = self.lower_expr(container); // The left-hand side of an array index assignment is not really an expression to be - // executed, so remove any added nodes from the CFG. - self.cfg.drain(idx..); + // executed, so remove any added nodes from the exection graph. + self.exec_graph.drain(idx..); fir::ExprKind::AssignIndex(container, index, replace) } hir::ExprKind::BinOp(op, lhs, rhs) => { let lhs = self.lower_expr(lhs); - let idx = self.cfg.len(); + let idx = self.exec_graph.len(); if matches!(op, hir::BinOp::AndL | hir::BinOp::OrL) { // Put in a placeholder jump for what will be the short-circuit - self.cfg.push(CfgNode::Jump(0)); + self.exec_graph.push(ExecGraphNode::Jump(0)); } else { - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); } let rhs = self.lower_expr(rhs); match op { // If the operator is logical AND, update the placeholder to skip the // right-hand side if the left-hand side is false hir::BinOp::AndL => { - self.cfg[idx] = CfgNode::JumpIfNot( - self.cfg + self.exec_graph[idx] = ExecGraphNode::JumpIfNot( + self.exec_graph .len() .try_into() .expect("nodes should fit into u32"), @@ -425,8 +436,8 @@ impl Lowerer { // If the operator is logical OR, update the placeholder to skip the // right-hand side if the left-hand side is true hir::BinOp::OrL => { - self.cfg[idx] = CfgNode::JumpIf( - self.cfg + self.exec_graph[idx] = ExecGraphNode::JumpIf( + self.exec_graph .len() .try_into() .expect("nodes should fit into u32"), @@ -439,7 +450,7 @@ impl Lowerer { hir::ExprKind::Block(block) => fir::ExprKind::Block(self.lower_block(block)), hir::ExprKind::Call(callee, arg) => { let call = self.lower_expr(callee); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); let arg = self.lower_expr(arg); fir::ExprKind::Call(call, arg) } @@ -451,18 +462,18 @@ impl Lowerer { } hir::ExprKind::If(cond, if_true, if_false) => { let cond = self.lower_expr(cond); - let branch_idx = self.cfg.len(); - // Put a placeholder in the CFG for the jump past the true branch - self.cfg.push(CfgNode::Jump(0)); + let branch_idx = self.exec_graph.len(); + // Put a placeholder in the execution graph for the jump past the true branch + self.exec_graph.push(ExecGraphNode::Jump(0)); let if_true = self.lower_expr(if_true); let (if_false, else_idx) = if let Some(if_false) = if_false.as_ref() { - // Put a placeholder in the CFG for the jump past the false branch - let idx = self.cfg.len(); - self.cfg.push(CfgNode::Jump(0)); + // Put a placeholder in the execution graph for the jump past the false branch + let idx = self.exec_graph.len(); + self.exec_graph.push(ExecGraphNode::Jump(0)); let if_false = self.lower_expr(if_false); // Update the placeholder to skip over the false branch - self.cfg[idx] = CfgNode::Jump( - self.cfg + self.exec_graph[idx] = ExecGraphNode::Jump( + self.exec_graph .len() .try_into() .expect("nodes should fit into u32"), @@ -471,18 +482,19 @@ impl Lowerer { } else { // An if-expr without an else cannot return a value, so we need to // insert a no-op to ensure a Unit value is returned for the expr. - let idx = self.cfg.len(); - self.cfg.push(CfgNode::Unit); + let idx = self.exec_graph.len(); + self.exec_graph.push(ExecGraphNode::Unit); (None, idx) }; // Update the placeholder to skip the true branch if the condition is false - self.cfg[branch_idx] = - CfgNode::JumpIfNot(else_idx.try_into().expect("nodes should fit into u32")); + self.exec_graph[branch_idx] = ExecGraphNode::JumpIfNot( + else_idx.try_into().expect("nodes should fit into u32"), + ); fir::ExprKind::If(cond, if_true, if_false) } hir::ExprKind::Index(container, index) => { let container = self.lower_expr(container); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); let index = self.lower_expr(index); fir::ExprKind::Index(container, index) } @@ -490,18 +502,18 @@ impl Lowerer { hir::ExprKind::Range(start, step, end) => { let start = start.as_ref().map(|s| self.lower_expr(s)); if start.is_some() { - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); } let step = step.as_ref().map(|s| self.lower_expr(s)); if step.is_some() { - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); } let end = end.as_ref().map(|e| self.lower_expr(e)); fir::ExprKind::Range(start, step, end) } hir::ExprKind::Return(expr) => { let expr = self.lower_expr(expr); - self.cfg.push(CfgNode::Ret); + self.exec_graph.push(ExecGraphNode::Ret); fir::ExprKind::Return(expr) } hir::ExprKind::Tuple(items) => fir::ExprKind::Tuple( @@ -509,7 +521,7 @@ impl Lowerer { .iter() .map(|i| { let i = self.lower_expr(i); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); i }) .collect(), @@ -518,25 +530,25 @@ impl Lowerer { fir::ExprKind::UnOp(lower_unop(*op), self.lower_expr(operand)) } hir::ExprKind::While(cond, body) => { - let cond_idx = self.cfg.len(); + let cond_idx = self.exec_graph.len(); let cond = self.lower_expr(cond); - let idx = self.cfg.len(); - // Put a placeholder in the CFG for the jump past the loop - self.cfg.push(CfgNode::Jump(0)); + let idx = self.exec_graph.len(); + // Put a placeholder in the execution graph for the jump past the loop + self.exec_graph.push(ExecGraphNode::Jump(0)); let body = self.lower_block(body); - self.cfg.push(CfgNode::Jump( + self.exec_graph.push(ExecGraphNode::Jump( cond_idx.try_into().expect("nodes should fit into u32"), )); // Update the placeholder to skip the loop if the condition is false - self.cfg[idx] = CfgNode::JumpIfNot( - self.cfg + self.exec_graph[idx] = ExecGraphNode::JumpIfNot( + self.exec_graph .len() .try_into() .expect("nodes should fit into u32"), ); // While-exprs never have a return value, so we need to insert a no-op to ensure // a Unit value is returned for the expr. - self.cfg.push(CfgNode::Unit); + self.exec_graph.push(ExecGraphNode::Unit); fir::ExprKind::While(cond, body) } hir::ExprKind::Closure(ids, id) => { @@ -551,16 +563,16 @@ impl Lowerer { ), hir::ExprKind::UpdateIndex(lhs, mid, rhs) => { let mid = self.lower_expr(mid); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); let rhs = self.lower_expr(rhs); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); let lhs = self.lower_expr(lhs); fir::ExprKind::UpdateIndex(lhs, mid, rhs) } hir::ExprKind::UpdateField(record, field, replace) => { let field = lower_field(field); let replace = self.lower_expr(replace); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); let record = self.lower_expr(record); fir::ExprKind::UpdateField(record, field, replace) } @@ -584,8 +596,8 @@ impl Lowerer { | fir::ExprKind::Return(..) | fir::ExprKind::While(..) => {} - // All other expressions should be added to the CFG. - _ => self.cfg.push(CfgNode::Expr(id)), + // All other expressions should be added to the execution graph. + _ => self.exec_graph.push(ExecGraphNode::Expr(id)), } let expr = fir::Expr { @@ -593,7 +605,7 @@ impl Lowerer { span: expr.span, ty, kind, - cfg_range: cfg_start_idx..self.cfg.len(), + exec_graph_range: graph_start_idx..self.exec_graph.len(), }; self.exprs.insert(id, expr); id @@ -603,7 +615,7 @@ impl Lowerer { match component { hir::StringComponent::Expr(expr) => { let expr = self.lower_expr(expr); - self.cfg.push(CfgNode::Store); + self.exec_graph.push(ExecGraphNode::Store); fir::StringComponent::Expr(expr) } hir::StringComponent::Lit(str) => fir::StringComponent::Lit(Rc::clone(str)), diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index cc6dea507c..2cf3ea8fb6 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -9,13 +9,14 @@ use std::rc::Rc; use crate::{ backend::{Backend, SparseSim}, debug::{map_hir_package_to_fir, Frame}, + exec_graph_section, output::{GenericReceiver, Receiver}, - sub_cfg, val, Env, Error, State, StepAction, StepResult, Value, + val, Env, Error, State, StepAction, StepResult, Value, }; use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_fir::fir::{self, CfgNode, StmtId}; +use qsc_fir::fir::{self, ExecGraphNode, StmtId}; use qsc_fir::fir::{PackageId, PackageStoreLookup}; use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; use qsc_passes::{run_core_passes, run_default_passes, PackageType}; @@ -24,15 +25,15 @@ use qsc_passes::{run_core_passes, run_default_passes, PackageType}; /// Creates a new environment and simulator. /// # Errors /// Returns the first error encountered during execution. -pub(super) fn eval_cfg( - cfg: Rc<[CfgNode]>, +pub(super) fn eval_graph( + graph: Rc<[ExecGraphNode]>, sim: &mut impl Backend>, globals: &impl PackageStoreLookup, package: PackageId, env: &mut Env, out: &mut impl Receiver, ) -> Result)> { - let mut state = State::new(package, cfg, None); + let mut state = State::new(package, graph, None); let StepResult::Return(value) = state.eval(globals, env, sim, out, &[], StepAction::Continue)? else { @@ -77,7 +78,7 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) { ); assert!(pass_errors.is_empty(), "{pass_errors:?}"); let unit_fir = fir_lowerer.lower_package(&unit.package); - let entry = unit_fir.entry_cfg.clone(); + let entry = unit_fir.entry_exec_graph.clone(); let id = store.insert(unit); let mut fir_store = fir::PackageStore::new(); @@ -89,7 +90,7 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) { fir_store.insert(map_hir_package_to_fir(id), unit_fir); let mut out = Vec::new(); - match eval_cfg( + match eval_graph( entry, &mut SparseSim::new(), &fir_store, @@ -146,7 +147,7 @@ fn check_partial_eval_stmt( let unit_fir = fir_lowerer.lower_package(&unit.package); fir_expect.assert_eq(&unit_fir.to_string()); - let entry = unit_fir.entry_cfg.clone(); + let entry = unit_fir.entry_exec_graph.clone(); let id = store.insert(unit); let mut fir_store = fir::PackageStore::new(); @@ -163,8 +164,8 @@ fn check_partial_eval_stmt( let (last_stmt, most_stmts) = stmts.split_last().expect("should have at least one stmt"); for stmt_id in most_stmts { let stmt = fir_store.get_stmt((id, *stmt_id).into()); - match eval_cfg( - sub_cfg(&entry, stmt.cfg_range.clone()), + match eval_graph( + exec_graph_section(&entry, stmt.exec_graph_range.clone()), &mut SparseSim::new(), &fir_store, id, @@ -177,8 +178,8 @@ fn check_partial_eval_stmt( } let stmt = fir_store.get_stmt((id, *last_stmt).into()); - match eval_cfg( - sub_cfg(&entry, stmt.cfg_range.clone()), + match eval_graph( + exec_graph_section(&entry, stmt.exec_graph_range.clone()), &mut SparseSim::new(), &fir_store, id, diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs index 41eac08fef..e769910bf4 100644 --- a/compiler/qsc_fir/src/fir.rs +++ b/compiler/qsc_fir/src/fir.rs @@ -556,7 +556,7 @@ pub struct Package { /// The entry expression for an executable package. pub entry: Option, /// The control flow graph for the entry expression in the package. - pub entry_cfg: Rc<[CfgNode]>, + pub entry_exec_graph: Rc<[ExecGraphNode]>, /// The blocks in the package. pub blocks: IndexMap, /// The expressions in the package. @@ -864,7 +864,7 @@ pub struct SpecDecl { /// The input of the specialization. pub input: Option, /// The flattened control flow graph for the execution of the specialization. - pub cfg: Rc<[CfgNode]>, + pub exec_graph: Rc<[ExecGraphNode]>, } impl Display for SpecDecl { @@ -879,7 +879,7 @@ impl Display for SpecDecl { #[derive(Copy, Clone, Debug, PartialEq)] /// A node within the control flow graph. -pub enum CfgNode { +pub enum ExecGraphNode { /// A binding of a value to a variable. Bind(PatId), /// An expression to execute. @@ -945,7 +945,7 @@ pub struct Stmt { /// The statement kind. pub kind: StmtKind, /// The locations within the containing control flow graph for the current statement. - pub cfg_range: ops::Range, + pub exec_graph_range: ops::Range, } impl Display for Stmt { @@ -997,7 +997,7 @@ pub struct Expr { /// The expression kind. pub kind: ExprKind, /// The locations within the containing control flow graph for the current expression. - pub cfg_range: ops::Range, + pub exec_graph_range: ops::Range, } impl Display for Expr { From dd169e08ac055ef5ca9c92533ace6dfe72161fad Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Mon, 18 Mar 2024 13:20:39 -0700 Subject: [PATCH 10/16] Rename `EvalRange` to `ValueRange` --- compiler/qsc/src/interpret.rs | 2 +- library/src/tests/core.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs index 882a54bad6..fe6aa4b8e8 100644 --- a/compiler/qsc/src/interpret.rs +++ b/compiler/qsc/src/interpret.rs @@ -15,7 +15,7 @@ pub use qsc_eval::{ debug::Frame, output::{self, GenericReceiver}, val::Closure, - val::Range as EvalRange, + val::Range as ValueRange, val::Result, val::Value, StepAction, StepResult, diff --git a/library/src/tests/core.rs b/library/src/tests/core.rs index 539e447aa7..039eae1262 100644 --- a/library/src/tests/core.rs +++ b/library/src/tests/core.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use super::test_expression; -use qsc::interpret::{EvalRange, Value}; +use qsc::interpret::{Value, ValueRange}; // Tests for Microsoft.Quantum.Core namespace @@ -76,7 +76,7 @@ fn check_range_reverse_1_5() { test_expression( "RangeReverse(1..5)", &Value::Range( - EvalRange { + ValueRange { start: Some(5), step: -1, end: Some(1), @@ -91,7 +91,7 @@ fn check_range_reverse_1_n1_5() { test_expression( "RangeReverse(1..-1..5)", &Value::Range( - EvalRange { + ValueRange { start: Some(5), step: 1, end: Some(1), @@ -106,7 +106,7 @@ fn check_range_reverse_1_7_10() { test_expression( "RangeReverse(1..7..10)", &Value::Range( - EvalRange { + ValueRange { start: Some(8), step: -7, end: Some(1), From db0a6f93b98eadcbc49b53433c2e65ee8ea73abb Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Mon, 18 Mar 2024 15:07:44 -0700 Subject: [PATCH 11/16] Update frame leave logic --- compiler/qsc_eval/src/lib.rs | 13 +++++-------- compiler/qsc_eval/src/lower.rs | 6 +----- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index a7a514d874..9f023d64b4 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -438,12 +438,9 @@ impl State { fn leave_frame(&mut self) { if let Some(frame) = self.call_stack.pop_frame() { self.package = frame.caller; - self.val_stack.pop(); - self.idx = self - .idx_stack - .pop() - .expect("should have at least one index"); - } + }; + self.val_stack.pop(); + self.idx = self.idx_stack.pop().unwrap_or_default(); self.exec_graph_stack.pop(); } @@ -590,9 +587,9 @@ impl State { None => { // We have reached the end of the current graph without reaching an explicit return node, // usually indicating the partial execution of a single sub-expression. - // This means we should leave the current frame but not the current environment scope, + // This means we should pop the execution graph but not the current environment scope, // so bound variables are still accessible after completion. - self.leave_frame(); + self.exec_graph_stack.pop(); assert!(self.exec_graph_stack.is_empty()); continue; } diff --git a/compiler/qsc_eval/src/lower.rs b/compiler/qsc_eval/src/lower.rs index 4d9af8e076..ca5c163187 100644 --- a/compiler/qsc_eval/src/lower.rs +++ b/compiler/qsc_eval/src/lower.rs @@ -53,11 +53,7 @@ impl Lowerer { pub fn lower_package(&mut self, package: &hir::Package) -> fir::Package { let entry = package.entry.as_ref().map(|e| self.lower_expr(e)); - let entry_exec_graph = self - .exec_graph - .drain(..) - .chain(once(ExecGraphNode::Ret)) - .collect(); + let entry_exec_graph = self.exec_graph.drain(..).collect(); let items: IndexMap = package .items .values() From 245f17d7373596bb1cc27489860a80ff6fcdc26e Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 19 Mar 2024 11:23:51 -0700 Subject: [PATCH 12/16] PR feedback --- compiler/qsc_eval/src/lib.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 9f023d64b4..14d6913f78 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -1,6 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +//! The Q# evaluator handles the execution of Q# programs and/or fragments. +//! It operates based on vectors of `ExecGraphNode` instances, which act as a control flow graph +//! and are generated by lowering to FIR. The evaluator will iterate through the given graph, +//! executing the instructions it encounters and updating the state it was given accordingly, and using +//! the FIR store to look up graphs for any called functions or operations. The evaluator handles tracking +//! of stack frames and push/pop of variable scopes, and uses the index into the current execution graph +//! as a kind of stack pointer, updating the index based on `Jump`, `JumpIf`, and `JumpIfNot` instructions. +//! +//! Of note, the evaluator does not own the program state, which is tracked by the passed in `Env` +//! and `Backend` instances. This allows the evaluator to be reentrant, and supports both whole-program, +//! effectively stateless execution (like running shots of a program) stateful execution scenarios +//! (like debugging or notebooks). + #[cfg(test)] mod tests; @@ -301,6 +314,14 @@ impl Range { pub struct Env(Vec); +impl Default for Env { + #[must_use] + fn default() -> Self { + // Always create a global scope for top-level statements. + Self(vec![Scope::default()]) + } +} + impl Env { #[must_use] fn get(&self, id: LocalVarId) -> Option<&Variable> { @@ -373,13 +394,6 @@ struct Scope { frame_id: usize, } -impl Default for Env { - #[must_use] - fn default() -> Self { - Self(vec![Scope::default()]) - } -} - pub struct State { exec_graph_stack: Vec>, idx: u32, From 151e8a12b0a53a9c26b569f3222ffda9ced84b08 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 19 Mar 2024 11:35:13 -0700 Subject: [PATCH 13/16] Fix FIR local idents --- compiler/qsc_eval/src/tests.rs | 60 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index 2cf3ea8fb6..9ff7984d61 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3741,10 +3741,10 @@ fn partial_eval_stmt_with_bound_variable() { Expr 25960 [0-19] [Type Unit]: Expr Block: 2097 Expr 25961 [9-10] [Type Int]: Lit: Int(3) Expr 25962 [12-15] [Type Int]: Expr Block: 2098 - Expr 25963 [13-14] [Type Int]: Var: Local 0 + Expr 25963 [13-14] [Type Int]: Var: Local 22 Expr 25964 [16-18] [Type Unit]: Unit Pats: - Pat 2896 [5-6] [Type Int]: Bind: Ident 0 [5-6] "x""#]], + Pat 2896 [5-6] [Type Int]: Bind: Ident 22 [5-6] "x""#]], &expect!["3"], ); } @@ -3781,19 +3781,19 @@ fn partial_eval_stmt_with_mutable_variable_update() { Expr 25962 [16-26] [Type Unit]: AssignOp (Add): 25963 25964 - Expr 25963 [20-21] [Type Int]: Var: Local 0 + Expr 25963 [20-21] [Type Int]: Var: Local 22 Expr 25964 [25-26] [Type Int]: Lit: Int(1) Expr 25965 [28-31] [Type Int]: Expr Block: 2098 - Expr 25966 [29-30] [Type Int]: Var: Local 0 + Expr 25966 [29-30] [Type Int]: Var: Local 22 Expr 25967 [32-42] [Type Unit]: Assign: 25968 25969 - Expr 25968 [36-37] [Type Int]: Var: Local 0 + Expr 25968 [36-37] [Type Int]: Var: Local 22 Expr 25969 [40-42] [Type Int]: UnOp (Neg): 25970 Expr 25970 [41-42] [Type Int]: Lit: Int(1) Pats: - Pat 2896 [9-10] [Type Int]: Bind: Ident 0 [9-10] "x""#]], + Pat 2896 [9-10] [Type Int]: Bind: Ident 22 [9-10] "x""#]], &expect!["1"], ); } @@ -3830,19 +3830,19 @@ fn partial_eval_stmt_with_mutable_variable_update_out_of_order_works() { Expr 25962 [16-26] [Type Unit]: AssignOp (Add): 25963 25964 - Expr 25963 [20-21] [Type Int]: Var: Local 0 + Expr 25963 [20-21] [Type Int]: Var: Local 22 Expr 25964 [25-26] [Type Int]: Lit: Int(1) Expr 25965 [28-31] [Type Int]: Expr Block: 2098 - Expr 25966 [29-30] [Type Int]: Var: Local 0 + Expr 25966 [29-30] [Type Int]: Var: Local 22 Expr 25967 [32-42] [Type Unit]: Assign: 25968 25969 - Expr 25968 [36-37] [Type Int]: Var: Local 0 + Expr 25968 [36-37] [Type Int]: Var: Local 22 Expr 25969 [40-42] [Type Int]: UnOp (Neg): 25970 Expr 25970 [41-42] [Type Int]: Lit: Int(1) Pats: - Pat 2896 [9-10] [Type Int]: Bind: Ident 0 [9-10] "x""#]], + Pat 2896 [9-10] [Type Int]: Bind: Ident 22 [9-10] "x""#]], &expect!["-1"], ); } @@ -3884,19 +3884,19 @@ fn partial_eval_stmt_with_mutable_variable_update_repeat_stmts_works() { Expr 25962 [16-26] [Type Unit]: AssignOp (Add): 25963 25964 - Expr 25963 [20-21] [Type Int]: Var: Local 0 + Expr 25963 [20-21] [Type Int]: Var: Local 22 Expr 25964 [25-26] [Type Int]: Lit: Int(1) Expr 25965 [28-31] [Type Int]: Expr Block: 2098 - Expr 25966 [29-30] [Type Int]: Var: Local 0 + Expr 25966 [29-30] [Type Int]: Var: Local 22 Expr 25967 [32-42] [Type Unit]: Assign: 25968 25969 - Expr 25968 [36-37] [Type Int]: Var: Local 0 + Expr 25968 [36-37] [Type Int]: Var: Local 22 Expr 25969 [40-42] [Type Int]: UnOp (Neg): 25970 Expr 25970 [41-42] [Type Int]: Lit: Int(1) Pats: - Pat 2896 [9-10] [Type Int]: Bind: Ident 0 [9-10] "x""#]], + Pat 2896 [9-10] [Type Int]: Bind: Ident 22 [9-10] "x""#]], &expect!["2"], ); } @@ -3932,11 +3932,11 @@ fn partial_eval_stmt_with_bool_short_circuit() { Expr 25963 [17-27] [Type Bool]: BinOp (OrL): 25964 25965 - Expr 25964 [17-18] [Type Bool]: Var: Local 0 + Expr 25964 [17-18] [Type Bool]: Var: Local 22 Expr 25965 [22-27] [Type Bool]: Lit: Bool(false) Expr 25966 [30-32] [Type Unit]: Unit Pats: - Pat 2896 [5-6] [Type Bool]: Bind: Ident 0 [5-6] "x""#]], + Pat 2896 [5-6] [Type Bool]: Bind: Ident 22 [5-6] "x""#]], &expect!["true"], ); } @@ -3972,11 +3972,11 @@ fn partial_eval_stmt_with_bool_no_short_circuit() { Expr 25963 [18-27] [Type Bool]: BinOp (OrL): 25964 25965 - Expr 25964 [18-19] [Type Bool]: Var: Local 0 + Expr 25964 [18-19] [Type Bool]: Var: Local 22 Expr 25965 [23-27] [Type Bool]: Lit: Bool(true) Expr 25966 [30-32] [Type Unit]: Unit Pats: - Pat 2896 [5-6] [Type Bool]: Bind: Ident 0 [5-6] "x""#]], + Pat 2896 [5-6] [Type Bool]: Bind: Ident 22 [5-6] "x""#]], &expect!["true"], ); } @@ -4019,18 +4019,18 @@ fn partial_eval_stmt_with_loop() { Expr 25963 [22-27] [Type Bool]: BinOp (Lt): 25964 25965 - Expr 25964 [22-23] [Type Int]: Var: Local 0 + Expr 25964 [22-23] [Type Int]: Var: Local 22 Expr 25965 [26-27] [Type Int]: Lit: Int(3) Expr 25966 [30-40] [Type Unit]: AssignOp (Add): 25967 25968 - Expr 25967 [34-35] [Type Int]: Var: Local 0 + Expr 25967 [34-35] [Type Int]: Var: Local 22 Expr 25968 [39-40] [Type Int]: Lit: Int(1) Expr 25969 [44-47] [Type Int]: Expr Block: 2099 - Expr 25970 [45-46] [Type Int]: Var: Local 0 + Expr 25970 [45-46] [Type Int]: Var: Local 22 Expr 25971 [48-50] [Type Unit]: Unit Pats: - Pat 2896 [9-10] [Type Int]: Bind: Ident 0 [9-10] "x""#]], + Pat 2896 [9-10] [Type Int]: Bind: Ident 22 [9-10] "x""#]], &expect!["3"], ); } @@ -4050,11 +4050,11 @@ fn partial_eval_stmt_function_calls() { Entry Expression: 25960 Items: Item 0 [41-102] (Public): - Namespace (Ident 1 [51-55] "Test"): Item 1 + Namespace (Ident 23 [51-55] "Test"): Item 1 Item 1 [62-100] (Public): Parent: 0 Callable 0 [62-100] (function): - name: Ident 2 [71-75] "Add1" + name: Ident 0 [71-75] "Add1" input: 2897 output: Int functors: empty set @@ -4089,7 +4089,7 @@ fn partial_eval_stmt_function_calls() { Expr 25962 [9-18] [Type (Int -> Int)]: Var: Item 1 Expr 25963 [19-20] [Type Int]: Lit: Int(4) Expr 25964 [23-26] [Type Int]: Expr Block: 2098 - Expr 25965 [24-25] [Type Int]: Var: Local 0 + Expr 25965 [24-25] [Type Int]: Var: Local 22 Expr 25966 [27-39] [Type Int]: Call: 25967 25968 @@ -4098,11 +4098,11 @@ fn partial_eval_stmt_function_calls() { Expr 25969 [93-98] [Type Int]: BinOp (Add): 25970 25971 - Expr 25970 [93-94] [Type Int]: Var: Local 3 + Expr 25970 [93-94] [Type Int]: Var: Local 1 Expr 25971 [97-98] [Type Int]: Lit: Int(1) Pats: - Pat 2896 [5-6] [Type Int]: Bind: Ident 0 [5-6] "x" - Pat 2897 [76-83] [Type Int]: Bind: Ident 3 [76-77] "x""#]], + Pat 2896 [5-6] [Type Int]: Bind: Ident 22 [5-6] "x" + Pat 2897 [76-83] [Type Int]: Bind: Ident 1 [76-77] "x""#]], &expect!["5"], ); } @@ -4148,10 +4148,10 @@ fn partial_eval_stmt_function_calls_from_library() { res: Item 1 (Package 0) generics: Int - Expr 25968 [28-29] [Type (Int)[]]: Var: Local 0 + Expr 25968 [28-29] [Type (Int)[]]: Var: Local 22 Expr 25969 [32-33] [Type Int]: Lit: Int(3) Pats: - Pat 2896 [5-6] [Type (Int)[]]: Bind: Ident 0 [5-6] "x""#]], + Pat 2896 [5-6] [Type (Int)[]]: Bind: Ident 22 [5-6] "x""#]], &expect!["3"], ); } From 329cb4543c3e669077bc288885a0e42134c8a4f2 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 19 Mar 2024 13:51:34 -0700 Subject: [PATCH 14/16] Update member name --- compiler/qsc_eval/src/lib.rs | 126 ++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 14d6913f78..2453f5657d 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -398,7 +398,7 @@ pub struct State { exec_graph_stack: Vec>, idx: u32, idx_stack: Vec, - curr_val: Option, + val_register: Option, val_stack: Vec>, package: PackageId, call_stack: CallStack, @@ -421,7 +421,7 @@ impl State { exec_graph_stack: vec![exec_graph], idx: 0, idx_stack: Vec::new(), - curr_val: None, + val_register: None, val_stack: vec![Vec::new()], package, call_stack: CallStack::default(), @@ -462,12 +462,12 @@ impl State { env.push_scope(self.call_stack.len()); } - fn take_curr_val(&mut self) -> Value { - self.curr_val.take().expect("value should be present") + fn take_val_register(&mut self) -> Value { + self.val_register.take().expect("value should be present") } - fn set_curr_val(&mut self, val: Value) { - self.curr_val = Some(val); + fn set_val_register(&mut self, val: Value) { + self.val_register = Some(val); } fn pop_val(&mut self) -> Value { @@ -487,7 +487,7 @@ impl State { } fn push_val(&mut self) { - let val = self.take_curr_val(); + let val = self.take_val_register(); self.val_stack .last_mut() .expect("should have at least one value frame") @@ -566,7 +566,7 @@ impl State { continue; } Some(ExecGraphNode::JumpIf(idx)) => { - let cond = self.curr_val == Some(Value::Bool(true)); + let cond = self.val_register == Some(Value::Bool(true)); if cond { self.idx = *idx; } else { @@ -575,7 +575,7 @@ impl State { continue; } Some(ExecGraphNode::JumpIfNot(idx)) => { - let cond = self.curr_val == Some(Value::Bool(true)); + let cond = self.val_register == Some(Value::Bool(true)); if cond { self.idx += 1; } else { @@ -590,7 +590,7 @@ impl State { } Some(ExecGraphNode::Unit) => { self.idx += 1; - self.set_curr_val(Value::unit()); + self.set_val_register(Value::unit()); continue; } Some(ExecGraphNode::Ret) => { @@ -623,7 +623,7 @@ impl State { // Some executions don't have any statements to execute, // such as a fragment that has only item definitions. // In that case, the values are empty and the result is unit. - self.curr_val.take().unwrap_or_else(Value::unit) + self.val_register.take().unwrap_or_else(Value::unit) } #[allow(clippy::similar_names)] @@ -652,10 +652,10 @@ impl State { self.eval_array_append_in_place(env, globals, *lhs)?; return Ok(()); } - let rhs_val = self.take_curr_val(); + let rhs_val = self.take_val_register(); self.eval_expr(env, sim, globals, out, *lhs)?; self.push_val(); - self.set_curr_val(rhs_val); + self.set_val_register(rhs_val); } self.eval_binop(*op, rhs_span)?; self.eval_assign(env, globals, *lhs)?; @@ -689,11 +689,11 @@ impl State { } ExprKind::Closure(args, callable) => { let closure = resolve_closure(env, self.package, expr.span, args, *callable)?; - self.set_curr_val(closure); + self.set_val_register(closure); } ExprKind::Fail(..) => { return Err(Error::UserFail( - self.take_curr_val().unwrap_string().to_string(), + self.take_val_register().unwrap_string().to_string(), self.to_global_span(expr.span), )); } @@ -707,7 +707,7 @@ impl State { self.eval_index(rhs_span)?; } ExprKind::Lit(lit) => { - self.set_curr_val(lit_to_val(lit)); + self.set_val_register(lit_to_val(lit)); } ExprKind::Range(start, step, end) => { self.eval_range(start.is_some(), step.is_some(), end.is_some()); @@ -724,7 +724,7 @@ impl State { self.eval_update_field(field.clone()); } ExprKind::Var(res, _) => { - self.set_curr_val(resolve_binding(env, self.package, *res, expr.span)?); + self.set_val_register(resolve_binding(env, self.package, *res, expr.span)?); } ExprKind::While(..) => { panic!("while expr should be handled by control flow") @@ -736,7 +736,7 @@ impl State { fn collect_string(&mut self, components: &[StringComponent]) { if let [StringComponent::Lit(str)] = components { - self.set_curr_val(Value::String(Rc::clone(str))); + self.set_val_register(Value::String(Rc::clone(str))); return; } @@ -752,12 +752,12 @@ impl State { } } } - self.set_curr_val(Value::String(Rc::from(string))); + self.set_val_register(Value::String(Rc::from(string))); } fn eval_arr(&mut self, len: usize) { let arr = self.pop_vals(len); - self.set_curr_val(Value::Array(arr.into())); + self.set_val_register(Value::Array(arr.into())); } fn eval_arr_lit(&mut self, arr: &Vec, globals: &impl PackageStoreLookup) { @@ -770,7 +770,7 @@ impl State { .expect("array should be uniquely referenced") .push(lit_to_val(lit)); } - self.set_curr_val(Value::Array(new_arr)); + self.set_val_register(Value::Array(new_arr)); } fn eval_array_append_in_place( @@ -780,7 +780,7 @@ impl State { lhs: ExprId, ) -> Result<(), Error> { let lhs = globals.get_expr((self.package, lhs).into()); - let rhs = self.take_curr_val(); + let rhs = self.take_val_register(); match (&lhs.kind, rhs) { (&ExprKind::Var(Res::Local(id), _), rhs) => match env.get_mut(id) { Some(var) => { @@ -794,7 +794,7 @@ impl State { } fn eval_arr_repeat(&mut self, span: Span) -> Result<(), Error> { - let size_val = self.take_curr_val().unwrap_int(); + let size_val = self.take_val_register().unwrap_int(); let item_val = self.pop_val(); let s = match size_val.try_into() { Ok(i) => Ok(i), @@ -803,7 +803,7 @@ impl State { self.to_global_span(span), )), }?; - self.set_curr_val(Value::Array(vec![item_val; s].into())); + self.set_val_register(Value::Array(vec![item_val; s].into())); Ok(()) } @@ -813,12 +813,12 @@ impl State { globals: &impl PackageStoreLookup, lhs: ExprId, ) -> Result<(), Error> { - let rhs = self.take_curr_val(); + let rhs = self.take_val_register(); self.update_binding(env, globals, lhs, rhs) } fn eval_bind(&mut self, env: &mut Env, globals: &impl PackageStoreLookup, pat: PatId) { - let val = self.take_curr_val(); + let val = self.take_val_register(); self.bind_value(env, globals, pat, val); } @@ -828,9 +828,9 @@ impl State { BinOp::AndB => self.eval_binop_simple(eval_binop_andb), BinOp::Div => self.eval_binop_with_error(span, eval_binop_div)?, BinOp::Eq => { - let rhs_val = self.take_curr_val(); + let rhs_val = self.take_val_register(); let lhs_val = self.pop_val(); - self.set_curr_val(Value::Bool(lhs_val == rhs_val)); + self.set_val_register(Value::Bool(lhs_val == rhs_val)); } BinOp::Exp => self.eval_binop_with_error(span, eval_binop_exp)?, BinOp::Gt => self.eval_binop_simple(eval_binop_gt), @@ -840,9 +840,9 @@ impl State { BinOp::Mod => self.eval_binop_with_error(span, eval_binop_mod)?, BinOp::Mul => self.eval_binop_simple(eval_binop_mul), BinOp::Neq => { - let rhs_val = self.take_curr_val(); + let rhs_val = self.take_val_register(); let lhs_val = self.pop_val(); - self.set_curr_val(Value::Bool(lhs_val != rhs_val)); + self.set_val_register(Value::Bool(lhs_val != rhs_val)); } BinOp::OrB => self.eval_binop_simple(eval_binop_orb), BinOp::Shl => self.eval_binop_with_error(span, eval_binop_shl)?, @@ -857,9 +857,9 @@ impl State { } fn eval_binop_simple(&mut self, binop_func: impl FnOnce(Value, Value) -> Value) { - let rhs_val = self.take_curr_val(); + let rhs_val = self.take_val_register(); let lhs_val = self.pop_val(); - self.set_curr_val(binop_func(lhs_val, rhs_val)); + self.set_val_register(binop_func(lhs_val, rhs_val)); } fn eval_binop_with_error( @@ -868,9 +868,9 @@ impl State { binop_func: impl FnOnce(Value, Value, PackageSpan) -> Result, ) -> Result<(), Error> { let span = self.to_global_span(span); - let rhs_val = self.take_curr_val(); + let rhs_val = self.take_val_register(); let lhs_val = self.pop_val(); - self.set_curr_val(binop_func(lhs_val, rhs_val, span)?); + self.set_val_register(binop_func(lhs_val, rhs_val, span)?); Ok(()) } @@ -883,7 +883,7 @@ impl State { arg_span: Span, out: &mut impl Receiver, ) -> Result<(), Error> { - let arg = self.take_curr_val(); + let arg = self.take_val_register(); let (callee_id, functor, fixed_args) = match self.pop_val() { Value::Closure(inner) => (inner.id, inner.functor, Some(inner.fixed_args)), Value::Global(id, functor) => (id, functor, None), @@ -895,7 +895,7 @@ impl State { let callee = match globals.get_global(callee_id) { Some(Global::Callable(callable)) => callable, Some(Global::Udt) => { - self.set_curr_val(arg); + self.set_val_register(arg); return Ok(()); } None => return Err(Error::UnboundName(self.to_global_span(callable_span))), @@ -924,7 +924,7 @@ impl State { callee_span, )); } - self.set_curr_val(val); + self.set_val_register(val); self.leave_frame(); Ok(()) } @@ -954,7 +954,7 @@ impl State { } fn eval_field(&mut self, field: Field) { - let record = self.take_curr_val(); + let record = self.take_val_register(); let val = match (record, field) { (Value::Range(inner), Field::Prim(PrimField::Start)) => Value::Int( inner @@ -972,16 +972,18 @@ impl State { } _ => panic!("invalid field access"), }; - self.set_curr_val(val); + self.set_val_register(val); } fn eval_index(&mut self, span: Span) -> Result<(), Error> { - let index_val = self.take_curr_val(); + let index_val = self.take_val_register(); let arr = self.pop_val().unwrap_array(); match &index_val { - Value::Int(i) => self.set_curr_val(index_array(&arr, *i, self.to_global_span(span))?), + Value::Int(i) => { + self.set_val_register(index_array(&arr, *i, self.to_global_span(span))?) + } Value::Range(inner) => { - self.set_curr_val(slice_array( + self.set_val_register(slice_array( &arr, inner.start, inner.step, @@ -996,7 +998,7 @@ impl State { fn eval_range(&mut self, has_start: bool, has_step: bool, has_end: bool) { let end = if has_end { - Some(self.take_curr_val().unwrap_int()) + Some(self.take_val_register().unwrap_int()) } else { None }; @@ -1010,11 +1012,11 @@ impl State { } else { None }; - self.set_curr_val(Value::Range(val::Range { start, step, end }.into())); + self.set_val_register(Value::Range(val::Range { start, step, end }.into())); } fn eval_update_index(&mut self, span: Span) -> Result<(), Error> { - let values = self.take_curr_val().unwrap_array(); + let values = self.take_val_register().unwrap_array(); let update = self.pop_val(); let index = self.pop_val(); let span = self.to_global_span(span); @@ -1050,7 +1052,7 @@ impl State { } None => return Err(Error::IndexOutOfRange(index, span)), } - self.set_curr_val(Value::Array(values.into())); + self.set_val_register(Value::Array(values.into())); Ok(()) } @@ -1075,7 +1077,7 @@ impl State { None => return Err(Error::IndexOutOfRange(idx, span)), } } - self.set_curr_val(Value::Array(values.into())); + self.set_val_register(Value::Array(values.into())); Ok(()) } @@ -1086,7 +1088,7 @@ impl State { lhs: ExprId, span: Span, ) -> Result<(), Error> { - let update = self.take_curr_val(); + let update = self.take_val_register(); let index = self.pop_val(); let span = self.to_global_span(span); match index { @@ -1106,15 +1108,15 @@ impl State { fn eval_tup(&mut self, len: usize) { let tup = self.pop_vals(len); - self.set_curr_val(Value::Tuple(tup.into())); + self.set_val_register(Value::Tuple(tup.into())); } fn eval_unop(&mut self, op: UnOp) { - let val = self.take_curr_val(); + let val = self.take_val_register(); match op { UnOp::Functor(functor) => match val { Value::Closure(inner) => { - self.set_curr_val(Value::Closure( + self.set_val_register(Value::Closure( val::Closure { functor: update_functor_app(functor, inner.functor), ..*inner @@ -1123,35 +1125,35 @@ impl State { )); } Value::Global(id, app) => { - self.set_curr_val(Value::Global(id, update_functor_app(functor, app))); + self.set_val_register(Value::Global(id, update_functor_app(functor, app))); } _ => panic!("value should be callable"), }, UnOp::Neg => match val { - Value::BigInt(v) => self.set_curr_val(Value::BigInt(v.neg())), - Value::Double(v) => self.set_curr_val(Value::Double(v.neg())), - Value::Int(v) => self.set_curr_val(Value::Int(v.wrapping_neg())), + Value::BigInt(v) => self.set_val_register(Value::BigInt(v.neg())), + Value::Double(v) => self.set_val_register(Value::Double(v.neg())), + Value::Int(v) => self.set_val_register(Value::Int(v.wrapping_neg())), _ => panic!("value should be number"), }, UnOp::NotB => match val { - Value::Int(v) => self.set_curr_val(Value::Int(!v)), - Value::BigInt(v) => self.set_curr_val(Value::BigInt(!v)), + Value::Int(v) => self.set_val_register(Value::Int(!v)), + Value::BigInt(v) => self.set_val_register(Value::BigInt(!v)), _ => panic!("value should be Int or BigInt"), }, UnOp::NotL => match val { - Value::Bool(b) => self.set_curr_val(Value::Bool(!b)), + Value::Bool(b) => self.set_val_register(Value::Bool(!b)), _ => panic!("value should be bool"), }, UnOp::Pos => match val { - Value::BigInt(_) | Value::Int(_) | Value::Double(_) => self.set_curr_val(val), + Value::BigInt(_) | Value::Int(_) | Value::Double(_) => self.set_val_register(val), _ => panic!("value should be number"), }, - UnOp::Unwrap => self.set_curr_val(val), + UnOp::Unwrap => self.set_val_register(val), } } fn eval_update_field(&mut self, field: Field) { - let record = self.take_curr_val(); + let record = self.take_val_register(); let value = self.pop_val(); let update = match (record, field) { (Value::Range(mut inner), Field::Prim(PrimField::Start)) => { @@ -1170,7 +1172,7 @@ impl State { .expect("field path should be valid"), _ => panic!("invalid field access"), }; - self.set_curr_val(update); + self.set_val_register(update); } fn bind_value(&self, env: &mut Env, globals: &impl PackageStoreLookup, pat: PatId, val: Value) { From dd22cd63f1689cd0c33a218216cecf3d7ce5753d Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 19 Mar 2024 13:59:29 -0700 Subject: [PATCH 15/16] Fix clippy --- compiler/qsc_eval/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 2453f5657d..edb69de490 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -980,7 +980,7 @@ impl State { let arr = self.pop_val().unwrap_array(); match &index_val { Value::Int(i) => { - self.set_val_register(index_array(&arr, *i, self.to_global_span(span))?) + self.set_val_register(index_array(&arr, *i, self.to_global_span(span))?); } Value::Range(inner) => { self.set_val_register(slice_array( From 608cdd673f7ed8ddb8916e5d0bd45a989737a5fb Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 19 Mar 2024 15:53:23 -0700 Subject: [PATCH 16/16] Update compiler/qsc_fir/src/fir.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: César Zaragoza Cortés --- compiler/qsc_fir/src/fir.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs index e769910bf4..a0ddfef54c 100644 --- a/compiler/qsc_fir/src/fir.rs +++ b/compiler/qsc_fir/src/fir.rs @@ -1075,8 +1075,7 @@ impl Display for ExprKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); match self { - ExprKind::Array(exprs) => display_array(indent, exprs)?, - ExprKind::ArrayLit(exprs) => display_array(indent, exprs)?, + ExprKind::Array(exprs) | ExprKind::ArrayLit(exprs) => display_array(indent, exprs)?, ExprKind::ArrayRepeat(val, size) => display_array_repeat(indent, *val, *size)?, ExprKind::Assign(lhs, rhs) => display_assign(indent, *lhs, *rhs)?, ExprKind::AssignOp(op, lhs, rhs) => display_assign_op(indent, *op, *lhs, *rhs)?,