diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs index 06e2f294a1..3dc5078dbe 100644 --- a/compiler/qsc/src/interpret/tests.rs +++ b/compiler/qsc/src/interpret/tests.rs @@ -1296,6 +1296,46 @@ mod given_interpreter { ); } + #[test] + fn adaptive_qirgen_custom_intrinsic_returning_bool() { + let mut interpreter = get_interpreter_with_capabilities( + TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::QubitReset, + ); + let res = interpreter + .qirgen("{ operation check_result(r : Result) : Bool { body intrinsic; }; operation Foo() : Bool { use q = Qubit(); let r = MResetZ(q); check_result(r) } Foo() }") + .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 @check_result(%Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__bool_record_output(i1 %var_0, i8* null) + ret void + } + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare i1 @check_result(%Result*) + + 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 + + !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 run_with_shots() { let mut interpreter = get_interpreter(); diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index e7f546308f..ae047c4481 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -99,6 +99,10 @@ pub enum Error { #[diagnostic(help("try invoking the desired callable directly"))] UnexpectedDynamicValue(#[label] PackageSpan), + #[error("cannot use a dynamic value of type `{0}` returned from intrinsic callable")] + #[diagnostic(code("Qsc.PartialEval.UnexpectedDynamicIntrsinicReturnType"))] + UnexpectedDynamicIntrinsicReturnType(String, #[label] PackageSpan), + #[error("partial evaluation failed with error: {0}")] #[diagnostic(code("Qsc.PartialEval.EvaluationFailed"))] EvaluationFailed(String, #[label] PackageSpan), @@ -134,6 +138,7 @@ impl Error { match self { Self::CapabilityError(_) => None, Self::UnexpectedDynamicValue(span) + | Self::UnexpectedDynamicIntrinsicReturnType(_, span) | Self::EvaluationFailed(_, span) | Self::OutputResultLiteral(span) | Self::Unexpected(_, span) @@ -1638,19 +1643,16 @@ impl<'a> PartialEvaluator<'a> { callee_expr_span: PackageSpan, call_type: CallableType, ) -> Result { - // Intrinsic callables that make it to this point are expected to be unitary. - if callable_decl.output != Ty::UNIT { - return Err(Error::Unexpected( - format!( - "non-classical call to non-Unit intrinsic `{}`", - callable_decl.name.name - ), - callee_expr_span, - )); - } - // Check if the callable is already in the program, and if not add it. let callable = self.create_intrinsic_callable(store_item_id, callable_decl, call_type); + let output_var = callable.output_type.map(|output_ty| { + let variable_id = self.resource_manager.next_var(); + rir::Variable { + variable_id, + ty: output_ty, + } + }); + let callable_id = self.get_or_insert_callable(callable); // Resove the call arguments, create the call instruction and insert it to the current block. @@ -1672,10 +1674,22 @@ impl<'a> PartialEvaluator<'a> { .map(|arg| self.map_eval_value_to_rir_operand(&arg.into_value())) .collect(); - let instruction = Instruction::Call(callable_id, args_operands, None); + let instruction = Instruction::Call(callable_id, args_operands, output_var); let current_block = self.get_current_rir_block_mut(); current_block.0.push(instruction); - Ok(Value::unit()) + let ret_val = match output_var { + None => Value::unit(), + Some(output_var) => { + let rir_var = map_rir_var_to_eval_var(output_var).map_err(|()| { + Error::UnexpectedDynamicIntrinsicReturnType( + callable_decl.output.to_string(), + callee_expr_span, + ) + })?; + Value::Var(rir_var) + } + }; + Ok(ret_val) } fn eval_expr_call_to_spec( diff --git a/compiler/qsc_partial_eval/src/tests/intrinsics.rs b/compiler/qsc_partial_eval/src/tests/intrinsics.rs index 4a2379682f..0e8dbf66f6 100644 --- a/compiler/qsc_partial_eval/src/tests/intrinsics.rs +++ b/compiler/qsc_partial_eval/src/tests/intrinsics.rs @@ -1152,3 +1152,150 @@ fn call_to_operation_with_codegen_intrinsic_override_should_skip_impl() { Return"#]], ); } + +#[test] +fn call_to_intrinsic_operation_that_returns_bool_value_should_produce_variable_usage() { + let program = get_rir_program(indoc! {" + namespace Test { + operation Op1() : Bool { + body intrinsic; + } + @EntryPoint() + operation Main() : Bool { + Op1() + } + } + "}); + + let op1_callable_id = CallableId(1); + assert_callable( + &program, + op1_callable_id, + &expect![[r#" + Callable: + name: Op1 + call_type: Regular + input_type: + output_type: Boolean + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Variable(0, Boolean) = Call id(1), args( ) + Call id(2), args( Variable(0, Boolean), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_intrinsic_operation_that_returns_int_value_should_produce_variable_usage() { + let program = get_rir_program(indoc! {" + namespace Test { + operation Op1() : Int { + body intrinsic; + } + @EntryPoint() + operation Main() : Int { + Op1() + } + } + "}); + + let op1_callable_id = CallableId(1); + assert_callable( + &program, + op1_callable_id, + &expect![[r#" + Callable: + name: Op1 + call_type: Regular + input_type: + output_type: Integer + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Variable(0, Integer) = Call id(1), args( ) + Call id(2), args( Variable(0, Integer), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_intrinsic_operation_that_returns_double_value_should_produce_variable_usage() { + let program = get_rir_program(indoc! {" + namespace Test { + operation Op1() : Double { + body intrinsic; + } + @EntryPoint() + operation Main() : Double { + Op1() + } + } + "}); + + let op1_callable_id = CallableId(1); + assert_callable( + &program, + op1_callable_id, + &expect![[r#" + Callable: + name: Op1 + call_type: Regular + input_type: + output_type: Double + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Variable(0, Double) = Call id(1), args( ) + Call id(2), args( Variable(0, Double), Pointer, ) + Return"#]], + ); +} + +#[test] +#[should_panic( + expected = "partial evaluation failed: UnexpectedDynamicIntrinsicReturnType(\"Result\", PackageSpan { package: PackageId(2), span: Span { lo: 137, hi: 140 } })" +)] +fn call_to_intrinsic_operation_that_returns_result_value_should_fail() { + let _ = get_rir_program(indoc! {" + namespace Test { + operation Op1() : Result { + body intrinsic; + } + @EntryPoint() + operation Main() : Result { + Op1() + } + } + "}); +} + +#[test] +#[should_panic( + expected = "partial evaluation failed: UnexpectedDynamicIntrinsicReturnType(\"Qubit\", PackageSpan { package: PackageId(2), span: Span { lo: 142, hi: 145 } })" +)] +fn call_to_intrinsic_operation_that_returns_qubit_value_should_fail() { + let _ = get_rir_program(indoc! {" + namespace Test { + operation Op1() : Qubit { + body intrinsic; + } + @EntryPoint() + operation Main() : Unit { + let q = Op1(); + } + } + "}); +}