Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions compiler/qsc/src/interpret/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
40 changes: 27 additions & 13 deletions compiler/qsc_partial_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1638,19 +1643,16 @@ impl<'a> PartialEvaluator<'a> {
callee_expr_span: PackageSpan,
call_type: CallableType,
) -> Result<Value, Error> {
// 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.
Expand All @@ -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(
Expand Down
147 changes: 147 additions & 0 deletions compiler/qsc_partial_eval/src/tests/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <VOID>
output_type: Boolean
body: <NONE>"#]],
);
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: <VOID>
output_type: Integer
body: <NONE>"#]],
);
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: <VOID>
output_type: Double
body: <NONE>"#]],
);
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();
}
}
"});
}
Loading