From ad4578e4344372bb1376836b268d55c428b6a85d Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 16 Apr 2024 21:41:49 -0700 Subject: [PATCH] Partial Evaluation Output Recording support (#1387) This adds logic to insert output recording calls for recognized types into RIR programs generated by partial evaluation. --- compiler/qsc/src/interpret/tests.rs | 81 +- compiler/qsc_eval/src/tests.rs | 3 +- .../src/evaluation_context.rs | 16 +- compiler/qsc_partial_eval/src/lib.rs | 328 +++- compiler/qsc_partial_eval/tests/branching.rs | 1426 +++++++++-------- .../qsc_partial_eval/tests/classical_args.rs | 117 +- .../qsc_partial_eval/tests/dynamic_vars.rs | 90 +- compiler/qsc_partial_eval/tests/intrinsics.rs | 534 +++--- compiler/qsc_partial_eval/tests/loops.rs | 275 ++-- compiler/qsc_partial_eval/tests/misc.rs | 129 +- .../tests/output_recording.rs | 526 ++++++ compiler/qsc_partial_eval/tests/qubits.rs | 224 +-- compiler/qsc_partial_eval/tests/results.rs | 753 +++++---- compiler/qsc_partial_eval/tests/test_utils.rs | 67 +- compiler/qsc_passes/src/loop_unification.rs | 1 - compiler/qsc_rir/src/builder.rs | 33 + .../src/passes/unreachable_code_check.rs | 1 - pip/tests/test_interpreter.py | 11 +- 18 files changed, 2907 insertions(+), 1708 deletions(-) create mode 100644 compiler/qsc_partial_eval/tests/output_recording.rs diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs index b6c9efac0d..aca479319a 100644 --- a/compiler/qsc/src/interpret/tests.rs +++ b/compiler/qsc/src/interpret/tests.rs @@ -727,7 +727,7 @@ mod given_interpreter { open Microsoft.Quantum.Math; open QIR.Intrinsic; @EntryPoint() - operation Main() : Unit { + operation Main() : Result { use q = Qubit(); let pi_over_2 = 4.0 / 2.0; __quantum__qis__rz__body(pi_over_2, q); @@ -735,6 +735,7 @@ mod given_interpreter { __quantum__qis__rz__body(some_angle, q); set some_angle = ArcCos(-1.0) / PI(); __quantum__qis__rz__body(some_angle, q); + __quantum__qis__mresetz__body(q) } }"# }, @@ -750,12 +751,88 @@ mod given_interpreter { call void @__quantum__qis__rz__body(double 2.0, %Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__rz__body(double 0.0, %Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__rz__body(double 1.0, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) ret void } declare void @__quantum__qis__rz__body(double, %Qubit*) - attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" } + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + "#]] + .assert_eq(&res); + } + + #[test] + fn adaptive_qirgen_nested_output_types() { + let mut interpreter = Interpreter::new( + true, + SourceMap::default(), + PackageType::Lib, + TargetCapabilityFlags::Adaptive, + LanguageFeatures::default(), + ) + .expect("interpreter should be created"); + let (result, output) = line( + &mut interpreter, + indoc! {r#" + namespace Test { + open QIR.Intrinsic; + @EntryPoint() + operation Main() : (Result, (Bool, Bool)) { + use q = Qubit(); + let r = __quantum__qis__mresetz__body(q); + (r, (r == One, r == Zero)) + } + }"# + }, + ); + is_only_value(&result, &output, &Value::unit()); + let res = interpreter.qirgen("Test.Main()").expect("expected success"); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + %var_0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*)) + %var_1 = icmp eq i1 %var_0, true + %var_2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*)) + %var_3 = icmp eq i1 %var_2, false + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__bool_record_output(i1 %var_1, i8* null) + call void @__quantum__rt__bool_record_output(i1 %var_3, i8* null) + ret void + } + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare i1 @__quantum__qis__read_result__body(%Result*) + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + declare void @__quantum__rt__bool_record_output(i1, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } attributes #1 = { "irreversible" } ; module flags diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index b130e8bdf5..479ccbddef 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3,7 +3,6 @@ #![allow(clippy::needless_raw_string_hashes)] -use core::panic; use std::rc::Rc; use crate::{ @@ -173,7 +172,7 @@ fn check_partial_eval_stmt( &mut GenericReceiver::new(&mut out), ) { Ok(_) => {} - Err(err) => panic!("Unexpected error: {:?}", err), + Err(err) => panic!("Unexpected error: {err:?}"), } } diff --git a/compiler/qsc_partial_eval/src/evaluation_context.rs b/compiler/qsc_partial_eval/src/evaluation_context.rs index be2306a662..ef213f3346 100644 --- a/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -72,6 +72,7 @@ pub struct Scope { pub callable: Option<(LocalItemId, FunctorApp)>, pub args_runtime_properties: Vec, pub env: Env, + last_expr: Option, hybrid_exprs: FxHashMap, hybrid_vars: FxHashMap, } @@ -87,12 +88,14 @@ impl Scope { callable, args_runtime_properties, env: Env::default(), + last_expr: None, hybrid_exprs: FxHashMap::default(), hybrid_vars: FxHashMap::default(), } } - pub fn get_expr_value(&self, expr_id: ExprId) -> &Value { + // Potential candidate for removal if only the last expression value is needed. + pub fn _get_expr_value(&self, expr_id: ExprId) -> &Value { self.hybrid_exprs .get(&expr_id) .expect("expression value does not exist") @@ -105,10 +108,21 @@ impl Scope { } pub fn insert_expr_value(&mut self, expr_id: ExprId, value: Value) { + self.last_expr = Some(expr_id); self.hybrid_exprs.insert(expr_id, value); } pub fn insert_local_var_value(&mut self, local_var_id: LocalVarId, value: Value) { self.hybrid_vars.insert(local_var_id, value); } + + pub fn clear_last_expr(&mut self) { + self.last_expr = None; + } + + pub fn last_expr_value(&self) -> Value { + self.last_expr + .and_then(|expr_id| self.hybrid_exprs.get(&expr_id)) + .map_or_else(Value::unit, Clone::clone) + } } diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index 20021dd85e..4b369525c0 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -26,9 +26,12 @@ use qsc_fir::{ visit::Visitor, }; use qsc_rca::{ComputeKind, ComputePropertiesLookup, PackageStoreComputeProperties}; -use qsc_rir::rir::{ - self, Callable, CallableId, CallableType, ConditionCode, Instruction, Literal, Operand, - Program, Variable, +use qsc_rir::{ + builder, + rir::{ + self, Callable, CallableId, CallableType, ConditionCode, Instruction, Literal, Operand, + Program, Variable, + }, }; use rustc_hash::FxHashMap; use std::{collections::hash_map::Entry, rc::Rc, result::Result}; @@ -54,6 +57,10 @@ pub enum Error { #[diagnostic(code("Qsc.PartialEval.EvaluationFailed"))] EvaluationFailed(qsc_eval::Error), + #[error("failed to evaluate array element expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateArrayElementExpression"))] + FailedToEvaluateArrayElementExpression(#[label] Span), + #[error("failed to evaluate {0}")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateCallable"))] FailedToEvaluateCallable(String, #[label] Span), @@ -144,28 +151,18 @@ impl<'a> PartialEvaluator<'a> { } } - fn bind_expr_to_pat(&mut self, pat_id: PatId, expr_id: ExprId) { + fn bind_value_to_pat(&mut self, pat_id: PatId, value: Value) { let pat = self.get_pat(pat_id); match &pat.kind { PatKind::Bind(ident) => { - let expr_value = self - .eval_context - .get_current_scope_mut() - .get_expr_value(expr_id) - .clone(); - self.bind_value_to_ident(ident, expr_value); + self.bind_value_to_ident(ident, value); } PatKind::Tuple(pats) => { - let expr = self.get_expr(expr_id); - match &expr.kind { - ExprKind::Tuple(exprs) => { - assert!(pats.len() == exprs.len()); - for (pat_id, expr_id) in pats.iter().zip(exprs.iter()) { - self.bind_expr_to_pat(*pat_id, *expr_id); - } - } - _ => panic!("expected a tuple expression to bind to a tuple pattern"), - }; + let tup = value.unwrap_tuple(); + assert!(pats.len() == tup.len()); + for (pat_id, value) in pats.iter().zip(tup.iter()) { + self.bind_value_to_pat(*pat_id, value.clone()); + } } PatKind::Discard => { // Nothing to bind to. @@ -228,8 +225,16 @@ impl<'a> PartialEvaluator<'a> { return Err(error); } - // Insert the return expression. + // Get the final value from the execution context. + let ret_val = self.eval_context.get_current_scope().last_expr_value(); + let output_recording: Vec = self.generate_output_recording_instructions( + ret_val, + &self.get_expr(self.entry.expr.expr).ty, + ); + + // Insert the return expression and return the generated program. let current_block = self.get_current_block_mut(); + current_block.0.extend(output_recording); current_block.0.push(Instruction::Return); // Set the number of qubits and results used by the program. @@ -300,8 +305,8 @@ impl<'a> PartialEvaluator<'a> { let store_expr_id = StoreExprId::from((current_package_id, expr_id)); let expr = self.package_store.get_expr(store_expr_id); match &expr.kind { - ExprKind::Array(_) => Err(Error::Unimplemented("Array Expr".to_string(), expr.span)), - ExprKind::ArrayLit(_) => Err(Error::Unimplemented("Array Lit".to_string(), expr.span)), + ExprKind::Array(exprs) => self.eval_expr_array(exprs), + ExprKind::ArrayLit(_) => panic!("array of literal values should always be classical"), ExprKind::ArrayRepeat(_, _) => { Err(Error::Unimplemented("Array Repeat".to_string(), expr.span)) } @@ -496,8 +501,10 @@ impl<'a> PartialEvaluator<'a> { match callable_decl.name.name.as_ref() { "__quantum__rt__qubit_allocate" => self.allocate_qubit(), "__quantum__rt__qubit_release" => self.release_qubit(&args_value), - "__quantum__qis__m__body" => self.measure_qubit(mz_callable(), &args_value), - "__quantum__qis__mresetz__body" => self.measure_qubit(mresetz_callable(), &args_value), + "__quantum__qis__m__body" => self.measure_qubit(builder::mz_decl(), &args_value), + "__quantum__qis__mresetz__body" => { + self.measure_qubit(builder::mresetz_decl(), &args_value) + } _ => self.eval_expr_call_to_intrinsic_qis(store_item_id, callable_decl, args_value), } } @@ -555,9 +562,7 @@ impl<'a> PartialEvaluator<'a> { // Check whether evaluating the block failed. if self.errors.is_empty() { - // Once we have proper support for evaluating all kinds of callables (not just parameterless unitary - // callables), we should the variable that stores the callable return value here. - Ok(Value::unit()) + Ok(popped_scope.last_expr_value()) } else { // Evaluating the block failed, generate an error specific to the callable. let global_callable = self @@ -724,8 +729,21 @@ impl<'a> PartialEvaluator<'a> { } } + fn eval_expr_array(&mut self, exprs: &Vec) -> Result { + let mut values = Vec::with_capacity(exprs.len()); + for expr_id in exprs { + let maybe_value = self.try_eval_expr(*expr_id); + let Ok(value) = maybe_value else { + let expr = self.get_expr(*expr_id); + return Err(Error::FailedToEvaluateArrayElementExpression(expr.span)); + }; + values.push(value); + } + Ok(Value::Array(values.into())) + } + fn eval_expr_tuple(&mut self, exprs: &Vec) -> Result { - let mut values = Vec::::new(); + let mut values = Vec::with_capacity(exprs.len()); for expr_id in exprs { let maybe_value = self.try_eval_expr(*expr_id); let Ok(value) = maybe_value else { @@ -806,7 +824,8 @@ impl<'a> PartialEvaluator<'a> { let result_operand = Operand::Literal(Literal::Result( id.try_into().expect("could not convert result ID to u32"), )); - let read_result_callable_id = self.get_or_insert_callable(read_result_callable()); + let read_result_callable_id = + self.get_or_insert_callable(builder::read_result_decl()); let variable_id = self.resource_manager.next_var(); let variable_ty = rir::Ty::Boolean; let variable = Variable { @@ -951,8 +970,7 @@ impl<'a> PartialEvaluator<'a> { 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()) + Ok(self.eval_context.get_current_scope().last_expr_value()) } else { Err(()) } @@ -963,15 +981,210 @@ impl<'a> PartialEvaluator<'a> { // error. self.visit_expr(expr_id); if self.errors.is_empty() { - let expr_value = self - .eval_context - .get_current_scope() - .get_expr_value(expr_id); - Ok(expr_value.clone()) + Ok(self.eval_context.get_current_scope().last_expr_value()) } else { Err(()) } } + + fn generate_output_recording_instructions( + &mut self, + ret_val: Value, + ty: &Ty, + ) -> Vec { + let mut instrs = Vec::new(); + + match ret_val { + Value::Array(vals) => self.record_array(ty, &mut instrs, &vals), + Value::Tuple(vals) => self.record_tuple(ty, &mut instrs, &vals), + Value::Result(res) => self.record_result(&mut instrs, res), + Value::Var(var) => self.record_variable(ty, &mut instrs, var), + Value::Bool(val) => self.record_bool(&mut instrs, val), + Value::Int(val) => self.record_int(&mut instrs, val), + + Value::BigInt(_) + | Value::Closure(_) + | Value::Double(_) + | Value::Global(_, _) + | Value::Pauli(_) + | Value::Qubit(_) + | Value::Range(_) + | Value::String(_) => panic!("unsupported value type in output recording"), + } + + instrs + } + + fn record_int(&mut self, instrs: &mut Vec, val: i64) { + let int_record_callable_id = self.get_int_record_callable(); + instrs.push(Instruction::Call( + int_record_callable_id, + vec![ + Operand::Literal(Literal::Integer(val)), + Operand::Literal(Literal::Pointer), + ], + None, + )); + } + + fn record_bool(&mut self, instrs: &mut Vec, val: bool) { + let bool_record_callable_id = self.get_bool_record_callable(); + instrs.push(Instruction::Call( + bool_record_callable_id, + vec![ + Operand::Literal(Literal::Bool(val)), + Operand::Literal(Literal::Pointer), + ], + None, + )); + } + + fn record_variable(&mut self, ty: &Ty, instrs: &mut Vec, var: Var) { + let (record_callable_id, record_ty) = match ty { + Ty::Prim(Prim::Bool) => (self.get_bool_record_callable(), rir::Ty::Boolean), + Ty::Prim(Prim::Int) => (self.get_int_record_callable(), rir::Ty::Integer), + _ => panic!("unsupported variable type in output recording"), + }; + instrs.push(Instruction::Call( + record_callable_id, + vec![ + Operand::Variable(Variable { + variable_id: var.0.into(), + ty: record_ty, + }), + Operand::Literal(Literal::Pointer), + ], + None, + )); + } + + fn record_result(&mut self, instrs: &mut Vec, res: val::Result) { + let result_record_callable_id = self.get_result_record_callable(); + instrs.push(Instruction::Call( + result_record_callable_id, + vec![ + Operand::Literal(Literal::Result( + res.unwrap_id() + .try_into() + .expect("result id should fit into u32"), + )), + Operand::Literal(Literal::Pointer), + ], + None, + )); + } + + fn record_tuple(&mut self, ty: &Ty, instrs: &mut Vec, vals: &Rc<[Value]>) { + let Ty::Tuple(elem_tys) = ty else { + panic!("expected tuple type for tuple value"); + }; + let tuple_record_callable_id = self.get_tuple_record_callable(); + instrs.push(Instruction::Call( + tuple_record_callable_id, + vec![ + Operand::Literal(Literal::Integer( + vals.len() + .try_into() + .expect("tuple length should fit into u32"), + )), + Operand::Literal(Literal::Pointer), + ], + None, + )); + for (val, elem_ty) in vals.iter().zip(elem_tys.iter()) { + instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)); + } + } + + fn record_array(&mut self, ty: &Ty, instrs: &mut Vec, vals: &Rc>) { + let Ty::Array(elem_ty) = ty else { + panic!("expected array type for array value"); + }; + let array_record_callable_id = self.get_array_record_callable(); + instrs.push(Instruction::Call( + array_record_callable_id, + vec![ + Operand::Literal(Literal::Integer( + vals.len() + .try_into() + .expect("array length should fit into u32"), + )), + Operand::Literal(Literal::Pointer), + ], + None, + )); + for val in vals.iter() { + instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)); + } + } + + fn get_array_record_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__rt__array_record_output") { + return *id; + } + + let callable = builder::array_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__array_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_tuple_record_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__rt__tuple_record_output") { + return *id; + } + + let callable = builder::tuple_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__tuple_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_result_record_callable(&mut self) -> CallableId { + if let Some(id) = self + .callables_map + .get("__quantum__rt__result_record_output") + { + return *id; + } + + let callable = builder::result_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__result_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_bool_record_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__rt__bool_record_output") { + return *id; + } + + let callable = builder::bool_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__bool_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_int_record_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__rt__int_record_output") { + return *id; + } + + let callable = builder::int_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__int_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } } impl<'a> Visitor<'a> for PartialEvaluator<'a> { @@ -1040,13 +1253,16 @@ impl<'a> Visitor<'a> for PartialEvaluator<'a> { let store_stmt_id = StoreStmtId::from((self.get_current_package_id(), stmt_id)); let stmt = self.package_store.get_stmt(store_stmt_id); match stmt.kind { - StmtKind::Expr(expr_id) | StmtKind::Semi(expr_id) => { + StmtKind::Expr(expr_id) => { + self.visit_expr(expr_id); + } + StmtKind::Semi(expr_id) => { self.visit_expr(expr_id); + self.eval_context.get_current_scope_mut().clear_last_expr(); } StmtKind::Local(_, 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); + if let Ok(value) = self.try_eval_expr(expr_id) { + self.bind_value_to_pat(pat_id, value); } } StmtKind::Item(_) => { @@ -1118,36 +1334,6 @@ fn map_fir_type_to_rir_type(ty: &Ty) -> rir::Ty { } } -fn mresetz_callable() -> Callable { - Callable { - name: "__quantum__qis__mresetz__body".to_string(), - input_type: vec![rir::Ty::Qubit, rir::Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn mz_callable() -> Callable { - Callable { - name: "__quantum__qis__mz__body".to_string(), - input_type: vec![rir::Ty::Qubit, rir::Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn read_result_callable() -> Callable { - Callable { - name: "__quantum__rt__read_result__body".to_string(), - input_type: vec![rir::Ty::Result], - output_type: Some(rir::Ty::Boolean), - body: None, - call_type: CallableType::Readout, - } -} - fn resolve_call_arg_operands(args_value: Value) -> Vec { let mut operands = Vec::::new(); if let Value::Tuple(elements) = args_value { diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs index dfce3fb7b2..f8364ee00d 100644 --- a/compiler/qsc_partial_eval/tests/branching.rs +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -1,58 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#![allow(clippy::needless_raw_string_hashes, clippy::similar_names)] +#![allow( + clippy::needless_raw_string_hashes, + clippy::similar_names, + clippy::too_many_lines +)] pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, Variable, -}; -use test_utils::{ - assert_block_instructions, assert_block_last_instruction, assert_callable, - compile_and_partially_evaluate, mresetz_callable, read_result_callable, -}; - -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, - } -} - -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, - } -} - -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, - } -} - -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, - } -} +use qsc_rir::rir::CallableId; +use test_utils::{assert_blocks, assert_callable, compile_and_partially_evaluate}; #[test] fn if_expression_with_true_condition() { @@ -71,18 +31,26 @@ fn if_expression_with_true_condition() { "#, }); let op_a_callable_id = CallableId(1); - assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); - assert_block_instructions( + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_blocks( &program, - BlockId(0), - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -102,10 +70,14 @@ fn if_expression_with_false_condition() { } "#, }); - // 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]); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], + ); } #[test] @@ -128,18 +100,26 @@ fn if_else_expression_with_true_condition() { "#, }); let op_a_callable_id = CallableId(1); - assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); - assert_block_instructions( + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_blocks( &program, - BlockId(0), - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -163,18 +143,26 @@ fn if_else_expression_with_false_condition() { "#, }); let op_b_callable_id = CallableId(1); - assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); - assert_block_instructions( + assert_callable( &program, - BlockId(0), - &[ - Instruction::Call( - op_b_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -201,18 +189,26 @@ fn if_elif_else_expression_with_true_elif_condition() { "#, }); let op_b_callable_id = CallableId(1); - assert_callable(&program, op_b_callable_id, &single_qubit_intrinsic_op_b()); - assert_block_instructions( + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_blocks( &program, - BlockId(0), - &[ - Instruction::Call( - op_b_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -236,41 +232,62 @@ fn if_expression_with_dynamic_condition() { // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); 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( + assert_callable( &program, - if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1)"#]], + ); } #[test] @@ -296,58 +313,78 @@ fn if_else_expression_with_dynamic_condition() { // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); let op_a_callable_id = CallableId(3); - assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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( + assert_callable( &program, - if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the else-block. - assert_block_instructions( + assert_blocks( &program, - else_block_id, - &[ - Instruction::Call( - op_b_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(5), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1) + Block 3:Block: + Call id(4), args( Qubit(0), ) + Jump(1)"#]], ); - - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } #[test] @@ -377,89 +414,101 @@ fn if_elif_else_expression_with_dynamic_condition() { // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); 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( + assert_callable( &program, - if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - - // 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, + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - assert_block_last_instruction(&program, else_block_id, &nested_branch_inst); - - // Verify the instructions in the nested-if-block. - assert_block_instructions( + let op_c_callable_id = CallableId(5); + assert_callable( &program, - nested_if_block_id, - &[ - Instruction::Call( - op_b_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Jump(nested_continuation_block_id), - ], + op_c_callable_id, + &expect![[r#" + Callable: + name: opC + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the nested-else-block. - assert_block_instructions( + assert_blocks( &program, - nested_else_block_id, - &[ - Instruction::Call( - op_c_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Jump(nested_continuation_block_id), - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(6), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(2), ) + Jump(1) + Block 3:Block: + Variable(2, Boolean) = Call id(2), args( Result(1), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Branch Variable(3, Boolean), 5, 6 + Block 4:Block: + Jump(1) + Block 5:Block: + Call id(4), args( Qubit(2), ) + Jump(4) + Block 6:Block: + Call id(5), args( Qubit(2), ) + Jump(4)"#]], ); - - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } #[test] @@ -484,41 +533,62 @@ fn if_expression_with_dynamic_condition_and_nested_if_expression_with_true_condi // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); 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( + assert_callable( &program, - if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1)"#]], + ); } #[test] @@ -543,32 +613,48 @@ fn if_expression_with_dynamic_condition_and_nested_if_expression_with_false_cond // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); - - // 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( + assert_callable( &program, - if_block_id, - &[Instruction::Jump(continuation_block_id)], + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], ); - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(3), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Jump(1)"#]], + ); } #[test] @@ -596,58 +682,78 @@ fn if_else_expression_with_dynamic_condition_and_nested_if_expression_with_true_ // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); let op_a_callable_id = CallableId(3); - assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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( + assert_callable( &program, - if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the else-block. - assert_block_instructions( + assert_blocks( &program, - else_block_id, - &[ - Instruction::Call( - op_b_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(5), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1) + Block 3:Block: + Call id(4), args( Qubit(0), ) + Jump(1)"#]], ); - - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } #[test] @@ -675,49 +781,64 @@ fn if_else_expression_with_dynamic_condition_and_nested_if_expression_with_false // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); 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( + assert_callable( &program, - if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the else-block. - assert_block_instructions( + assert_blocks( &program, - else_block_id, - &[Instruction::Jump(continuation_block_id)], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1) + Block 3:Block: + Jump(1)"#]], ); - - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } #[test] @@ -743,62 +864,69 @@ fn if_expression_with_dynamic_condition_and_nested_if_expression_with_dynamic_co // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); - 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_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], ); - assert_block_last_instruction(&program, if_block_id, &nested_branch_inst); - - // Verify the instructions in the nested-if-block. - assert_block_instructions( + let op_a_callable_id = CallableId(3); + assert_callable( &program, - nested_if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Jump(nested_continuation_block_id), - ], + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the nested-continuation-block. - assert_block_instructions( + assert_blocks( &program, - nested_continuation_block_id, - &[Instruction::Jump(continuation_block_id)], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Variable(2, Boolean) = Call id(2), args( Result(1), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Branch Variable(3, Boolean), 4, 3 + Block 3:Block: + Jump(1) + Block 4:Block: + Call id(3), args( Qubit(2), ) + Jump(3)"#]], ); - - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } #[allow(clippy::too_many_lines)] @@ -836,134 +964,123 @@ fn doubly_nested_if_else_expressions_with_dynamic_conditions() { // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); - let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); - 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( + assert_callable( &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), - ], + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], ); - - // Verify the instructions in the first nested else-block. - assert_block_instructions( + let read_result_callable_id = CallableId(2); + assert_callable( &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), - ], + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], ); - - // Verify the instructions in the first nested continuation-block. - assert_block_instructions( + let op_a_callable_id = CallableId(3); + assert_callable( &program, - first_nested_continuation_block_id, - &[Instruction::Jump(continuation_block_id)], + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - - // 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, + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - assert_block_last_instruction(&program, else_block_id, &second_nested_branch_inst); - - // Verify the instructions in the second nested if-block. - assert_block_instructions( + let op_c_callable_id = CallableId(5); + assert_callable( &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), - ], + op_c_callable_id, + &expect![[r#" + Callable: + name: opC + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - - // Verify the instructions in the second nested else-block. - assert_block_instructions( + let op_d_callable_id = CallableId(6); + assert_callable( &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), - ], + op_d_callable_id, + &expect![[r#" + Callable: + name: opD + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the second nested continuation-block. - assert_block_instructions( + assert_blocks( &program, - second_nested_continuation_block_id, - &[Instruction::Jump(continuation_block_id)], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 6 + Block 1:Block: + Call id(7), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Variable(2, Boolean) = Call id(2), args( Result(1), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(false) + Branch Variable(3, Boolean), 4, 5 + Block 3:Block: + Jump(1) + Block 4:Block: + Call id(3), args( Qubit(2), ) + Jump(3) + Block 5:Block: + Call id(4), args( Qubit(2), ) + Jump(3) + Block 6:Block: + Variable(4, Boolean) = Call id(2), args( Result(1), ) + Variable(5, Boolean) = Icmp Eq, Variable(4, Boolean), Bool(true) + Branch Variable(5, Boolean), 8, 9 + Block 7:Block: + Jump(1) + Block 8:Block: + Call id(5), args( Qubit(2), ) + Jump(7) + Block 9:Block: + Call id(6), args( Qubit(2), ) + Jump(7)"#]], ); - - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } #[test] @@ -988,53 +1105,75 @@ fn if_expression_with_dynamic_condition_and_subsequent_call_to_operation() { // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); let op_a_callable_id = CallableId(3); - assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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( + assert_callable( &program, - if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the continuation-block. - assert_block_instructions( + assert_blocks( &program, - continuation_block_id, - &[ - Instruction::Call( - op_b_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Qubit(0), ) + Call id(5), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1)"#]], ); } @@ -1063,69 +1202,90 @@ fn if_else_expression_with_dynamic_condition_and_subsequent_call_to_operation() // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); let op_a_callable_id = CallableId(3); - assert_callable(&program, op_a_callable_id, &single_qubit_intrinsic_op_a()); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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( + assert_callable( &program, - if_block_id, - &[ - Instruction::Call( - op_a_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - - // Verify the instructions in the else-block. - assert_block_instructions( + let op_c_callable_id = CallableId(5); + assert_callable( &program, - else_block_id, - &[ - Instruction::Call( - op_b_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Jump(continuation_block_id), - ], + op_c_callable_id, + &expect![[r#" + Callable: + name: opC + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], ); - // Verify the instructions in the continuation-block. - assert_block_instructions( + assert_blocks( &program, - continuation_block_id, - &[ - Instruction::Call( - op_c_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(5), args( Qubit(0), ) + Call id(6), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1) + Block 3:Block: + Call id(4), args( Qubit(0), ) + Jump(1)"#]], ); } diff --git a/compiler/qsc_partial_eval/tests/classical_args.rs b/compiler/qsc_partial_eval/tests/classical_args.rs index 6bec1ea12d..d3a85f6786 100644 --- a/compiler/qsc_partial_eval/tests/classical_args.rs +++ b/compiler/qsc_partial_eval/tests/classical_args.rs @@ -5,22 +5,11 @@ pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, -}; +use qsc_rir::rir::{BlockId, CallableId}; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; -fn double_to_unit_intrinsic_op() -> Callable { - Callable { - name: "op".to_string(), - input_type: vec![Ty::Double], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - #[test] fn call_to_intrinsic_operation_using_double_literal() { let program = compile_and_partially_evaluate(indoc! {r#" @@ -33,18 +22,26 @@ fn call_to_intrinsic_operation_using_double_literal() { } "#}); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &double_to_unit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Double + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(1.0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Double(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -63,28 +60,28 @@ fn calls_to_intrinsic_operation_using_inline_expressions() { } "#}); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &double_to_unit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Double + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(0.0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(1.0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(1.0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Double(0), ) + Call id(1), args( Double(1), ) + Call id(1), args( Double(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -106,27 +103,27 @@ fn calls_to_intrinsic_operation_using_variables() { } "#}); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &double_to_unit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Double + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(2.0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(4.0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(8.0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Double(2), ) + Call id(1), args( Double(4), ) + Call id(1), args( Double(8), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } diff --git a/compiler/qsc_partial_eval/tests/dynamic_vars.rs b/compiler/qsc_partial_eval/tests/dynamic_vars.rs index 8d44fd8e8a..a3c9c7b7c0 100644 --- a/compiler/qsc_partial_eval/tests/dynamic_vars.rs +++ b/compiler/qsc_partial_eval/tests/dynamic_vars.rs @@ -5,12 +5,12 @@ pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{BlockId, CallableId, Instruction, Literal, Operand, Ty, Variable}; -use test_utils::{ - assert_block_instructions, assert_block_last_instruction, assert_callable, - compile_and_partially_evaluate, mresetz_callable, read_result_callable, -}; +use qsc_rir::rir::CallableId; +use test_utils::{assert_callable, compile_and_partially_evaluate}; + +use crate::test_utils::assert_blocks; #[test] fn dynamic_int_from_if_expression_with_single_measurement_comparison_and_classical_blocks() { @@ -30,52 +30,52 @@ fn dynamic_int_from_if_expression_with_single_measurement_comparison_and_classic // Verify the callables added to the program. let mresetz_callable_id = CallableId(1); - assert_callable(&program, mresetz_callable_id, &mresetz_callable()); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let read_result_callable_id = CallableId(2); - assert_callable(&program, read_result_callable_id, &read_result_callable()); - - // 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); - - // Create the expected variable that will hold the dynamic integer value. - let dynamic_int_var = Variable { - variable_id: 2.into(), - ty: Ty::Integer, - }; - - // Verify the instructions in the if-block. - assert_block_instructions( + assert_callable( &program, - if_block_id, - &[ - Instruction::Store(Operand::Literal(Literal::Integer(0)), dynamic_int_var), - Instruction::Jump(continuation_block_id), - ], + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], ); - // Verify the instructions in the else-block. - assert_block_instructions( + assert_blocks( &program, - else_block_id, - &[ - Instruction::Store(Operand::Literal(Literal::Integer(1)), dynamic_int_var), - Instruction::Jump(continuation_block_id), - ], + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(3), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Variable(2, Integer) = Store Integer(0) + Jump(1) + Block 3:Block: + Variable(2, Integer) = Store Integer(1) + Jump(1)"#]], ); - - // Verify the instructions in the continuation-block. - assert_block_instructions(&program, continuation_block_id, &[Instruction::Return]); } #[test] diff --git a/compiler/qsc_partial_eval/tests/intrinsics.rs b/compiler/qsc_partial_eval/tests/intrinsics.rs index 6dbcf79603..4408d768f6 100644 --- a/compiler/qsc_partial_eval/tests/intrinsics.rs +++ b/compiler/qsc_partial_eval/tests/intrinsics.rs @@ -5,14 +5,15 @@ pub mod test_utils; +use expect_test::{expect, Expect}; use indoc::{formatdoc, indoc}; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, -}; +use qsc_rir::rir::{BlockId, CallableId}; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; fn check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -30,27 +31,14 @@ fn check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &single_qubit_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], - ); + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } fn check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -68,30 +56,14 @@ fn check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_in .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &single_qubit_rotation_intrinsic_op(intrinsic_name), - ); - 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::Return, - ], - ); + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } fn check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -109,31 +81,14 @@ fn check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_inst .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &two_qubits_rotation_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Double(0.0)), - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Qubit(1)), - ], - None, - ), - Instruction::Return, - ], - ); + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } fn check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -151,30 +106,14 @@ fn check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &two_qubits_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Qubit(1)), - ], - None, - ), - Instruction::Return, - ], - ); + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } fn check_call_to_three_qubits_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -192,103 +131,27 @@ fn check_call_to_three_qubits_instrinsic_adds_callable_and_generates_instruction .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &three_qubits_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Qubit(1)), - Operand::Literal(Literal::Qubit(2)), - ], - None, - ), - Instruction::Return, - ], - ); -} - -fn measurement_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Qubit, Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn reset_intrinsic_op() -> Callable { - Callable { - name: "__quantum__qis__reset__body".to_string(), - input_type: vec![Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Reset, - } -} - -fn single_qubit_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -fn single_qubit_rotation_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Double, Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -fn two_qubits_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Qubit, Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -fn two_qubits_rotation_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Double, Ty::Qubit, Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -fn three_qubits_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Qubit, Ty::Qubit, Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } #[test] fn call_to_intrinsic_h_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__h__body", + &expect![[r#" + Callable: + name: __quantum__qis__h__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -296,6 +159,19 @@ fn call_to_intrinsic_h_adds_callable_and_generates_instruction() { fn call_to_intrinsic_s_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__s__body", + &expect![[r#" + Callable: + name: __quantum__qis__s__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -303,6 +179,19 @@ fn call_to_intrinsic_s_adds_callable_and_generates_instruction() { fn call_to_intrinsic_adjoint_s_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__s__adj", + &expect![[r#" + Callable: + name: __quantum__qis__s__adj + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -310,6 +199,19 @@ fn call_to_intrinsic_adjoint_s_adds_callable_and_generates_instruction() { fn call_to_intrinsic_t_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__t__body", + &expect![[r#" + Callable: + name: __quantum__qis__t__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -317,6 +219,19 @@ fn call_to_intrinsic_t_adds_callable_and_generates_instruction() { fn call_to_intrinsic_adjoint_t_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__t__adj", + &expect![[r#" + Callable: + name: __quantum__qis__t__adj + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -324,6 +239,19 @@ fn call_to_intrinsic_adjoint_t_adds_callable_and_generates_instruction() { fn call_to_intrinsic_x_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__x__body", + &expect![[r#" + Callable: + name: __quantum__qis__x__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -331,6 +259,19 @@ fn call_to_intrinsic_x_adds_callable_and_generates_instruction() { fn call_to_intrinsic_y_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__y__body", + &expect![[r#" + Callable: + name: __quantum__qis__y__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -338,6 +279,19 @@ fn call_to_intrinsic_y_adds_callable_and_generates_instruction() { fn call_to_intrinsic_z_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__z__body", + &expect![[r#" + Callable: + name: __quantum__qis__z__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -345,6 +299,20 @@ fn call_to_intrinsic_z_adds_callable_and_generates_instruction() { fn call_to_intrinsic_swap_adds_callable_and_generates_instruction() { check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__swap__body", + &expect![[r#" + Callable: + name: __quantum__qis__swap__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -352,6 +320,20 @@ fn call_to_intrinsic_swap_adds_callable_and_generates_instruction() { fn call_to_intrinsic_cx_adds_callable_and_generates_instruction() { check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__cx__body", + &expect![[r#" + Callable: + name: __quantum__qis__cx__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -359,6 +341,20 @@ fn call_to_intrinsic_cx_adds_callable_and_generates_instruction() { fn call_to_intrinsic_cy_adds_callable_and_generates_instruction() { check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__cy__body", + &expect![[r#" + Callable: + name: __quantum__qis__cy__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -366,6 +362,20 @@ fn call_to_intrinsic_cy_adds_callable_and_generates_instruction() { fn call_to_intrinsic_cz_adds_callable_and_generates_instruction() { check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__cz__body", + &expect![[r#" + Callable: + name: __quantum__qis__cz__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -373,6 +383,21 @@ fn call_to_intrinsic_cz_adds_callable_and_generates_instruction() { fn call_to_intrinsic_ccx_adds_callable_and_generates_instruction() { check_call_to_three_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__ccx__body", + &expect![[r#" + Callable: + name: __quantum__qis__ccx__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + [2]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), Qubit(2), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -380,6 +405,20 @@ fn call_to_intrinsic_ccx_adds_callable_and_generates_instruction() { fn call_to_intrinsic_rx_adds_callable_and_generates_instruction() { check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__rx__body", + &expect![[r#" + Callable: + name: __quantum__qis__rx__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -387,6 +426,21 @@ fn call_to_intrinsic_rx_adds_callable_and_generates_instruction() { fn call_to_intrinsic_rxx_adds_callable_and_generates_instruction() { check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__rxx__body", + &expect![[r#" + Callable: + name: __quantum__qis__rxx__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + [2]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -394,6 +448,20 @@ fn call_to_intrinsic_rxx_adds_callable_and_generates_instruction() { fn call_to_intrinsic_ry_adds_callable_and_generates_instruction() { check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__ry__body", + &expect![[r#" + Callable: + name: __quantum__qis__ry__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -401,6 +469,21 @@ fn call_to_intrinsic_ry_adds_callable_and_generates_instruction() { fn call_to_intrinsic_ryy_adds_callable_and_generates_instruction() { check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__ryy__body", + &expect![[r#" + Callable: + name: __quantum__qis__ryy__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + [2]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -408,6 +491,20 @@ fn call_to_intrinsic_ryy_adds_callable_and_generates_instruction() { fn call_to_intrinsic_rz_adds_callable_and_generates_instruction() { check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__rz__body", + &expect![[r#" + Callable: + name: __quantum__qis__rz__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -415,6 +512,21 @@ fn call_to_intrinsic_rz_adds_callable_and_generates_instruction() { fn call_to_intrinsic_rzz_adds_callable_and_generates_instruction() { check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__rzz__body", + &expect![[r#" + Callable: + name: __quantum__qis__rzz__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + [2]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -432,18 +544,26 @@ fn check_partial_eval_for_call_to_reset() { "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &reset_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__reset__body + call_type: Reset + input_type: + [0]: Qubit + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -464,22 +584,24 @@ fn call_to_intrinsic_m_adds_callable_and_generates_instruction() { assert_callable( &program, op_callable_id, - &measurement_intrinsic_op("__quantum__qis__mz__body"), + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -500,21 +622,23 @@ fn call_to_intrinsic_mresetz_adds_callable_and_generates_instruction() { assert_callable( &program, op_callable_id, - &measurement_intrinsic_op("__quantum__qis__mresetz__body"), + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } diff --git a/compiler/qsc_partial_eval/tests/loops.rs b/compiler/qsc_partial_eval/tests/loops.rs index 24f443727b..541e143adc 100644 --- a/compiler/qsc_partial_eval/tests/loops.rs +++ b/compiler/qsc_partial_eval/tests/loops.rs @@ -5,32 +5,11 @@ pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, -}; +use qsc_rir::rir::{BlockId, CallableId}; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; -fn single_qubit_intrinsic() -> Callable { - Callable { - name: "op".to_string(), - input_type: vec![Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -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 unitary_call_within_a_for_loop() { let program = compile_and_partially_evaluate(indoc! { @@ -49,28 +28,28 @@ fn unitary_call_within_a_for_loop() { }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -94,28 +73,28 @@ fn unitary_call_within_a_while_loop() { }); let rotation_callable_id = CallableId(1); - assert_callable(&program, rotation_callable_id, &single_qubit_intrinsic()); + assert_callable( + &program, + rotation_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -139,28 +118,28 @@ fn unitary_call_within_a_repeat_until_loop() { }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -185,38 +164,26 @@ fn rotation_call_within_a_for_loop() { assert_callable( &program, rotation_callable_id, - &single_qubit_rotation_intrinsic(), + &expect![[r#" + Callable: + name: rotation + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(1), args( Double(1), Qubit(0), ) + Call id(1), args( Double(2), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -241,37 +208,29 @@ fn rotation_call_within_a_while_loop() { }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_rotation_intrinsic()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: rotation + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(1), args( Double(1), Qubit(0), ) + Call id(1), args( Double(2), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -299,37 +258,25 @@ fn rotation_call_within_a_repeat_until_loop() { assert_callable( &program, rotation_callable_id, - &single_qubit_rotation_intrinsic(), + &expect![[r#" + Callable: + name: rotation + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(1), args( Double(1), Qubit(0), ) + Call id(1), args( Double(2), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } diff --git a/compiler/qsc_partial_eval/tests/misc.rs b/compiler/qsc_partial_eval/tests/misc.rs index da2b4a7bab..77940e2a43 100644 --- a/compiler/qsc_partial_eval/tests/misc.rs +++ b/compiler/qsc_partial_eval/tests/misc.rs @@ -5,22 +5,11 @@ pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, -}; +use qsc_rir::rir::{BlockId, CallableId}; 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! { @@ -41,28 +30,28 @@ fn unitary_call_within_an_if_with_classical_condition_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, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -88,28 +77,28 @@ fn unitary_call_within_an_if_with_classical_condition_within_a_while_loop() { }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -135,27 +124,27 @@ fn unitary_call_within_an_if_with_classical_condition_within_a_repeat_until_loop }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } diff --git a/compiler/qsc_partial_eval/tests/output_recording.rs b/compiler/qsc_partial_eval/tests/output_recording.rs new file mode 100644 index 0000000000..3c9cd3c6f9 --- /dev/null +++ b/compiler/qsc_partial_eval/tests/output_recording.rs @@ -0,0 +1,526 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::needless_raw_string_hashes)] + +use expect_test::expect; +use indoc::indoc; +use test_utils::compile_and_partially_evaluate; + +pub mod test_utils; + +#[test] +fn output_recording_for_tuple_of_different_types() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Bool) { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + (r, r == Zero) + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: + Callable 3: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Call id(3), args( Integer(2), Pointer, ) + Call id(4), args( Result(0), Pointer, ) + Call id(5), args( Variable(1, Boolean), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_nested_tuples() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, (Bool, Result), (Bool,)) { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + (r, (r == Zero, r), (r == One,)) + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: + Callable 3: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Variable(2, Boolean) = Call id(2), args( Result(0), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Call id(3), args( Integer(3), Pointer, ) + Call id(4), args( Result(0), Pointer, ) + Call id(3), args( Integer(2), Pointer, ) + Call id(5), args( Variable(1, Boolean), Pointer, ) + Call id(4), args( Result(0), Pointer, ) + Call id(3), args( Integer(1), Pointer, ) + Call id(5), args( Variable(3, Boolean), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_tuple_of_arrays() { + // This program would not actually pass RCA checks as it shows up as using a dynamically sized array. + // However, the output recording should still be correct if/when we support this kind of return in the future. + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Bool[]) { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + (r, [r == Zero, r == One]) + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: + Callable 3: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__array_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 6: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Variable(2, Boolean) = Call id(2), args( Result(0), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Call id(3), args( Integer(2), Pointer, ) + Call id(4), args( Result(0), Pointer, ) + Call id(5), args( Integer(2), Pointer, ) + Call id(6), args( Variable(1, Boolean), Pointer, ) + Call id(6), args( Variable(3, Boolean), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_array_of_tuples() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Bool)[] { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + [(r, r == Zero), (r, r == One)] + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: + Callable 3: Callable: + name: __quantum__rt__array_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 6: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Variable(2, Boolean) = Call id(2), args( Result(0), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Call id(3), args( Integer(2), Pointer, ) + Call id(4), args( Integer(2), Pointer, ) + Call id(5), args( Result(0), Pointer, ) + Call id(6), args( Variable(1, Boolean), Pointer, ) + Call id(4), args( Integer(2), Pointer, ) + Call id(5), args( Result(0), Pointer, ) + Call id(6), args( Variable(3, Boolean), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_literal_bool() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Bool { + true + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Bool(true), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 0 + num_results: 0"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_literal_int() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Int { + 42 + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__rt__integer_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Integer(42), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 0 + num_results: 0"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_mix_of_literal_and_variable() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Bool) { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + (r, true) + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 3: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Integer(2), Pointer, ) + Call id(3), args( Result(0), Pointer, ) + Call id(4), args( Bool(true), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} diff --git a/compiler/qsc_partial_eval/tests/qubits.rs b/compiler/qsc_partial_eval/tests/qubits.rs index db2c9e9ee9..bfaf208138 100644 --- a/compiler/qsc_partial_eval/tests/qubits.rs +++ b/compiler/qsc_partial_eval/tests/qubits.rs @@ -5,22 +5,11 @@ pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, -}; +use qsc_rir::rir::{BlockId, CallableId}; 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 qubit_ids_are_correct_for_allocate_use_release_one_qubit() { let program = compile_and_partially_evaluate(indoc! { @@ -36,22 +25,31 @@ fn qubit_ids_are_correct_for_allocate_use_release_one_qubit() { } "#, }); - 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::Return, - ], - ); - assert_eq!(program.num_qubits, 1); - assert_eq!(program.num_results, 0); + expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]] + .assert_eq(&program.get_callable(CallableId(1)).to_string()); + expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]] + .assert_eq(&program.get_callable(CallableId(2)).to_string()); + expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]] + .assert_eq(&program.get_block(BlockId(0)).to_string()); } #[test] @@ -76,28 +74,42 @@ fn qubit_ids_are_correct_for_allocate_use_release_multiple_qubits() { "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let tuple_callable_id = CallableId(2); + assert_callable( + &program, + tuple_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); 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(1))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(1), ) + Call id(1), args( Qubit(2), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 3); assert_eq!(program.num_results, 0); @@ -125,28 +137,42 @@ fn qubit_ids_are_correct_for_allocate_use_release_one_qubit_multiple_times() { "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let tuple_callable_id = CallableId(2); + assert_callable( + &program, + tuple_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); 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, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 1); assert_eq!(program.num_results, 0); @@ -180,38 +206,44 @@ fn qubit_ids_are_correct_for_allocate_use_release_multiple_qubits_interleaved() "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let tuple_callable_id = CallableId(2); + assert_callable( + &program, + tuple_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); 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(1))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(3))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(1), ) + Call id(1), args( Qubit(2), ) + Call id(1), args( Qubit(2), ) + Call id(1), args( Qubit(3), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 4); assert_eq!(program.num_results, 0); diff --git a/compiler/qsc_partial_eval/tests/results.rs b/compiler/qsc_partial_eval/tests/results.rs index 64c3adf5c5..c6ef1c7303 100644 --- a/compiler/qsc_partial_eval/tests/results.rs +++ b/compiler/qsc_partial_eval/tests/results.rs @@ -5,72 +5,60 @@ pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, ConditionCode, Instruction, Literal, Operand, Ty, - Variable, VariableId, -}; +use qsc_rir::rir::{BlockId, CallableId}; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; -fn m_intrinsic_op() -> Callable { - Callable { - name: "__quantum__qis__mz__body".to_string(), - input_type: vec![Ty::Qubit, Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn mresetz_intrinsic_op() -> 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_reasult_intrinsic_op() -> 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, - } -} - #[test] fn result_ids_are_correct_for_measuring_and_resetting_one_qubit() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Result { use q = Qubit(); - QIR.Intrinsic.__quantum__qis__mresetz__body(q); + QIR.Intrinsic.__quantum__qis__mresetz__body(q) } } "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &mresetz_intrinsic_op()); + let result_record_id = CallableId(2); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Result(0), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 1); assert_eq!(program.num_results, 1); @@ -82,29 +70,49 @@ fn result_ids_are_correct_for_measuring_one_qubit() { r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Result { use q = Qubit(); - QIR.Intrinsic.__quantum__qis__m__body(q); + QIR.Intrinsic.__quantum__qis__m__body(q) } } "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &m_intrinsic_op()); + let result_record_id = CallableId(2); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Result(0), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 1); assert_eq!(program.num_results, 1); @@ -116,47 +124,143 @@ fn result_ids_are_correct_for_measuring_one_qubit_multiple_times() { r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : (Result, Result, Result) { use q = Qubit(); - QIR.Intrinsic.__quantum__qis__m__body(q); - QIR.Intrinsic.__quantum__qis__m__body(q); - QIR.Intrinsic.__quantum__qis__m__body(q); + (QIR.Intrinsic.__quantum__qis__m__body(q), + QIR.Intrinsic.__quantum__qis__m__body(q), + QIR.Intrinsic.__quantum__qis__m__body(q)) } } "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &m_intrinsic_op()); + let tuple_record_id = CallableId(2); + let result_record_id = CallableId(3); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + tuple_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(1)), - ], - None, - ), - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(2)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(0), Result(1), ) + Call id(1), args( Qubit(0), Result(2), ) + Call id(2), args( Integer(3), Pointer, ) + Call id(3), args( Result(0), Pointer, ) + Call id(3), args( Result(1), Pointer, ) + Call id(3), args( Result(2), Pointer, ) + Return"#]], + ); +} + +#[test] +fn result_ids_are_correct_for_measuring_one_qubit_multiple_times_into_array() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Result[] { + use q = Qubit(); + [QIR.Intrinsic.__quantum__qis__m__body(q), + QIR.Intrinsic.__quantum__qis__m__body(q), + QIR.Intrinsic.__quantum__qis__m__body(q)] + } + } + "#, + }); + let op_callable_id = CallableId(1); + let array_record_id = CallableId(2); + let result_record_id = CallableId(3); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + array_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__array_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(0), Result(1), ) + Call id(1), args( Qubit(0), Result(2), ) + Call id(2), args( Integer(3), Pointer, ) + Call id(3), args( Result(0), Pointer, ) + Call id(3), args( Result(1), Pointer, ) + Call id(3), args( Result(2), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 1); assert_eq!(program.num_results, 3); @@ -168,47 +272,70 @@ fn result_ids_are_correct_for_measuring_multiple_qubits() { r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : (Result, Result, Result) { use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); - QIR.Intrinsic.__quantum__qis__m__body(q0); - QIR.Intrinsic.__quantum__qis__m__body(q1); - QIR.Intrinsic.__quantum__qis__m__body(q2); + (QIR.Intrinsic.__quantum__qis__m__body(q0), + QIR.Intrinsic.__quantum__qis__m__body(q1), + QIR.Intrinsic.__quantum__qis__m__body(q2)) } } "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &m_intrinsic_op()); + let tuple_record_id = CallableId(2); + let result_record_id = CallableId(3); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + tuple_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(1)), - Operand::Literal(Literal::Result(1)), - ], - None, - ), - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(2)), - Operand::Literal(Literal::Result(2)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Call id(1), args( Qubit(2), Result(2), ) + Call id(2), args( Integer(3), Pointer, ) + Call id(3), args( Result(0), Pointer, ) + Call id(3), args( Result(1), Pointer, ) + Call id(3), args( Result(2), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 3); assert_eq!(program.num_results, 3); @@ -220,72 +347,68 @@ fn comparing_measurement_results_for_equality_adds_read_result_and_comparison_in r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Bool { use (q0, q1) = (Qubit(), Qubit()); let r0 = QIR.Intrinsic.__quantum__qis__m__body(q0); let r1 = QIR.Intrinsic.__quantum__qis__m__body(q1); - let b = r0 == r1; + r0 == r1 } } "#, }); let measurement_callable_id = CallableId(1); - assert_callable(&program, measurement_callable_id, &m_intrinsic_op()); + assert_callable( + &program, + measurement_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let readout_callable_id = CallableId(2); - assert_callable(&program, readout_callable_id, &read_reasult_intrinsic_op()); + assert_callable( + &program, + readout_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let bool_record_id = CallableId(3); + assert_callable( + &program, + bool_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &vec![ - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(1)), - Operand::Literal(Literal::Result(1)), - ], - None, - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(0))], - Some(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(1))], - Some(Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }), - ), - Instruction::Icmp( - ConditionCode::Eq, - Operand::Variable(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - Operand::Variable(Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }), - Variable { - variable_id: VariableId(2), - ty: Ty::Boolean, - }, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Call id(2), args( Result(1), ) + Variable(2, Boolean) = Icmp Eq, Variable(0, Boolean), Variable(1, Boolean) + Call id(3), args( Variable(2, Boolean), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 2); assert_eq!(program.num_results, 2); @@ -297,72 +420,68 @@ fn comparing_measurement_results_for_inequality_adds_read_result_and_comparison_ r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Bool { use (q0, q1) = (Qubit(), Qubit()); let r0 = QIR.Intrinsic.__quantum__qis__m__body(q0); let r1 = QIR.Intrinsic.__quantum__qis__m__body(q1); - let b = r0 != r1; + r0 != r1 } } "#, }); let measurement_callable_id = CallableId(1); - assert_callable(&program, measurement_callable_id, &m_intrinsic_op()); + assert_callable( + &program, + measurement_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let readout_callable_id = CallableId(2); - assert_callable(&program, readout_callable_id, &read_reasult_intrinsic_op()); + assert_callable( + &program, + readout_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let bool_record_id = CallableId(3); + assert_callable( + &program, + bool_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &vec![ - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(1)), - Operand::Literal(Literal::Result(1)), - ], - None, - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(0))], - Some(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(1))], - Some(Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }), - ), - Instruction::Icmp( - ConditionCode::Ne, - Operand::Variable(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - Operand::Variable(Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }), - Variable { - variable_id: VariableId(2), - ty: Ty::Boolean, - }, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Call id(2), args( Result(1), ) + Variable(2, Boolean) = Icmp Ne, Variable(0, Boolean), Variable(1, Boolean) + Call id(3), args( Variable(2, Boolean), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 2); assert_eq!(program.num_results, 2); @@ -375,52 +494,65 @@ fn comparing_measurement_result_against_result_literal_for_equality_adds_read_re r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Bool { use q = Qubit(); let r = QIR.Intrinsic.__quantum__qis__m__body(q); - let b = r == One; + r == One } } "#, }); let measurement_callable_id = CallableId(1); - assert_callable(&program, measurement_callable_id, &m_intrinsic_op()); + assert_callable( + &program, + measurement_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let readout_callable_id = CallableId(2); - assert_callable(&program, readout_callable_id, &read_reasult_intrinsic_op()); + assert_callable( + &program, + readout_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let bool_record_id = CallableId(3); + assert_callable( + &program, + bool_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(0))], - Some(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - ), - Instruction::Icmp( - ConditionCode::Eq, - Operand::Variable(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - Operand::Literal(Literal::Bool(true)), - Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Call id(3), args( Variable(1, Boolean), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 1); assert_eq!(program.num_results, 1); @@ -433,52 +565,65 @@ fn comparing_measurement_result_against_result_literal_for_inequality_adds_read_ r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Bool { use q = Qubit(); let r = QIR.Intrinsic.__quantum__qis__m__body(q); - let b = r != Zero; + r != Zero } } "#, }); let measurement_callable_id = CallableId(1); - assert_callable(&program, measurement_callable_id, &m_intrinsic_op()); + assert_callable( + &program, + measurement_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let readout_callable_id = CallableId(2); - assert_callable(&program, readout_callable_id, &read_reasult_intrinsic_op()); + assert_callable( + &program, + readout_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let bool_record_id = CallableId(3); + assert_callable( + &program, + bool_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(0))], - Some(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - ), - Instruction::Icmp( - ConditionCode::Ne, - Operand::Variable(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - Operand::Literal(Literal::Bool(false)), - Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Ne, Variable(0, Boolean), Bool(false) + Call id(3), args( Variable(1, Boolean), Pointer, ) + Return"#]], ); assert_eq!(program.num_qubits, 1); assert_eq!(program.num_results, 1); diff --git a/compiler/qsc_partial_eval/tests/test_utils.rs b/compiler/qsc_partial_eval/tests/test_utils.rs index 8141a2108d..2552d8083d 100644 --- a/compiler/qsc_partial_eval/tests/test_utils.rs +++ b/compiler/qsc_partial_eval/tests/test_utils.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use expect_test::Expect; use qsc::{incremental::Compiler, PackageType}; use qsc_data_structures::language_features::LanguageFeatures; use qsc_fir::fir::PackageStore; @@ -8,40 +9,26 @@ use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCa use qsc_lowerer::{map_hir_package_to_fir, Lowerer}; use qsc_partial_eval::{partially_evaluate, ProgramEntry}; use qsc_rca::{Analyzer, PackageStoreComputeProperties}; -use qsc_rir::rir::{BlockId, Callable, CallableId, CallableType, Instruction, Program, Ty}; +use qsc_rir::rir::{BlockId, CallableId, Program}; -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, expected_insts: &Expect) { + let block = program.get_block(block_id); + expected_insts.assert_eq(&block.to_string()); } -pub fn assert_block_instructions( - program: &Program, - block_id: BlockId, - expected_insts: &[Instruction], -) { - let block = program.blocks.get(block_id).expect("block does not exist"); - assert_eq!( - block.0.len(), - expected_insts.len(), - "expected number of instructions is different than actual number of instructions" - ); - for (expected_inst, actual_inst) in expected_insts.iter().zip(block.0.iter()) { - assert_eq!(expected_inst, actual_inst); - } +pub fn assert_blocks(program: &Program, expected_blocks: &Expect) { + let all_blocks = program + .blocks + .iter() + .fold("Blocks:".to_string(), |acc, (id, block)| { + acc + &format!("\nBlock {}:", id.0) + &block.to_string() + }); + expected_blocks.assert_eq(&all_blocks); } -pub fn assert_callable(program: &Program, callable_id: CallableId, expected_callable: &Callable) { - let actual_callable = program - .callables - .get(callable_id) - .expect("callable does not exist "); - assert_eq!(expected_callable, actual_callable); +pub fn assert_callable(program: &Program, callable_id: CallableId, expected_callable: &Expect) { + let actual_callable = program.get_callable(callable_id); + expected_callable.assert_eq(&actual_callable.to_string()); } #[must_use] @@ -58,28 +45,6 @@ pub fn compile_and_partially_evaluate(source: &str) -> Program { } } -#[must_use] -pub fn mresetz_callable() -> 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, - } -} - -#[must_use] -pub fn read_result_callable() -> 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, - } -} - struct CompilationContext { fir_store: PackageStore, compute_properties: PackageStoreComputeProperties, diff --git a/compiler/qsc_passes/src/loop_unification.rs b/compiler/qsc_passes/src/loop_unification.rs index 147bbb4e24..7edef04211 100644 --- a/compiler/qsc_passes/src/loop_unification.rs +++ b/compiler/qsc_passes/src/loop_unification.rs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use core::panic; use std::mem::take; use qsc_data_structures::span::Span; diff --git a/compiler/qsc_rir/src/builder.rs b/compiler/qsc_rir/src/builder.rs index 5ed292af2d..135f74c79a 100644 --- a/compiler/qsc_rir/src/builder.rs +++ b/compiler/qsc_rir/src/builder.rs @@ -116,6 +116,28 @@ pub fn result_record_decl() -> Callable { } } +#[must_use] +pub fn int_record_decl() -> Callable { + Callable { + name: "__quantum__rt__integer_record_output".to_string(), + input_type: vec![Ty::Integer, Ty::Pointer], + output_type: None, + body: None, + call_type: CallableType::OutputRecording, + } +} + +#[must_use] +pub fn bool_record_decl() -> Callable { + Callable { + name: "__quantum__rt__bool_record_output".to_string(), + input_type: vec![Ty::Boolean, Ty::Pointer], + output_type: None, + body: None, + call_type: CallableType::OutputRecording, + } +} + #[must_use] pub fn array_record_decl() -> Callable { Callable { @@ -127,6 +149,17 @@ pub fn array_record_decl() -> Callable { } } +#[must_use] +pub fn tuple_record_decl() -> Callable { + Callable { + name: "__quantum__rt__tuple_record_output".to_string(), + input_type: vec![Ty::Integer, Ty::Pointer], + output_type: None, + body: None, + call_type: CallableType::OutputRecording, + } +} + /// Creates a new program with a single, entry callable that has block 0 as its body. #[must_use] pub fn new_program() -> Program { diff --git a/compiler/qsc_rir/src/passes/unreachable_code_check.rs b/compiler/qsc_rir/src/passes/unreachable_code_check.rs index 31066bb875..ebe20d797e 100644 --- a/compiler/qsc_rir/src/passes/unreachable_code_check.rs +++ b/compiler/qsc_rir/src/passes/unreachable_code_check.rs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use core::panic; use std::iter::once; use crate::{ diff --git a/pip/tests/test_interpreter.py b/pip/tests/test_interpreter.py index af76a540e0..bab27dcfbe 100644 --- a/pip/tests/test_interpreter.py +++ b/pip/tests/test_interpreter.py @@ -321,7 +321,7 @@ def test_adaptive_qir_can_be_generated() -> None: open Microsoft.Quantum.Math; open QIR.Intrinsic; @EntryPoint() - operation Main() : Unit { + operation Main() : Result { use q = Qubit(); let pi_over_two = 4.0 / 2.0; __quantum__qis__rz__body(pi_over_two, q); @@ -329,6 +329,7 @@ def test_adaptive_qir_can_be_generated() -> None: __quantum__qis__rz__body(some_angle, q); set some_angle = ArcCos(-1.0) / PI(); __quantum__qis__rz__body(some_angle, q); + __quantum__qis__mresetz__body(q) } } """ @@ -345,12 +346,18 @@ def test_adaptive_qir_can_be_generated() -> None: call void @__quantum__qis__rz__body(double 2.0, %Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__rz__body(double 0.0, %Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__rz__body(double 1.0, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) ret void } declare void @__quantum__qis__rz__body(double, %Qubit*) - attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" } + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } attributes #1 = { "irreversible" } ; module flags