From 755b82e79325ae1d7225914fe85647ab59b73df1 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Tue, 9 Apr 2024 20:37:09 -0700 Subject: [PATCH 01/12] Introduce active branches and branching test set. --- .../src/evaluation_context.rs | 16 ++++- compiler/qsc_partial_eval/src/lib.rs | 8 +-- compiler/qsc_partial_eval/tests/branching.rs | 58 +++++++++++++++++++ 3 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 compiler/qsc_partial_eval/tests/branching.rs diff --git a/compiler/qsc_partial_eval/src/evaluation_context.rs b/compiler/qsc_partial_eval/src/evaluation_context.rs index e6a97e1f50..abaa9a4617 100644 --- a/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -9,7 +9,7 @@ use qsc_rir::rir::BlockId; use rustc_hash::FxHashMap; pub struct EvaluationContext { - pub current_block: BlockId, + active_blocks: Vec, scopes: Vec, } @@ -17,11 +17,18 @@ impl EvaluationContext { pub fn new(entry_package_id: PackageId, initial_block: BlockId) -> Self { let entry_callable_scope = Scope::new(entry_package_id, None, Vec::new()); Self { - current_block: initial_block, + active_blocks: vec![BlockNode { + id: initial_block, + next: None, + }], scopes: vec![entry_callable_scope], } } + pub fn get_current_block_id(&self) -> BlockId { + self.active_blocks.last().expect("no active blocks").id + } + pub fn get_current_scope(&self) -> &Scope { self.scopes .last() @@ -45,6 +52,11 @@ impl EvaluationContext { } } +pub struct BlockNode { + pub id: BlockId, + pub next: Option, +} + pub struct Scope { pub package_id: PackageId, pub callable: Option<(LocalItemId, FunctorApp)>, diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index 18832e2e9d..3ec04dc195 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -198,11 +198,7 @@ impl<'a> PartialEvaluator<'a> { } // Insert the return expression and return the generated program. - let current_block = self - .program - .blocks - .get_mut(self.eval_context.current_block) - .expect("block does not exist"); + let current_block = self.get_current_block_mut(); current_block.0.push(Instruction::Return); Ok(self.program) } @@ -580,7 +576,7 @@ impl<'a> PartialEvaluator<'a> { fn get_current_block_mut(&mut self) -> &mut rir::Block { self.program .blocks - .get_mut(self.eval_context.current_block) + .get_mut(self.eval_context.get_current_block_id()) .expect("block does not exist") } diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs new file mode 100644 index 0000000000..e1da5842c4 --- /dev/null +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::needless_raw_string_hashes)] + +mod test_utils; + +use indoc::indoc; +use qsc_rir::rir::{ + BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, +}; +use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; + +fn single_qubit_intrinsic_op_a() -> Callable { + Callable { + name: "opA".to_string(), + input_type: vec![Ty::Qubit], + output_type: None, + body: None, + call_type: CallableType::Regular, + } +} + +#[ignore = "WIP"] +#[test] +fn classic_condition_evaluates_true_branch() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if true { + opA(q); + } else { + opB(q); + } + } + } + "#, + }); + let op_a_callable_id = CallableId(1); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} From 14165bd4ae67aff8580ed494187574715670b860 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Tue, 9 Apr 2024 21:25:28 -0700 Subject: [PATCH 02/12] Basic classical condition works. --- compiler/qsc_partial_eval/src/lib.rs | 73 ++++++++++++++++++- compiler/qsc_partial_eval/tests/branching.rs | 2 +- compiler/qsc_partial_eval/tests/test_utils.rs | 23 ++++++ 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index 3ec04dc195..01760c3fd6 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -48,18 +48,35 @@ pub enum Error { #[error("partial evaluation error: {0}")] #[diagnostic(code("Qsc.PartialEval.EvaluationFailed"))] EvaluationFailed(qsc_eval::Error), + #[error("failed to evaluate {0}")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateCallable"))] FailedToEvaluateCallable(String, #[label] Span), + #[error("failed to evaluate callee expression")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateCalleeExpression"))] FailedToEvaluateCalleeExpression(#[label] Span), + #[error("failed to evaluate tuple element expression")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateTupleElementExpression"))] FailedToEvaluateTupleElementExpression(#[label] Span), + #[error("failed to evaluate binary expression operand")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateBinaryExpressionOperand"))] FailedToEvaluateBinaryExpressionOperand(#[label] Span), + + #[error("failed to evaluate condition expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateConditionExpression"))] + FailedToEvaluateConditionExpression(#[label] Span), + + #[error("failed to evaluate if body expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateIfBodyExpression"))] + FailedToEvaluateIfBodyExpression(#[label] Span), + + #[error("failed to evaluate block expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateBlockExpression"))] + FailedToEvaluateBlockExpression(#[label] Span), + #[error("failed to evaluate: {0} not yet implemented")] #[diagnostic(code("Qsc.PartialEval.Unimplemented"))] Unimplemented(String, #[label] Span), @@ -280,7 +297,7 @@ impl<'a> PartialEvaluator<'a> { ExprKind::BinOp(bin_op, lhs_expr_id, rhs_expr_id) => { self.eval_expr_bin_op(expr_id, *bin_op, *lhs_expr_id, *rhs_expr_id) } - ExprKind::Block(_) => Err(Error::Unimplemented("Block Expr".to_string(), expr.span)), + ExprKind::Block(block_id) => self.eval_expr_block(*block_id), ExprKind::Call(callee_expr_id, args_expr_id) => { self.eval_expr_call(*callee_expr_id, *args_expr_id) } @@ -290,7 +307,9 @@ impl<'a> PartialEvaluator<'a> { ExprKind::Fail(_) => panic!("instruction generation for fail expression is invalid"), ExprKind::Field(_, _) => Err(Error::Unimplemented("Field Expr".to_string(), expr.span)), ExprKind::Hole => panic!("instruction generation for hole expressions is invalid"), - ExprKind::If(_, _, _) => Err(Error::Unimplemented("If Expr".to_string(), expr.span)), + ExprKind::If(condition_expr_id, body_expr_id, otherwise_expr_id) => { + self.eval_expr_if(*condition_expr_id, *body_expr_id, *otherwise_expr_id) + } ExprKind::Index(_, _) => Err(Error::Unimplemented("Index Expr".to_string(), expr.span)), ExprKind::Lit(_) => panic!("instruction generation for literal expressions is invalid"), ExprKind::Range(_, _, _) => { @@ -379,6 +398,18 @@ impl<'a> PartialEvaluator<'a> { Ok(value) } + fn eval_expr_block(&mut self, block_id: BlockId) -> Result { + self.visit_block(block_id); + if self.errors.is_empty() { + // This should change eventually, but return UNIT for now. + Ok(Value::unit()) + } else { + let block = self.get_block(block_id); + let error = Error::FailedToEvaluateBlockExpression(block.span); + Err(error) + } + } + fn eval_expr_call( &mut self, callee_expr_id: ExprId, @@ -515,6 +546,38 @@ impl<'a> PartialEvaluator<'a> { } } + fn eval_expr_if( + &mut self, + condition_expr_id: ExprId, + body_expr_id: ExprId, + _otherwise_expr_id: Option, + ) -> Result { + // Visit the both the condition expression to get its value. + let maybe_condition_value = self.try_eval_expr(condition_expr_id); + let Ok(condition_value) = maybe_condition_value else { + let condition_expr = self.get_expr(condition_expr_id); + let error = Error::FailedToEvaluateConditionExpression(condition_expr.span); + return Err(error); + }; + + // + if let Value::Bool(condition_bool) = condition_value { + if condition_bool { + let maybe_body_value = self.try_eval_expr(body_expr_id); + return if let Ok(body_value) = maybe_body_value { + Ok(body_value) + } else { + let body_expr = self.get_expr(body_expr_id); + let error = Error::FailedToEvaluateConditionExpression(body_expr.span); + Err(error) + }; + } + } + + // TODO (cesarzc): do the right thing. + Ok(Value::unit()) + } + fn eval_expr_tuple(&mut self, exprs: &Vec) -> Result { let mut values = Vec::::new(); for expr_id in exprs { @@ -767,8 +830,10 @@ impl<'a> Visitor<'a> for PartialEvaluator<'a> { self.visit_expr(expr_id); } StmtKind::Local(_, pat_id, expr_id) => { - self.visit_expr(expr_id); - self.bind_expr_to_pat(pat_id, expr_id); + let maybe_expr_value = self.try_eval_expr(expr_id); + if maybe_expr_value.is_ok() { + self.bind_expr_to_pat(pat_id, expr_id); + } } StmtKind::Item(_) => { // Do nothing. diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs index e1da5842c4..f0e5a09a2b 100644 --- a/compiler/qsc_partial_eval/tests/branching.rs +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -21,7 +21,6 @@ fn single_qubit_intrinsic_op_a() -> Callable { } } -#[ignore = "WIP"] #[test] fn classic_condition_evaluates_true_branch() { let program = compile_and_partially_evaluate(indoc! { @@ -41,6 +40,7 @@ fn classic_condition_evaluates_true_branch() { } "#, }); + println!("{program}"); let op_a_callable_id = CallableId(1); assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); assert_block_instructions( diff --git a/compiler/qsc_partial_eval/tests/test_utils.rs b/compiler/qsc_partial_eval/tests/test_utils.rs index f0cde17b53..0767ff9611 100644 --- a/compiler/qsc_partial_eval/tests/test_utils.rs +++ b/compiler/qsc_partial_eval/tests/test_utils.rs @@ -9,6 +9,7 @@ use qsc_lowerer::{map_hir_package_to_fir, Lowerer}; use qsc_partial_eval::partially_evaluate; use qsc_rca::{Analyzer, PackageStoreComputeProperties}; use qsc_rir::rir::{BlockId, Callable, CallableId, Instruction, Program}; +use std::{fs::File, io::Write}; pub fn assert_block_instructions( program: &Program, @@ -69,6 +70,8 @@ impl CompilationContext { let fir_store = lower_hir_package_store(compiler.package_store()); let analyzer = Analyzer::init(&fir_store); let compute_properties = analyzer.analyze_all(); + write_fir_store_to_files(&fir_store); + write_compute_properties_to_files(&compute_properties); Self { fir_store, compute_properties, @@ -88,3 +91,23 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore } fir_store } + +// TODO (cesarzc): remove. +fn write_fir_store_to_files(store: &PackageStore) { + for (id, package) in store { + let filename = format!("dbg/fir.package{id}.txt"); + let mut package_file = File::create(filename).expect("File could be created"); + let package_string = format!("{package}"); + write!(package_file, "{package_string}").expect("Writing to file should succeed."); + } +} + +// TODO (cesarzc): remove. +fn write_compute_properties_to_files(store: &PackageStoreComputeProperties) { + for (id, package) in store.iter() { + let filename = format!("dbg/rca.package{id}.txt"); + let mut package_file = File::create(filename).expect("File could be created"); + let package_string = format!("{package}"); + write!(package_file, "{package_string}").expect("Writing to file should succeed."); + } +} From a0cb0255ecab95ebc390070a2993dc0cf2451b04 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Wed, 10 Apr 2024 13:35:18 -0700 Subject: [PATCH 03/12] Support for otherwise classical branching. --- compiler/qsc_partial_eval/src/lib.rs | 35 +++++-- compiler/qsc_partial_eval/tests/branching.rs | 102 ++++++++++++++++++- 2 files changed, 126 insertions(+), 11 deletions(-) diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index 01760c3fd6..809b28eb43 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -69,9 +69,13 @@ pub enum Error { #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateConditionExpression"))] FailedToEvaluateConditionExpression(#[label] Span), - #[error("failed to evaluate if body expression")] + #[error("failed to evaluate the true block of an if expression")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateIfBodyExpression"))] - FailedToEvaluateIfBodyExpression(#[label] Span), + FailedToEvaluateIfExpressionTrueBlock(#[label] Span), + + #[error("failed to evaluate the false block of an if expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateIfExpressionFalseBlock"))] + FailedToEvaluateIfExpressionFalseBlock(#[label] Span), #[error("failed to evaluate block expression")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateBlockExpression"))] @@ -550,7 +554,7 @@ impl<'a> PartialEvaluator<'a> { &mut self, condition_expr_id: ExprId, body_expr_id: ExprId, - _otherwise_expr_id: Option, + otherwise_expr_id: Option, ) -> Result { // Visit the both the condition expression to get its value. let maybe_condition_value = self.try_eval_expr(condition_expr_id); @@ -560,18 +564,31 @@ impl<'a> PartialEvaluator<'a> { return Err(error); }; - // + // If the condition value is a Boolean literal, use the value to decide which branch to + // evaluate. if let Value::Bool(condition_bool) = condition_value { - if condition_bool { + let maybe_if_expr_value = if condition_bool { let maybe_body_value = self.try_eval_expr(body_expr_id); - return if let Ok(body_value) = maybe_body_value { + if let Ok(body_value) = maybe_body_value { Ok(body_value) } else { let body_expr = self.get_expr(body_expr_id); - let error = Error::FailedToEvaluateConditionExpression(body_expr.span); + let error = Error::FailedToEvaluateIfExpressionTrueBlock(body_expr.span); Err(error) - }; - } + } + } else if let Some(otherwise_expr_id) = otherwise_expr_id { + let maybe_otherwise_value = self.try_eval_expr(otherwise_expr_id); + if let Ok(otherwise_value) = maybe_otherwise_value { + Ok(otherwise_value) + } else { + let otherwise_expr = self.get_expr(otherwise_expr_id); + let error = Error::FailedToEvaluateIfExpressionFalseBlock(otherwise_expr.span); + Err(error) + } + } else { + Ok(Value::unit()) + }; + return maybe_if_expr_value; } // TODO (cesarzc): do the right thing. diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs index f0e5a09a2b..e9b2ae1da4 100644 --- a/compiler/qsc_partial_eval/tests/branching.rs +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -21,8 +21,72 @@ fn single_qubit_intrinsic_op_a() -> Callable { } } +fn single_qubit_intrinsic_op_b() -> Callable { + Callable { + name: "opB".to_string(), + input_type: vec![Ty::Qubit], + output_type: None, + body: None, + call_type: CallableType::Regular, + } +} + +#[test] +fn if_expression_with_classical_condition_evaluates_true_branch() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if true { + opA(q); + } + } + } + "#, + }); + let op_a_callable_id = CallableId(1); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} + #[test] -fn classic_condition_evaluates_true_branch() { +fn if_expression_with_classical_condition_does_not_evaluate_true_branch() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if false { + opA(q); + } + } + } + "#, + }); + // This program is expected to just have the entry-point callable, whose block only has a return + // intruction. + assert_eq!(program.callables.iter().count(), 1); + assert_block_instructions(&program, BlockId(0), &[Instruction::Return]); +} + +#[test] +fn if_expression_with_classical_condition_evaluates_true_branch_and_not_false_branch() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @@ -40,7 +104,6 @@ fn classic_condition_evaluates_true_branch() { } "#, }); - println!("{program}"); let op_a_callable_id = CallableId(1); assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); assert_block_instructions( @@ -56,3 +119,38 @@ fn classic_condition_evaluates_true_branch() { ], ); } + +#[test] +fn if_expression_with_classical_condition_evaluates_false_branch_and_not_true_branch() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if false { + opA(q); + } else { + opB(q); + } + } + } + "#, + }); + let op_a_callable_id = CallableId(1); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_b()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} From ed743dd87b571da4e631d10952204a0b1d2b6b83 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Wed, 10 Apr 2024 16:04:33 -0700 Subject: [PATCH 04/12] Basic branching works \o/. --- .../src/evaluation_context.rs | 10 ++ compiler/qsc_partial_eval/src/lib.rs | 151 +++++++++++++++--- compiler/qsc_partial_eval/tests/branching.rs | 87 +++++++++- compiler/qsc_partial_eval/tests/test_utils.rs | 10 ++ 4 files changed, 234 insertions(+), 24 deletions(-) diff --git a/compiler/qsc_partial_eval/src/evaluation_context.rs b/compiler/qsc_partial_eval/src/evaluation_context.rs index abaa9a4617..08eced90b3 100644 --- a/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -41,12 +41,22 @@ impl EvaluationContext { .expect("the evaluation context does not have a current scope") } + pub fn pop_block_node(&mut self) -> BlockNode { + self.active_blocks + .pop() + .expect("there are no active blocks in the evaluation context") + } + pub fn pop_scope(&mut self) -> Scope { self.scopes .pop() .expect("there are no scopes in the evaluation context") } + pub fn push_block_node(&mut self, b: BlockNode) { + self.active_blocks.push(b); + } + pub fn push_scope(&mut self, s: Scope) { self.scopes.push(s); } diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index 809b28eb43..f29df3ebe9 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -4,7 +4,7 @@ mod evaluation_context; mod management; -use evaluation_context::{EvaluationContext, Scope}; +use evaluation_context::{BlockNode, EvaluationContext, Scope}; use management::{QuantumIntrinsicsChecker, ResourceManager}; use miette::Diagnostic; use qsc_data_structures::functors::FunctorApp; @@ -69,13 +69,9 @@ pub enum Error { #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateConditionExpression"))] FailedToEvaluateConditionExpression(#[label] Span), - #[error("failed to evaluate the true block of an if expression")] - #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateIfBodyExpression"))] - FailedToEvaluateIfExpressionTrueBlock(#[label] Span), - - #[error("failed to evaluate the false block of an if expression")] - #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateIfExpressionFalseBlock"))] - FailedToEvaluateIfExpressionFalseBlock(#[label] Span), + #[error("failed to evaluate a branch block of an if expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateIfExpressionBranchBlock"))] + FailedToEvaluateIfExpressionBranchBlock(#[label] Span), #[error("failed to evaluate block expression")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateBlockExpression"))] @@ -107,8 +103,7 @@ impl<'a> PartialEvaluator<'a> { let mut resource_manager = ResourceManager::default(); let mut program = Program::new(); let entry_block_id = resource_manager.next_block(); - let entry_block = rir::Block(Vec::new()); - program.blocks.insert(entry_block_id, entry_block); + program.blocks.insert(entry_block_id, rir::Block::default()); let entry_point_id = resource_manager.next_callable(); let entry_point = rir::Callable { name: "main".into(), @@ -201,6 +196,12 @@ impl<'a> PartialEvaluator<'a> { } } + fn create_program_block(&mut self) -> rir::BlockId { + let block_id = self.resource_manager.next_block(); + self.program.blocks.insert(block_id, rir::Block::default()); + block_id + } + fn eval(mut self) -> Result { let current_package = self.get_current_package_id(); let entry_package = self.package_store.get(current_package); @@ -311,9 +312,12 @@ impl<'a> PartialEvaluator<'a> { ExprKind::Fail(_) => panic!("instruction generation for fail expression is invalid"), ExprKind::Field(_, _) => Err(Error::Unimplemented("Field Expr".to_string(), expr.span)), ExprKind::Hole => panic!("instruction generation for hole expressions is invalid"), - ExprKind::If(condition_expr_id, body_expr_id, otherwise_expr_id) => { - self.eval_expr_if(*condition_expr_id, *body_expr_id, *otherwise_expr_id) - } + ExprKind::If(condition_expr_id, body_expr_id, otherwise_expr_id) => self.eval_expr_if( + expr_id, + *condition_expr_id, + *body_expr_id, + *otherwise_expr_id, + ), ExprKind::Index(_, _) => Err(Error::Unimplemented("Index Expr".to_string(), expr.span)), ExprKind::Lit(_) => panic!("instruction generation for literal expressions is invalid"), ExprKind::Range(_, _, _) => { @@ -552,6 +556,7 @@ impl<'a> PartialEvaluator<'a> { fn eval_expr_if( &mut self, + if_expr_id: ExprId, condition_expr_id: ExprId, body_expr_id: ExprId, otherwise_expr_id: Option, @@ -573,7 +578,7 @@ impl<'a> PartialEvaluator<'a> { Ok(body_value) } else { let body_expr = self.get_expr(body_expr_id); - let error = Error::FailedToEvaluateIfExpressionTrueBlock(body_expr.span); + let error = Error::FailedToEvaluateIfExpressionBranchBlock(body_expr.span); Err(error) } } else if let Some(otherwise_expr_id) = otherwise_expr_id { @@ -582,7 +587,7 @@ impl<'a> PartialEvaluator<'a> { Ok(otherwise_value) } else { let otherwise_expr = self.get_expr(otherwise_expr_id); - let error = Error::FailedToEvaluateIfExpressionFalseBlock(otherwise_expr.span); + let error = Error::FailedToEvaluateIfExpressionBranchBlock(otherwise_expr.span); Err(error) } } else { @@ -591,8 +596,108 @@ impl<'a> PartialEvaluator<'a> { return maybe_if_expr_value; } - // TODO (cesarzc): do the right thing. - Ok(Value::unit()) + // At this point the condition value is not classical, so we need to generate a branching instruction. + // First, we pop the current block node and generate a new one which the new branches will jump to when their + // instructions end. + let current_block_node = self.eval_context.pop_block_node(); + let continuation_block_node_id = self.create_program_block(); + let continuation_block_node = BlockNode { + id: continuation_block_node_id, + next: current_block_node.next, + }; + self.eval_context.push_block_node(continuation_block_node); + + // Since the if expression can represent a dynamic value, create a variable to store it if the expression is + // non-unit. + let if_expr = self.get_expr(if_expr_id); + let maybe_if_expr_var = if if_expr.ty == Ty::UNIT { + None + } else { + let variable_id = self.resource_manager.next_var(); + let variable_ty = map_fir_type_to_rir_type(&if_expr.ty); + Some(Variable { + variable_id, + ty: variable_ty, + }) + }; + + // Evaluate the body expression. + let (body_block_node_id, body_expr_value) = + self.eval_expr_if_branch(body_expr_id, continuation_block_node_id)?; + // If there is a variable to save to, add a store instruction at the end of the body block. + if let Some(if_expr_var) = maybe_if_expr_var { + let body_operand = map_eval_value_to_rir_operand(&body_expr_value); + let store_ins = Instruction::Store(body_operand, if_expr_var); + let body_block = self.get_program_block_mut(body_block_node_id); + body_block.0.push(store_ins); + } + + // Evaluate the otherwise expression (if any), and determine the block to branch to if the condition is false. + let otherwise_block_node_id = if let Some(otherwise_expr_id) = otherwise_expr_id { + let (otherwise_block_node_id, otherwise_expr_value) = + self.eval_expr_if_branch(otherwise_expr_id, continuation_block_node_id)?; + // If there is a variable to save to, add a store instruction at the end of the otherwise block. + if let Some(if_expr_var) = maybe_if_expr_var { + let otherwise_operand = map_eval_value_to_rir_operand(&otherwise_expr_value); + let store_ins = Instruction::Store(otherwise_operand, if_expr_var); + let otherwise_block = self.get_program_block_mut(otherwise_block_node_id); + otherwise_block.0.push(store_ins); + } + otherwise_block_node_id + } else { + continuation_block_node_id + }; + + // Finally, we insert the branch instruction. + let condition_var = if let Value::Var(var) = condition_value { + Variable { + variable_id: var.0.into(), + ty: rir::Ty::Boolean, + } + } else { + panic!("the condition of an if expression is expected to be a variable"); + }; + let branch_ins = + Instruction::Branch(condition_var, body_block_node_id, otherwise_block_node_id); + let current_block = self.get_program_block_mut(current_block_node.id); + current_block.0.push(branch_ins); + + // Return the value of the if expression. + let if_expr_value = if let Some(if_expr_var) = maybe_if_expr_var { + Value::Var(Var(if_expr_var.variable_id.into())) + } else { + Value::unit() + }; + Ok(if_expr_value) + } + + fn eval_expr_if_branch( + &mut self, + branch_body_expr_id: ExprId, + continuation_block_id: rir::BlockId, + ) -> Result<(rir::BlockId, Value), Error> { + // Create the block node that corresponds to the branch body and push it as the active one. + let block_node_id = self.create_program_block(); + let block_node = BlockNode { + id: block_node_id, + next: Some(continuation_block_id), + }; + self.eval_context.push_block_node(block_node); + + // Evaluate the branch body expression. + let maybe_body_value = self.try_eval_expr(branch_body_expr_id); + let Ok(body_value) = maybe_body_value else { + let body_body_expr = self.get_expr(branch_body_expr_id); + let error = Error::FailedToEvaluateIfExpressionBranchBlock(body_body_expr.span); + return Err(error); + }; + + // Finally, jump to the continuation block and pop the current block node. + let current_block = self.get_current_block_mut(); + let jump_ins = Instruction::Jump(continuation_block_id); + current_block.0.push(jump_ins); + let _ = self.eval_context.pop_block_node(); + Ok((block_node_id, body_value)) } fn eval_expr_tuple(&mut self, exprs: &Vec) -> Result { @@ -654,10 +759,7 @@ impl<'a> PartialEvaluator<'a> { } fn get_current_block_mut(&mut self) -> &mut rir::Block { - self.program - .blocks - .get_mut(self.eval_context.get_current_block_id()) - .expect("block does not exist") + self.get_program_block_mut(self.eval_context.get_current_block_id()) } fn get_current_package_id(&self) -> PackageId { @@ -709,6 +811,13 @@ impl<'a> PartialEvaluator<'a> { .expect("callable not present") } + fn get_program_block_mut(&mut self, id: rir::BlockId) -> &mut rir::Block { + self.program + .blocks + .get_mut(id) + .expect("program block does not exist") + } + fn is_classical_stmt(&self, stmt_id: StmtId) -> bool { let current_package_id = self.get_current_package_id(); let store_stmt_id = StoreStmtId::from((current_package_id, stmt_id)); diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs index e9b2ae1da4..63017df29c 100644 --- a/compiler/qsc_partial_eval/tests/branching.rs +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -7,9 +7,32 @@ mod test_utils; use indoc::indoc; use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, + BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, Variable, }; -use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; +use test_utils::{ + assert_block_instructions, assert_block_last_instruction, assert_callable, + compile_and_partially_evaluate, +}; + +fn mresetz() -> Callable { + Callable { + name: "__quantum__qis__mresetz__body".to_string(), + input_type: vec![Ty::Qubit, Ty::Result], + output_type: None, + body: None, + call_type: CallableType::Measurement, + } +} + +fn read_result() -> Callable { + Callable { + name: "__quantum__rt__read_result__body".to_string(), + input_type: vec![Ty::Result], + output_type: Some(Ty::Boolean), + body: None, + call_type: CallableType::Readout, + } +} fn single_qubit_intrinsic_op_a() -> Callable { Callable { @@ -104,7 +127,7 @@ fn if_expression_with_classical_condition_evaluates_true_branch_and_not_false_br } "#, }); - let op_a_callable_id = CallableId(1); + let op_a_callable_id = CallableId(3); assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); assert_block_instructions( &program, @@ -154,3 +177,61 @@ fn if_expression_with_classical_condition_evaluates_false_branch_and_not_true_br ], ); } + +#[test] +fn if_expression_with_dynamic_condition_evaluates_true_branch() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == Zero { + opA(q); + } + } + } + "#, + }); + println!("{program}"); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + + // Set the values of the block IDs we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_true_block_id = BlockId(2); + + // Verify the branch instruction from the initial block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_true_block_id, continuation_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions of the if-true block. + assert_block_instructions( + &program, + if_true_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions of the continuation block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} diff --git a/compiler/qsc_partial_eval/tests/test_utils.rs b/compiler/qsc_partial_eval/tests/test_utils.rs index 0767ff9611..fe1a95efdb 100644 --- a/compiler/qsc_partial_eval/tests/test_utils.rs +++ b/compiler/qsc_partial_eval/tests/test_utils.rs @@ -11,6 +11,16 @@ use qsc_rca::{Analyzer, PackageStoreComputeProperties}; use qsc_rir::rir::{BlockId, Callable, CallableId, Instruction, Program}; use std::{fs::File, io::Write}; +pub fn assert_block_last_instruction( + program: &Program, + block_id: BlockId, + expected_inst: &Instruction, +) { + let block = program.blocks.get(block_id).expect("block does not exist"); + let actual_inst = block.0.last().expect("block does not have instructions"); + assert_eq!(expected_inst, actual_inst); +} + pub fn assert_block_instructions( program: &Program, block_id: BlockId, From ccc28cf605ea01286bb3acd4cf9ce59d00a783a2 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Wed, 10 Apr 2024 18:22:18 -0700 Subject: [PATCH 05/12] Add tests. --- compiler/qsc_partial_eval/tests/branching.rs | 774 +++++++++++++++++- compiler/qsc_partial_eval/tests/test_utils.rs | 4 +- 2 files changed, 759 insertions(+), 19 deletions(-) diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs index 63017df29c..425aa6d2b0 100644 --- a/compiler/qsc_partial_eval/tests/branching.rs +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#![allow(clippy::needless_raw_string_hashes)] +#![allow(clippy::needless_raw_string_hashes, clippy::similar_names)] mod test_utils; @@ -54,8 +54,18 @@ fn single_qubit_intrinsic_op_b() -> Callable { } } +fn single_qubit_intrinsic_op_c() -> Callable { + Callable { + name: "opC".to_string(), + input_type: vec![Ty::Qubit], + output_type: None, + body: None, + call_type: CallableType::Regular, + } +} + #[test] -fn if_expression_with_classical_condition_evaluates_true_branch() { +fn if_expression_with_true_condition() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @@ -87,7 +97,7 @@ fn if_expression_with_classical_condition_evaluates_true_branch() { } #[test] -fn if_expression_with_classical_condition_does_not_evaluate_true_branch() { +fn if_expression_with_false_condition() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @@ -109,7 +119,7 @@ fn if_expression_with_classical_condition_does_not_evaluate_true_branch() { } #[test] -fn if_expression_with_classical_condition_evaluates_true_branch_and_not_false_branch() { +fn if_else_expression_with_true_condition() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @@ -127,7 +137,7 @@ fn if_expression_with_classical_condition_evaluates_true_branch_and_not_false_br } "#, }); - let op_a_callable_id = CallableId(3); + let op_a_callable_id = CallableId(1); assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); assert_block_instructions( &program, @@ -144,7 +154,7 @@ fn if_expression_with_classical_condition_evaluates_true_branch_and_not_false_br } #[test] -fn if_expression_with_classical_condition_evaluates_false_branch_and_not_true_branch() { +fn if_else_expression_with_false_condition() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @@ -162,14 +172,52 @@ fn if_expression_with_classical_condition_evaluates_false_branch_and_not_true_br } "#, }); - let op_a_callable_id = CallableId(1); - assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_b()); + let op_b_callable_id = CallableId(1); + assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); assert_block_instructions( &program, BlockId(0), &[ Instruction::Call( - op_a_callable_id, + op_b_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} + +#[test] +fn if_elif_else_expression_with_true_elif_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + operation opC(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if false { + opA(q); + } elif true { + opB(q); + } else { + opC(q); + } + } + } + "#, + }); + let op_b_callable_id = CallableId(1); + assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_b_callable_id, vec![Operand::Literal(Literal::Qubit(0))], None, ), @@ -179,7 +227,7 @@ fn if_expression_with_classical_condition_evaluates_false_branch_and_not_true_br } #[test] -fn if_expression_with_dynamic_condition_evaluates_true_branch() { +fn if_expression_with_dynamic_condition() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @@ -195,8 +243,256 @@ fn if_expression_with_dynamic_condition_evaluates_true_branch() { } "#, }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, continuation_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions in the if-block. + assert_block_instructions( + &program, + if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} + +#[test] +fn if_else_expression_with_dynamic_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == One { + opA(q); + } else { + opB(q); + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + let op_b_callable_id = CallableId(4); + assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + let else_block_id = BlockId(3); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, else_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions in the if-block. + assert_block_instructions( + &program, + if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the else-block. + assert_block_instructions( + &program, + else_block_id, + &[ + Instruction::Call( + op_b_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} + +#[test] +fn if_elif_else_expression_with_dynamic_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + operation opC(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); + let r0 = QIR.Intrinsic.__quantum__qis__mresetz__body(q0); + let r1 = QIR.Intrinsic.__quantum__qis__mresetz__body(q1); + if r0 == One { + opA(q2); + } elif r1 == One { + opB(q2); + } else { + opC(q2); + } + } + } + "#, + }); println!("{program}"); + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + let op_b_callable_id = CallableId(4); + assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); + let op_c_callable_id = CallableId(5); + assert_callable(&program, op_c_callable_id, &single_qubit_intrinsic_op_c()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + let else_block_id = BlockId(3); + let nested_continuation_block_id = BlockId(4); + let nested_if_block_id = BlockId(5); + let nested_else_block_id = BlockId(6); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, else_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions in the if-block. + assert_block_instructions( + &program, + if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(2))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the branch instruction in the else-block. + let nested_condition_var = Variable { + variable_id: 3.into(), + ty: Ty::Boolean, + }; + let nested_branch_inst = Instruction::Branch( + nested_condition_var, + nested_if_block_id, + nested_else_block_id, + ); + assert_block_last_instruction(&program, else_block_id, &nested_branch_inst); + + // Verify the instructions in the nested-if-block. + assert_block_instructions( + &program, + nested_if_block_id, + &[ + Instruction::Call( + op_b_callable_id, + vec![Operand::Literal(Literal::Qubit(2))], + None, + ), + Instruction::Jump(nested_continuation_block_id), + ], + ); + + // Verify the instructions in the nested-else-block. + assert_block_instructions( + &program, + nested_else_block_id, + &[ + Instruction::Call( + op_c_callable_id, + vec![Operand::Literal(Literal::Qubit(2))], + None, + ), + Instruction::Jump(nested_continuation_block_id), + ], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} + +#[test] +fn if_expression_with_dynamic_condition_and_nested_if_expression_with_true_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == Zero { + if true { + opA(q); + } + } + } + } + "#, + }); + // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); assert_callable(&program, mresetz_callable_id, &mresetz()); @@ -205,23 +501,23 @@ fn if_expression_with_dynamic_condition_evaluates_true_branch() { let op_a_callable_id = CallableId(3); assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); - // Set the values of the block IDs we want to verify. + // Set the IDs of the blocks we want to verify. let initial_block_id = BlockId(0); let continuation_block_id = BlockId(1); - let if_true_block_id = BlockId(2); + let if_block_id = BlockId(2); - // Verify the branch instruction from the initial block. + // Verify the branch instruction in the initial-block. let condition_var = Variable { variable_id: 1.into(), ty: Ty::Boolean, }; - let branch_inst = Instruction::Branch(condition_var, if_true_block_id, continuation_block_id); + let branch_inst = Instruction::Branch(condition_var, if_block_id, continuation_block_id); assert_block_last_instruction(&program, initial_block_id, &branch_inst); - // Verify the instructions of the if-true block. + // Verify the instructions in the if-block. assert_block_instructions( &program, - if_true_block_id, + if_block_id, &[ Instruction::Call( op_a_callable_id, @@ -232,6 +528,450 @@ fn if_expression_with_dynamic_condition_evaluates_true_branch() { ], ); - // Verify the instructions of the continuation block. + // Verify the instructions in the continuation-block. assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } + +#[test] +fn if_expression_with_dynamic_condition_and_nested_if_expression_with_false_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == Zero { + if false { + opA(q); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, continuation_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions in the if-block. + assert_block_instructions( + &program, + if_block_id, + &[Instruction::Jump(continuation_block_id)], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} + +#[test] +fn if_else_expression_with_dynamic_condition_and_nested_if_expression_with_true_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == One { + opA(q); + } else { + if true { + opB(q); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + let op_b_callable_id = CallableId(4); + assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + let else_block_id = BlockId(3); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, else_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions in the if-block. + assert_block_instructions( + &program, + if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the else-block. + assert_block_instructions( + &program, + else_block_id, + &[ + Instruction::Call( + op_b_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} + +#[test] +fn if_else_expression_with_dynamic_condition_and_nested_if_expression_with_false_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == One { + opA(q); + } else { + if false { + opB(q); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + let else_block_id = BlockId(3); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, else_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions in the if-block. + assert_block_instructions( + &program, + if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the else-block. + assert_block_instructions( + &program, + else_block_id, + &[Instruction::Jump(continuation_block_id)], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} + +#[test] +fn if_expression_with_dynamic_condition_and_nested_if_expression_with_dynamic_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); + let r0 = QIR.Intrinsic.__quantum__qis__mresetz__body(q0); + let r1 = QIR.Intrinsic.__quantum__qis__mresetz__body(q1); + if r0 == Zero { + if r1 == One { + opA(q2); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + let nested_continuation_block_id = BlockId(3); + let nested_if_block_id = BlockId(4); + + // Verify the branch instruction in the initial block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, continuation_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the branch instruction in the if-block. + let nested_condition_var = Variable { + variable_id: 3.into(), + ty: Ty::Boolean, + }; + let nested_branch_inst = Instruction::Branch( + nested_condition_var, + nested_if_block_id, + nested_continuation_block_id, + ); + assert_block_last_instruction(&program, if_block_id, &nested_branch_inst); + + // Verify the instructions in the nested-if-block. + assert_block_instructions( + &program, + nested_if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(2))], + None, + ), + Instruction::Jump(nested_continuation_block_id), + ], + ); + + // Verify the instructions in the nested-continuation-block. + assert_block_instructions( + &program, + nested_continuation_block_id, + &[Instruction::Jump(continuation_block_id)], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} + +#[test] +fn if_expression_with_dynamic_condition_and_subsequent_call_to_operation() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == Zero { + opA(q); + } + opB(q); + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + let op_b_callable_id = CallableId(4); + assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, continuation_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions in the if-block. + assert_block_instructions( + &program, + if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions( + &program, + continuation_block_id, + &[ + Instruction::Call( + op_b_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} + +#[test] +fn if_else_expression_with_dynamic_condition_and_subsequent_call_to_operation() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + operation opC(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == One { + opA(q); + } else { + opB(q); + } + opC(q); + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + let op_b_callable_id = CallableId(4); + assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); + let op_c_callable_id = CallableId(5); + assert_callable(&program, op_c_callable_id, &single_qubit_intrinsic_op_c()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + let else_block_id = BlockId(3); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, else_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the instructions in the if-block. + assert_block_instructions( + &program, + if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the else-block. + assert_block_instructions( + &program, + else_block_id, + &[ + Instruction::Call( + op_b_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Jump(continuation_block_id), + ], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions( + &program, + continuation_block_id, + &[ + Instruction::Call( + op_c_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} diff --git a/compiler/qsc_partial_eval/tests/test_utils.rs b/compiler/qsc_partial_eval/tests/test_utils.rs index fe1a95efdb..ea4e77921a 100644 --- a/compiler/qsc_partial_eval/tests/test_utils.rs +++ b/compiler/qsc_partial_eval/tests/test_utils.rs @@ -80,8 +80,8 @@ impl CompilationContext { let fir_store = lower_hir_package_store(compiler.package_store()); let analyzer = Analyzer::init(&fir_store); let compute_properties = analyzer.analyze_all(); - write_fir_store_to_files(&fir_store); - write_compute_properties_to_files(&compute_properties); + //write_fir_store_to_files(&fir_store); + //write_compute_properties_to_files(&compute_properties); Self { fir_store, compute_properties, From a8b1d85e98b53973b3b183055067310bef61abd6 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Wed, 10 Apr 2024 18:26:23 -0700 Subject: [PATCH 06/12] Prepare for code review. --- compiler/qsc_partial_eval/tests/branching.rs | 2 +- compiler/qsc_partial_eval/tests/test_utils.rs | 25 ++----------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs index 425aa6d2b0..2879a63c54 100644 --- a/compiler/qsc_partial_eval/tests/branching.rs +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -3,7 +3,7 @@ #![allow(clippy::needless_raw_string_hashes, clippy::similar_names)] -mod test_utils; +pub mod test_utils; use indoc::indoc; use qsc_rir::rir::{ diff --git a/compiler/qsc_partial_eval/tests/test_utils.rs b/compiler/qsc_partial_eval/tests/test_utils.rs index ea4e77921a..c060a79813 100644 --- a/compiler/qsc_partial_eval/tests/test_utils.rs +++ b/compiler/qsc_partial_eval/tests/test_utils.rs @@ -9,8 +9,9 @@ use qsc_lowerer::{map_hir_package_to_fir, Lowerer}; use qsc_partial_eval::partially_evaluate; use qsc_rca::{Analyzer, PackageStoreComputeProperties}; use qsc_rir::rir::{BlockId, Callable, CallableId, Instruction, Program}; -use std::{fs::File, io::Write}; +// Allowing this function as dead code is needed because not all integration tests use it. +#[allow(dead_code)] pub fn assert_block_last_instruction( program: &Program, block_id: BlockId, @@ -80,8 +81,6 @@ impl CompilationContext { let fir_store = lower_hir_package_store(compiler.package_store()); let analyzer = Analyzer::init(&fir_store); let compute_properties = analyzer.analyze_all(); - //write_fir_store_to_files(&fir_store); - //write_compute_properties_to_files(&compute_properties); Self { fir_store, compute_properties, @@ -101,23 +100,3 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore } fir_store } - -// TODO (cesarzc): remove. -fn write_fir_store_to_files(store: &PackageStore) { - for (id, package) in store { - let filename = format!("dbg/fir.package{id}.txt"); - let mut package_file = File::create(filename).expect("File could be created"); - let package_string = format!("{package}"); - write!(package_file, "{package_string}").expect("Writing to file should succeed."); - } -} - -// TODO (cesarzc): remove. -fn write_compute_properties_to_files(store: &PackageStoreComputeProperties) { - for (id, package) in store.iter() { - let filename = format!("dbg/rca.package{id}.txt"); - let mut package_file = File::create(filename).expect("File could be created"); - let package_string = format!("{package}"); - write!(package_file, "{package_string}").expect("Writing to file should succeed."); - } -} From 2d95e002dcedfe53248a5bbeab6c18edc1a4add3 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Wed, 10 Apr 2024 21:43:53 -0700 Subject: [PATCH 07/12] Create double nested if expressions test. --- compiler/qsc_partial_eval/tests/branching.rs | 176 ++++++++++++++++++- 1 file changed, 175 insertions(+), 1 deletion(-) diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs index 2879a63c54..1cb6050460 100644 --- a/compiler/qsc_partial_eval/tests/branching.rs +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -64,6 +64,16 @@ fn single_qubit_intrinsic_op_c() -> Callable { } } +fn single_qubit_intrinsic_op_d() -> Callable { + Callable { + name: "opD".to_string(), + input_type: vec![Ty::Qubit], + output_type: None, + body: None, + call_type: CallableType::Regular, + } +} + #[test] fn if_expression_with_true_condition() { let program = compile_and_partially_evaluate(indoc! { @@ -384,7 +394,6 @@ fn if_elif_else_expression_with_dynamic_condition() { } "#, }); - println!("{program}"); // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); @@ -812,6 +821,171 @@ fn if_expression_with_dynamic_condition_and_nested_if_expression_with_dynamic_co assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } +#[allow(clippy::too_many_lines)] +#[test] +fn doubly_nested_if_else_expressions_with_dynamic_conditions() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + operation opC(q : Qubit) : Unit { body intrinsic; } + operation opD(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); + let r0 = QIR.Intrinsic.__quantum__qis__mresetz__body(q0); + let r1 = QIR.Intrinsic.__quantum__qis__mresetz__body(q1); + if r0 == Zero { + if r1 == Zero { + opA(q2); + } else { + opB(q2); + } + } else { + if r1 == One{ + opC(q2); + } else { + opD(q2); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable(&program, mresetz_callable_id, &mresetz()); + let read_result_callable_id = CallableId(2); + assert_callable(&program, read_result_callable_id, &read_result()); + let op_a_callable_id = CallableId(3); + assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + let op_b_callable_id = CallableId(4); + assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); + let op_c_callable_id = CallableId(5); + assert_callable(&program, op_c_callable_id, &single_qubit_intrinsic_op_c()); + let op_d_callable_id = CallableId(6); + assert_callable(&program, op_d_callable_id, &single_qubit_intrinsic_op_d()); + + // Set the IDs of the blocks we want to verify. + let initial_block_id = BlockId(0); + let continuation_block_id = BlockId(1); + let if_block_id = BlockId(2); + let else_block_id = BlockId(6); + let first_nested_continuation_block_id = BlockId(3); + let first_nested_if_block_id = BlockId(4); + let first_nested_else_block_id = BlockId(5); + let second_nested_continuation_block_id = BlockId(7); + let second_nested_if_block_id = BlockId(8); + let second_nested_else_block_id = BlockId(9); + + // Verify the branch instruction in the initial-block. + let condition_var = Variable { + variable_id: 1.into(), + ty: Ty::Boolean, + }; + let branch_inst = Instruction::Branch(condition_var, if_block_id, else_block_id); + assert_block_last_instruction(&program, initial_block_id, &branch_inst); + + // Verify the branch instruction in the if-block. + let first_nested_condition_var = Variable { + variable_id: 3.into(), + ty: Ty::Boolean, + }; + let first_nested_branch_inst = Instruction::Branch( + first_nested_condition_var, + first_nested_if_block_id, + first_nested_else_block_id, + ); + assert_block_last_instruction(&program, if_block_id, &first_nested_branch_inst); + + // Verify the instructions in the first nested if-block. + assert_block_instructions( + &program, + first_nested_if_block_id, + &[ + Instruction::Call( + op_a_callable_id, + vec![Operand::Literal(Literal::Qubit(2))], + None, + ), + Instruction::Jump(first_nested_continuation_block_id), + ], + ); + + // Verify the instructions in the first nested else-block. + assert_block_instructions( + &program, + first_nested_else_block_id, + &[ + Instruction::Call( + op_b_callable_id, + vec![Operand::Literal(Literal::Qubit(2))], + None, + ), + Instruction::Jump(first_nested_continuation_block_id), + ], + ); + + // Verify the instructions in the first nested continuation-block. + assert_block_instructions( + &program, + first_nested_continuation_block_id, + &[Instruction::Jump(continuation_block_id)], + ); + + // Verify the branch instruction in the else-block. + let second_nested_condition_var = Variable { + variable_id: 5.into(), + ty: Ty::Boolean, + }; + let second_nested_branch_inst = Instruction::Branch( + second_nested_condition_var, + second_nested_if_block_id, + second_nested_else_block_id, + ); + assert_block_last_instruction(&program, else_block_id, &second_nested_branch_inst); + + // Verify the instructions in the second nested if-block. + assert_block_instructions( + &program, + second_nested_if_block_id, + &[ + Instruction::Call( + op_c_callable_id, + vec![Operand::Literal(Literal::Qubit(2))], + None, + ), + Instruction::Jump(second_nested_continuation_block_id), + ], + ); + + // Verify the instructions in the second nested else-block. + assert_block_instructions( + &program, + second_nested_else_block_id, + &[ + Instruction::Call( + op_d_callable_id, + vec![Operand::Literal(Literal::Qubit(2))], + None, + ), + Instruction::Jump(second_nested_continuation_block_id), + ], + ); + + // Verify the instructions in the second nested continuation-block. + assert_block_instructions( + &program, + second_nested_continuation_block_id, + &[Instruction::Jump(continuation_block_id)], + ); + + // Verify the instructions in the continuation-block. + assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); +} + #[test] fn if_expression_with_dynamic_condition_and_subsequent_call_to_operation() { let program = compile_and_partially_evaluate(indoc! { From 32ff50a03e7de7a873720ce4bbc70296ff2205ca Mon Sep 17 00:00:00 2001 From: cesarzc Date: Thu, 11 Apr 2024 12:33:45 -0700 Subject: [PATCH 08/12] Small improvement in preparation for coming changes. --- compiler/qsc_partial_eval/src/lib.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index f29df3ebe9..db3a455d14 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -407,15 +407,11 @@ impl<'a> PartialEvaluator<'a> { } fn eval_expr_block(&mut self, block_id: BlockId) -> Result { - self.visit_block(block_id); - if self.errors.is_empty() { - // This should change eventually, but return UNIT for now. - Ok(Value::unit()) - } else { + let maybe_block_value = self.try_eval_block(block_id); + maybe_block_value.map_err(|()| { let block = self.get_block(block_id); - let error = Error::FailedToEvaluateBlockExpression(block.span); - Err(error) - } + Error::FailedToEvaluateBlockExpression(block.span) + }) } fn eval_expr_call( @@ -864,6 +860,16 @@ impl<'a> PartialEvaluator<'a> { Value::unit() } + fn try_eval_block(&mut self, block_id: BlockId) -> Result { + self.visit_block(block_id); + if self.errors.is_empty() { + // This should change eventually, but return UNIT for now. + Ok(Value::unit()) + } else { + Err(()) + } + } + fn try_eval_expr(&mut self, expr_id: ExprId) -> Result { // Visit the expression, which will either populate the expression entry in the scope's value map or add an // error. From 238f231a0f6d1046cad40b7c23cde7a65ad4017b Mon Sep 17 00:00:00 2001 From: cesarzc Date: Thu, 11 Apr 2024 11:08:05 -0700 Subject: [PATCH 09/12] Initial setup for loop unrolling. --- compiler/qsc_partial_eval/src/lib.rs | 85 +++++++++++++++++++----- compiler/qsc_partial_eval/tests/loops.rs | 42 ++++++++++++ 2 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 compiler/qsc_partial_eval/tests/loops.rs diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index db3a455d14..4d8fb392b7 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -77,6 +77,10 @@ pub enum Error { #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateBlockExpression"))] FailedToEvaluateBlockExpression(#[label] Span), + #[error("failed to evaluate loop condition")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateLoopCondition"))] + FailedToEvaluateLoopCondition(#[label] Span), + #[error("failed to evaluate: {0} not yet implemented")] #[diagnostic(code("Qsc.PartialEval.Unimplemented"))] Unimplemented(String, #[label] Span), @@ -338,7 +342,9 @@ impl<'a> PartialEvaluator<'a> { expr.span, )), ExprKind::Var(res, _) => Ok(self.eval_expr_var(res)), - ExprKind::While(_, _) => Err(Error::Unimplemented("While Expr".to_string(), expr.span)), + ExprKind::While(condition_expr_id, body_block_id) => { + self.eval_expr_while(*condition_expr_id, *body_block_id) + } } } @@ -727,6 +733,43 @@ impl<'a> PartialEvaluator<'a> { } } + fn eval_expr_while( + &mut self, + condition_expr_id: ExprId, + body_block_id: BlockId, + ) -> Result { + // Verify assumptions. + assert!( + self.is_classical_expr(condition_expr_id), + "loop conditions must be purely classical" + ); + let body_block = self.get_block(body_block_id); + assert_eq!( + body_block.ty, + Ty::UNIT, + "the type of a loop block is expected to be Unit" + ); + + // Evaluate the block until the loop condition is false. + let mut eval_condition = || { + let maybe_condition_expr_value = self.try_eval_expr(condition_expr_id); + if let Ok(condition_expr_value) = maybe_condition_expr_value { + let Value::Bool(condition_bool) = condition_expr_value else { + panic!("loop condition must be a Boolean"); + }; + Ok(condition_bool) + } else { + let condition_expr = self.get_expr(condition_expr_id); + let error = Error::FailedToEvaluateLoopCondition(condition_expr.span); + Err(error) + } + }; + // TODO (cesarzc): implement. + //while eval_condition()? {} + + unimplemented!(); + } + fn eval_result_as_bool_operand(&mut self, result: val::Result) -> Operand { match result { val::Result::Id(id) => { @@ -792,6 +835,24 @@ impl<'a> PartialEvaluator<'a> { Some(spec_decl) } + fn get_expr_compute_kind(&self, expr_id: ExprId) -> ComputeKind { + let current_package_id = self.get_current_package_id(); + let store_expr_id = StoreExprId::from((current_package_id, expr_id)); + let expr_generator_set = self.compute_properties.get_expr(store_expr_id); + let callable_scope = self.eval_context.get_current_scope(); + expr_generator_set + .generate_application_compute_kind(&callable_scope.args_runtime_properties) + } + + fn get_stmt_compute_kind(&self, stmt_id: StmtId) -> ComputeKind { + let current_package_id = self.get_current_package_id(); + let store_stmt_id = StoreStmtId::from((current_package_id, stmt_id)); + let stmt_generator_set = self.compute_properties.get_stmt(store_stmt_id); + let callable_scope = self.eval_context.get_current_scope(); + stmt_generator_set + .generate_application_compute_kind(&callable_scope.args_runtime_properties) + } + fn get_or_insert_callable(&mut self, callable: Callable) -> CallableId { // Check if the callable is already in the program, and if not add it. let callable_name = callable.name.clone(); @@ -814,13 +875,13 @@ impl<'a> PartialEvaluator<'a> { .expect("program block does not exist") } + fn is_classical_expr(&self, expr_id: ExprId) -> bool { + let compute_kind = self.get_expr_compute_kind(expr_id); + matches!(compute_kind, ComputeKind::Classical) + } + fn is_classical_stmt(&self, stmt_id: StmtId) -> bool { - let current_package_id = self.get_current_package_id(); - let store_stmt_id = StoreStmtId::from((current_package_id, stmt_id)); - let stmt_generator_set = self.compute_properties.get_stmt(store_stmt_id); - let callable_scope = self.eval_context.get_current_scope(); - let compute_kind = stmt_generator_set - .generate_application_compute_kind(&callable_scope.args_runtime_properties); + let compute_kind = self.get_stmt_compute_kind(stmt_id); matches!(compute_kind, ComputeKind::Classical) } @@ -924,14 +985,8 @@ impl<'a> Visitor<'a> for PartialEvaluator<'a> { "visiting an expression when errors have already happened should never happen" ); - // Determine whether the expression is classical since how we evaluate it depends on it. - let current_package_id = self.get_current_package_id(); - let store_expr_id = StoreExprId::from((current_package_id, expr_id)); - let expr_generator_set = self.compute_properties.get_expr(store_expr_id); - let callable_scope = self.eval_context.get_current_scope(); - let compute_kind = expr_generator_set - .generate_application_compute_kind(&callable_scope.args_runtime_properties); - let expr_result = if matches!(compute_kind, ComputeKind::Classical) { + // We evaluate an expression differently depending on whether it is classical or not. + let expr_result = if self.is_classical_expr(expr_id) { self.eval_classical_expr(expr_id) } else { self.eval_expr(expr_id) diff --git a/compiler/qsc_partial_eval/tests/loops.rs b/compiler/qsc_partial_eval/tests/loops.rs new file mode 100644 index 0000000000..d80c6165d8 --- /dev/null +++ b/compiler/qsc_partial_eval/tests/loops.rs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::needless_raw_string_hashes)] + +mod test_utils; + +use indoc::indoc; +use qsc_rir::rir::{BlockId, Callable, CallableId, CallableType, Instruction, Ty}; +use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; + +fn single_qubit_intrinsic_op() -> Callable { + Callable { + name: "op".to_string(), + input_type: vec![Ty::Qubit], + output_type: None, + body: None, + call_type: CallableType::Regular, + } +} + +#[ignore = "WIP"] +#[test] +fn operation_call_within_a_for_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + for _ in 0..5 { + op(q); + } + } + } + "#, + }); + let op_callable_id = CallableId(1); + assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_block_instructions(&program, BlockId(0), &[Instruction::Return]); +} From 46d1b01f2edbf26300ad44ad2c33532cdda803b3 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Thu, 11 Apr 2024 15:36:26 -0700 Subject: [PATCH 10/12] Basic loop unrolling works. --- compiler/qsc_partial_eval/src/lib.rs | 41 +++++++++++++++--------- compiler/qsc_partial_eval/tests/loops.rs | 31 +++++++++++++++--- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index 4d8fb392b7..4631f5ad6a 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -81,6 +81,10 @@ pub enum Error { #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateLoopCondition"))] FailedToEvaluateLoopCondition(#[label] Span), + #[error("failed to evaluate loop body")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateLoopBody"))] + FailedToEvaluateLoopBody(#[label] Span), + #[error("failed to evaluate: {0} not yet implemented")] #[diagnostic(code("Qsc.PartialEval.Unimplemented"))] Unimplemented(String, #[label] Span), @@ -751,23 +755,30 @@ impl<'a> PartialEvaluator<'a> { ); // Evaluate the block until the loop condition is false. - let mut eval_condition = || { - let maybe_condition_expr_value = self.try_eval_expr(condition_expr_id); - if let Ok(condition_expr_value) = maybe_condition_expr_value { - let Value::Bool(condition_bool) = condition_expr_value else { - panic!("loop condition must be a Boolean"); - }; - Ok(condition_bool) - } else { - let condition_expr = self.get_expr(condition_expr_id); - let error = Error::FailedToEvaluateLoopCondition(condition_expr.span); - Err(error) + while self.eval_expr_while_condition(condition_expr_id)? { + let maybe_block_value = self.try_eval_block(body_block_id); + if maybe_block_value.is_err() { + let block = self.get_block(body_block_id); + let error = Error::FailedToEvaluateLoopBody(block.span); + return Err(error); } - }; - // TODO (cesarzc): implement. - //while eval_condition()? {} + } - unimplemented!(); + Ok(Value::unit()) + } + + fn eval_expr_while_condition(&mut self, condition_expr_id: ExprId) -> Result { + let maybe_condition_expr_value = self.try_eval_expr(condition_expr_id); + if let Ok(condition_expr_value) = maybe_condition_expr_value { + let Value::Bool(condition_bool) = condition_expr_value else { + panic!("loop condition must be a Boolean"); + }; + Ok(condition_bool) + } else { + let condition_expr = self.get_expr(condition_expr_id); + let error = Error::FailedToEvaluateLoopCondition(condition_expr.span); + Err(error) + } } fn eval_result_as_bool_operand(&mut self, result: val::Result) -> Operand { diff --git a/compiler/qsc_partial_eval/tests/loops.rs b/compiler/qsc_partial_eval/tests/loops.rs index d80c6165d8..07d921f0a8 100644 --- a/compiler/qsc_partial_eval/tests/loops.rs +++ b/compiler/qsc_partial_eval/tests/loops.rs @@ -6,7 +6,9 @@ mod test_utils; use indoc::indoc; -use qsc_rir::rir::{BlockId, Callable, CallableId, CallableType, Instruction, Ty}; +use qsc_rir::rir::{ + BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, +}; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; fn single_qubit_intrinsic_op() -> Callable { @@ -19,7 +21,6 @@ fn single_qubit_intrinsic_op() -> Callable { } } -#[ignore = "WIP"] #[test] fn operation_call_within_a_for_loop() { let program = compile_and_partially_evaluate(indoc! { @@ -29,14 +30,36 @@ fn operation_call_within_a_for_loop() { @EntryPoint() operation Main() : Unit { use q = Qubit(); - for _ in 0..5 { + for _ in 1..3 { op(q); } } } "#, }); + let op_callable_id = CallableId(1); assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); - assert_block_instructions(&program, BlockId(0), &[Instruction::Return]); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); } From 1ed05616eceb5edc879748e9df308229a9d70b24 Mon Sep 17 00:00:00 2001 From: cesarzc Date: Thu, 11 Apr 2024 21:33:16 -0700 Subject: [PATCH 11/12] Add tests. --- compiler/qsc_partial_eval/tests/loops.rs | 276 ++++++++++++++++++++++- compiler/qsc_partial_eval/tests/misc.rs | 158 +++++++++++++ 2 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 compiler/qsc_partial_eval/tests/misc.rs diff --git a/compiler/qsc_partial_eval/tests/loops.rs b/compiler/qsc_partial_eval/tests/loops.rs index 07d921f0a8..7002abdc56 100644 --- a/compiler/qsc_partial_eval/tests/loops.rs +++ b/compiler/qsc_partial_eval/tests/loops.rs @@ -11,7 +11,7 @@ use qsc_rir::rir::{ }; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; -fn single_qubit_intrinsic_op() -> Callable { +fn single_qubit_intrinsic() -> Callable { Callable { name: "op".to_string(), input_type: vec![Ty::Qubit], @@ -21,8 +21,18 @@ fn single_qubit_intrinsic_op() -> Callable { } } +fn single_qubit_rotation_intrinsic() -> Callable { + Callable { + name: "rotation".to_string(), + input_type: vec![Ty::Double, Ty::Qubit], + output_type: None, + body: None, + call_type: CallableType::Regular, + } +} + #[test] -fn operation_call_within_a_for_loop() { +fn unitary_call_within_a_for_loop() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @@ -39,7 +49,7 @@ fn operation_call_within_a_for_loop() { }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable(&program, op_callable_id, &single_qubit_intrinsic()); assert_block_instructions( &program, BlockId(0), @@ -63,3 +73,263 @@ fn operation_call_within_a_for_loop() { ], ); } + +#[test] +fn unitary_call_within_a_while_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + mutable idx = 0; + while idx < 3 { + op(q); + set idx += 1; + } + } + } + "#, + }); + + let rotation_callable_id = CallableId(1); + assert_callable(&program, rotation_callable_id, &single_qubit_intrinsic()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + rotation_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + rotation_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + rotation_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} + +#[test] +fn unitary_call_within_a_repeat_until_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + mutable idx = 0; + repeat { + op(q); + set idx += 1; + } until idx >= 3; + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable(&program, op_callable_id, &single_qubit_intrinsic()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} + +#[test] +fn rotation_call_within_a_for_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation rotation(theta : Double, q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + for theta in [0.0, 1.0, 2.0] { + rotation(theta, q); + } + } + } + "#, + }); + + let rotation_callable_id = CallableId(1); + assert_callable( + &program, + rotation_callable_id, + &single_qubit_rotation_intrinsic(), + ); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + rotation_callable_id, + vec![ + Operand::Literal(Literal::Double(0.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Call( + rotation_callable_id, + vec![ + Operand::Literal(Literal::Double(1.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Call( + rotation_callable_id, + vec![ + Operand::Literal(Literal::Double(2.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Return, + ], + ); +} + +#[test] +fn rotation_call_within_a_while_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation rotation(theta : Double, q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let angles = [0.0, 1.0, 2.0]; + mutable idx = 0; + while idx < 3 { + rotation(angles[idx], q); + set idx += 1; + } + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable(&program, op_callable_id, &single_qubit_rotation_intrinsic()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_callable_id, + vec![ + Operand::Literal(Literal::Double(0.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Call( + op_callable_id, + vec![ + Operand::Literal(Literal::Double(1.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Call( + op_callable_id, + vec![ + Operand::Literal(Literal::Double(2.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Return, + ], + ); +} + +#[test] +fn rotation_call_within_a_repeat_until_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation rotation(theta : Double, q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let angles = [0.0, 1.0, 2.0]; + mutable idx = 0; + repeat { + rotation(angles[idx], q); + set idx += 1; + } until idx >= 3; + } + } + "#, + }); + + let rotation_callable_id = CallableId(1); + assert_callable( + &program, + rotation_callable_id, + &single_qubit_rotation_intrinsic(), + ); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + rotation_callable_id, + vec![ + Operand::Literal(Literal::Double(0.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Call( + rotation_callable_id, + vec![ + Operand::Literal(Literal::Double(1.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Call( + rotation_callable_id, + vec![ + Operand::Literal(Literal::Double(2.0)), + Operand::Literal(Literal::Qubit(0)), + ], + None, + ), + Instruction::Return, + ], + ); +} diff --git a/compiler/qsc_partial_eval/tests/misc.rs b/compiler/qsc_partial_eval/tests/misc.rs new file mode 100644 index 0000000000..4527967287 --- /dev/null +++ b/compiler/qsc_partial_eval/tests/misc.rs @@ -0,0 +1,158 @@ +#![allow(clippy::needless_raw_string_hashes)] + +mod test_utils; + +use indoc::indoc; +use qsc_rir::rir::{ + BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, +}; +use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; + +fn single_qubit_intrinsic_op() -> Callable { + Callable { + name: "op".to_string(), + input_type: vec![Ty::Qubit], + output_type: None, + body: None, + call_type: CallableType::Regular, + } +} + +#[test] +fn unitary_call_within_an_if_with_classical_condition_within_a_for_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + for idx in 0..5 { + if idx % 2 == 0 { + op(q); + } + } + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} + +#[test] +fn unitary_call_within_an_if_with_classical_condition_within_a_while_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + mutable idx = 0; + while idx <= 5 { + if idx % 2 == 0 { + op(q); + } + set idx += 1; + } + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} + +#[test] +fn unitary_call_within_an_if_with_classical_condition_within_a_repeat_until_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + mutable idx = 0; + repeat { + if idx % 2 == 0 { + op(q); + } + set idx += 1; + } until idx > 5; + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_block_instructions( + &program, + BlockId(0), + &[ + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Call( + op_callable_id, + vec![Operand::Literal(Literal::Qubit(0))], + None, + ), + Instruction::Return, + ], + ); +} From 99a800e4c6311b210fd0f7a7d9ae75f74f1e142c Mon Sep 17 00:00:00 2001 From: cesarzc Date: Fri, 12 Apr 2024 13:38:37 -0700 Subject: [PATCH 12/12] Add copyright header. --- compiler/qsc_partial_eval/tests/misc.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/qsc_partial_eval/tests/misc.rs b/compiler/qsc_partial_eval/tests/misc.rs index 18b447911f..da2b4a7bab 100644 --- a/compiler/qsc_partial_eval/tests/misc.rs +++ b/compiler/qsc_partial_eval/tests/misc.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #![allow(clippy::needless_raw_string_hashes)] pub mod test_utils;