From 8924c658e914507455bd5d8d7fef091594df1699 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 29 Apr 2025 15:30:50 -0700 Subject: [PATCH 1/2] Support return values from custom intrinsics This change updates the handling for intrinsic generation such that any of the supported dynamic types for QIR generation (specifically `Bool`, `Int`, and `Double` in the matching profiles) can be returned from intrinsic callables. These were already correctly handled and analyzed from Runtime Capabilities Analysis, so all that was missing was support during partial evaluation and RIR generation. --- compiler/qsc/src/interpret/tests.rs | 40 +++++ compiler/qsc_partial_eval/src/lib.rs | 40 +++-- .../qsc_partial_eval/src/tests/intrinsics.rs | 144 ++++++++++++++++++ 3 files changed, 211 insertions(+), 13 deletions(-) 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..aa7ef6e035 100644 --- a/compiler/qsc_partial_eval/src/tests/intrinsics.rs +++ b/compiler/qsc_partial_eval/src/tests/intrinsics.rs @@ -1152,3 +1152,147 @@ 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(); + } + } + "}); +} From 04bbf92f24d765213df4ef5ce25c10da7c697b0a Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 29 Apr 2025 15:32:00 -0700 Subject: [PATCH 2/2] Fix formatting --- compiler/qsc_partial_eval/src/tests/intrinsics.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/qsc_partial_eval/src/tests/intrinsics.rs b/compiler/qsc_partial_eval/src/tests/intrinsics.rs index aa7ef6e035..0e8dbf66f6 100644 --- a/compiler/qsc_partial_eval/src/tests/intrinsics.rs +++ b/compiler/qsc_partial_eval/src/tests/intrinsics.rs @@ -1153,7 +1153,6 @@ fn call_to_operation_with_codegen_intrinsic_override_should_skip_impl() { ); } - #[test] fn call_to_intrinsic_operation_that_returns_bool_value_should_produce_variable_usage() { let program = get_rir_program(indoc! {" @@ -1266,7 +1265,9 @@ fn call_to_intrinsic_operation_that_returns_double_value_should_produce_variable } #[test] -#[should_panic(expected = "partial evaluation failed: UnexpectedDynamicIntrinsicReturnType(\"Result\", PackageSpan { package: PackageId(2), span: Span { lo: 137, hi: 140 } })")] +#[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 { @@ -1282,7 +1283,9 @@ fn call_to_intrinsic_operation_that_returns_result_value_should_fail() { } #[test] -#[should_panic(expected = "partial evaluation failed: UnexpectedDynamicIntrinsicReturnType(\"Qubit\", PackageSpan { package: PackageId(2), span: Span { lo: 142, hi: 145 } })")] +#[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 {