diff --git a/source/compiler/qsc/src/codegen/tests.rs b/source/compiler/qsc/src/codegen/tests.rs index 593c6b2844e..1d2bfa2176b 100644 --- a/source/compiler/qsc/src/codegen/tests.rs +++ b/source/compiler/qsc/src/codegen/tests.rs @@ -6030,6 +6030,7 @@ fn array_with_dynamic_contents_passed_as_argument_with_static_index_does_not_emi block_0: %var_2 = alloca i64 %var_5 = alloca i64 + %var_8 = alloca i64 call void @__quantum__rt__initialize(ptr null) call void @__quantum__qis__m__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) %var_0 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) @@ -6051,8 +6052,10 @@ fn array_with_dynamic_contents_passed_as_argument_with_static_index_does_not_emi store i64 0, ptr %var_5 br label %block_6 block_6: - %var_8 = load i64, ptr %var_2 - call void @__quantum__rt__int_record_output(i64 %var_8, ptr @0) + %var_11 = load i64, ptr %var_2 + store i64 %var_11, ptr %var_8 + %var_13 = load i64, ptr %var_8 + call void @__quantum__rt__int_record_output(i64 %var_13, ptr @0) ret i64 0 } diff --git a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs index b49998dc738..d8ef6b4dcc7 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs @@ -534,6 +534,7 @@ fn result_array_dynamic_index_succeeds() { %var_2 = alloca i64 %var_3 = alloca i64 %var_5 = alloca i1 + %var_11 = alloca i64 call void @__quantum__rt__initialize(ptr null) call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr)) @@ -543,36 +544,38 @@ fn result_array_dynamic_index_succeeds() { store i64 0, ptr %var_3 br label %block_1 block_1: - %var_13 = load i64, ptr %var_3 - %var_4 = icmp sle i64 %var_13, 3 + %var_14 = load i64, ptr %var_3 + %var_4 = icmp sle i64 %var_14, 3 store i1 true, ptr %var_5 br i1 %var_4, label %block_2, label %block_3 block_2: - %var_16 = load i1, ptr %var_5 - br i1 %var_16, label %block_4, label %block_5 + %var_17 = load i1, ptr %var_5 + br i1 %var_17, label %block_4, label %block_5 block_3: store i1 false, ptr %var_5 br label %block_2 block_4: - %var_18 = load i64, ptr %var_3 - %var_19_offset_chk = icmp slt i64 %var_18, 0 - %var_19_offset = select i1 %var_19_offset_chk, i64 1, i64 0 - %var_19 = getelementptr [4 x ptr], ptr @array0, i64 %var_19_offset, i64 %var_18 - %var_6 = load ptr, ptr %var_19 + %var_21 = load i64, ptr %var_3 + %var_22_offset_chk = icmp slt i64 %var_21, 0 + %var_22_offset = select i1 %var_22_offset_chk, i64 1, i64 0 + %var_22 = getelementptr [4 x ptr], ptr @array0, i64 %var_22_offset, i64 %var_21 + %var_6 = load ptr, ptr %var_22 %var_7 = call i1 @__quantum__rt__read_result(ptr %var_6) br i1 %var_7, label %block_6, label %block_7 block_5: - %var_17 = load i64, ptr %var_2 - call void @__quantum__rt__int_record_output(i64 %var_17, ptr @0) + %var_18 = load i64, ptr %var_2 + store i64 %var_18, ptr %var_11 + %var_20 = load i64, ptr %var_11 + call void @__quantum__rt__int_record_output(i64 %var_20, ptr @0) ret i64 0 block_6: - %var_22 = load i64, ptr %var_2 - %var_9 = add i64 %var_22, 1 + %var_25 = load i64, ptr %var_2 + %var_9 = add i64 %var_25, 1 store i64 %var_9, ptr %var_2 br label %block_7 block_7: - %var_20 = load i64, ptr %var_3 - %var_10 = add i64 %var_20, 1 + %var_23 = load i64, ptr %var_3 + %var_10 = add i64 %var_23, 1 store i64 %var_10, ptr %var_3 br label %block_1 } @@ -641,6 +644,7 @@ fn result_array_while_loop_dynamic_index_succeeds() { block_0: %var_2 = alloca i64 %var_3 = alloca i64 + %var_10 = alloca i64 call void @__quantum__rt__initialize(ptr null) call void @H(ptr inttoptr (i64 0 to ptr)) call void @H(ptr inttoptr (i64 1 to ptr)) @@ -654,29 +658,31 @@ fn result_array_while_loop_dynamic_index_succeeds() { store i64 0, ptr %var_3 br label %block_1 block_1: - %var_12 = load i64, ptr %var_3 - %var_4 = icmp slt i64 %var_12, 4 + %var_13 = load i64, ptr %var_3 + %var_4 = icmp slt i64 %var_13, 4 br i1 %var_4, label %block_2, label %block_3 block_2: - %var_14 = load i64, ptr %var_3 - %var_15_offset_chk = icmp slt i64 %var_14, 0 - %var_15_offset = select i1 %var_15_offset_chk, i64 1, i64 0 - %var_15 = getelementptr [4 x ptr], ptr @array0, i64 %var_15_offset, i64 %var_14 - %var_5 = load ptr, ptr %var_15 + %var_17 = load i64, ptr %var_3 + %var_18_offset_chk = icmp slt i64 %var_17, 0 + %var_18_offset = select i1 %var_18_offset_chk, i64 1, i64 0 + %var_18 = getelementptr [4 x ptr], ptr @array0, i64 %var_18_offset, i64 %var_17 + %var_5 = load ptr, ptr %var_18 %var_6 = call i1 @__quantum__rt__read_result(ptr %var_5) br i1 %var_6, label %block_4, label %block_5 block_3: - %var_13 = load i64, ptr %var_2 - call void @__quantum__rt__int_record_output(i64 %var_13, ptr @0) + %var_14 = load i64, ptr %var_2 + store i64 %var_14, ptr %var_10 + %var_16 = load i64, ptr %var_10 + call void @__quantum__rt__int_record_output(i64 %var_16, ptr @0) ret i64 0 block_4: - %var_18 = load i64, ptr %var_2 - %var_8 = add i64 %var_18, 1 + %var_21 = load i64, ptr %var_2 + %var_8 = add i64 %var_21, 1 store i64 %var_8, ptr %var_2 br label %block_5 block_5: - %var_16 = load i64, ptr %var_3 - %var_9 = add i64 %var_16, 1 + %var_19 = load i64, ptr %var_3 + %var_9 = add i64 %var_19, 1 store i64 %var_9, ptr %var_3 br label %block_1 } @@ -742,6 +748,7 @@ fn mutable_result_variable_succeeds() { define i64 @ENTRYPOINT__main() #0 { block_0: %var_1 = alloca ptr + %var_5 = alloca ptr call void @__quantum__rt__initialize(ptr null) call void @H(ptr inttoptr (i64 0 to ptr)) call void @__quantum__qis__m__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) @@ -754,8 +761,10 @@ fn mutable_result_variable_succeeds() { store ptr inttoptr (i64 1 to ptr), ptr %var_1 br label %block_2 block_2: - %var_4 = load ptr, ptr %var_1 - call void @__quantum__rt__result_record_output(ptr %var_4, ptr @0) + %var_7 = load ptr, ptr %var_1 + store ptr %var_7, ptr %var_5 + %var_9 = load ptr, ptr %var_5 + call void @__quantum__rt__result_record_output(ptr %var_9, ptr @0) ret i64 0 } @@ -1108,6 +1117,7 @@ fn static_while_inside_emit_while_succeeds() { block_0: %var_0 = alloca i64 %var_3 = alloca i64 + %var_7 = alloca i64 call void @__quantum__rt__initialize(ptr null) store i64 0, ptr %var_0 br label %block_1 @@ -1119,19 +1129,21 @@ fn static_while_inside_emit_while_succeeds() { store i64 0, ptr %var_3 br label %block_4 block_3: - %var_8 = load i64, ptr %var_0 - call void @__quantum__rt__int_record_output(i64 %var_8, ptr @0) + %var_9 = load i64, ptr %var_0 + store i64 %var_9, ptr %var_7 + %var_11 = load i64, ptr %var_7 + call void @__quantum__rt__int_record_output(i64 %var_11, ptr @0) ret i64 0 block_4: - %var_10 = load i64, ptr %var_3 - %var_4 = icmp slt i64 %var_10, 3 + %var_13 = load i64, ptr %var_3 + %var_4 = icmp slt i64 %var_13, 3 br i1 %var_4, label %block_5, label %block_6 block_5: - %var_11 = load i64, ptr %var_0 - %var_5 = add i64 %var_11, 1 + %var_14 = load i64, ptr %var_0 + %var_5 = add i64 %var_14, 1 store i64 %var_5, ptr %var_0 - %var_13 = load i64, ptr %var_3 - %var_6 = add i64 %var_13, 1 + %var_16 = load i64, ptr %var_3 + %var_6 = add i64 %var_16, 1 store i64 %var_6, ptr %var_3 br label %block_4 block_6: @@ -1196,34 +1208,37 @@ fn nested_emit_while_loops_succeeds() { block_0: %var_1 = alloca i64 %var_4 = alloca i64 + %var_8 = alloca i64 call void @__quantum__rt__initialize(ptr null) store i64 0, ptr %var_1 br label %block_1 block_1: - %var_9 = load i64, ptr %var_1 - %var_2 = icmp slt i64 %var_9, 3 + %var_10 = load i64, ptr %var_1 + %var_2 = icmp slt i64 %var_10, 3 br i1 %var_2, label %block_2, label %block_3 block_2: call void @H(ptr inttoptr (i64 0 to ptr)) store i64 0, ptr %var_4 br label %block_4 block_3: - %var_10 = load i64, ptr %var_1 - call void @__quantum__rt__int_record_output(i64 %var_10, ptr @0) + %var_11 = load i64, ptr %var_1 + store i64 %var_11, ptr %var_8 + %var_13 = load i64, ptr %var_8 + call void @__quantum__rt__int_record_output(i64 %var_13, ptr @0) ret i64 0 block_4: - %var_12 = load i64, ptr %var_4 - %var_5 = icmp slt i64 %var_12, 2 + %var_15 = load i64, ptr %var_4 + %var_5 = icmp slt i64 %var_15, 2 br i1 %var_5, label %block_5, label %block_6 block_5: call void @H(ptr inttoptr (i64 1 to ptr)) - %var_15 = load i64, ptr %var_4 - %var_6 = add i64 %var_15, 1 + %var_18 = load i64, ptr %var_4 + %var_6 = add i64 %var_18, 1 store i64 %var_6, ptr %var_4 br label %block_4 block_6: - %var_13 = load i64, ptr %var_1 - %var_7 = add i64 %var_13, 1 + %var_16 = load i64, ptr %var_1 + %var_7 = add i64 %var_16, 1 store i64 %var_7, ptr %var_1 br label %block_1 } @@ -1289,37 +1304,40 @@ fn for_loop_over_qubits_with_dynamic_exit_succeeds() { %var_0 = alloca i1 %var_2 = alloca i1 %var_3 = alloca i64 + %var_11 = alloca i1 call void @__quantum__rt__initialize(ptr null) store i1 false, ptr %var_0 store i1 false, ptr %var_2 store i64 0, ptr %var_3 br label %block_1 block_1: - %var_14 = load i64, ptr %var_3 - %var_4 = icmp slt i64 %var_14, 3 + %var_15 = load i64, ptr %var_3 + %var_4 = icmp slt i64 %var_15, 3 br i1 %var_4, label %block_2, label %block_3 block_2: - %var_16 = load i64, ptr %var_3 - %var_17_offset_chk = icmp slt i64 %var_16, 0 - %var_17_offset = select i1 %var_17_offset_chk, i64 1, i64 0 - %var_17 = getelementptr [3 x ptr], ptr @array0, i64 %var_17_offset, i64 %var_16 - %var_5 = load ptr, ptr %var_17 + %var_19 = load i64, ptr %var_3 + %var_20_offset_chk = icmp slt i64 %var_19, 0 + %var_20_offset = select i1 %var_20_offset_chk, i64 1, i64 0 + %var_20 = getelementptr [3 x ptr], ptr @array0, i64 %var_20_offset, i64 %var_19 + %var_5 = load ptr, ptr %var_20 call void @H(ptr %var_5) call void @__quantum__qis__mresetz__body(ptr %var_5, ptr inttoptr (i64 0 to ptr)) %var_8 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) store i1 %var_8, ptr %var_0 - %var_19 = load i1, ptr %var_0 - br i1 %var_19, label %block_4, label %block_5 + %var_22 = load i1, ptr %var_0 + br i1 %var_22, label %block_4, label %block_5 block_3: - %var_15 = load i1, ptr %var_2 - call void @__quantum__rt__bool_record_output(i1 %var_15, ptr @0) + %var_16 = load i1, ptr %var_2 + store i1 %var_16, ptr %var_11 + %var_18 = load i1, ptr %var_11 + call void @__quantum__rt__bool_record_output(i1 %var_18, ptr @0) ret i64 0 block_4: store i1 true, ptr %var_2 br label %block_5 block_5: - %var_20 = load i64, ptr %var_3 - %var_10 = add i64 %var_20, 1 + %var_23 = load i64, ptr %var_3 + %var_10 = add i64 %var_23, 1 store i64 %var_10, ptr %var_3 br label %block_1 } @@ -2616,6 +2634,148 @@ fn preparepurestated_cyclic_library_calls_generate_correct_qir() { .assert_eq(&qir); } +#[test] +fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable_updated() { + let source = " + operation Main() : Int { + use q0 = Qubit(); + mutable n = 0; + if MResetZ(q0) == One { n = 2; } + let h = n; + n = 7; + h + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + @0 = internal constant [4 x i8] c"0_i\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + %var_0 = alloca i64 + %var_4 = alloca i64 + call void @__quantum__rt__initialize(ptr null) + store i64 0, ptr %var_0 + call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) + %var_1 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) + br i1 %var_1, label %block_1, label %block_2 + block_1: + store i64 2, ptr %var_0 + br label %block_2 + block_2: + %var_6 = load i64, ptr %var_0 + store i64 %var_6, ptr %var_4 + store i64 7, ptr %var_0 + %var_9 = load i64, ptr %var_4 + call void @__quantum__rt__int_record_output(i64 %var_9, ptr @0) + ret i64 0 + } + + declare void @__quantum__rt__initialize(ptr) + + declare void @__quantum__qis__mresetz__body(ptr, ptr) #1 + + declare i1 @__quantum__rt__read_result(ptr) #2 + + declare void @__quantum__rt__int_record_output(i64, ptr) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + attributes #2 = { nofree nosync nounwind willreturn memory(argmem: read) } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9} + + !0 = !{i32 1, !"qir_major_version", i32 2} + !1 = !{i32 7, !"qir_minor_version", i32 1} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + !5 = !{i32 5, !"float_computations", !{!"double"}} + !6 = !{i32 7, !"backwards_branching", i2 3} + !7 = !{i32 1, !"arrays", i1 true} + !8 = !{i32 1, !"ir_functions", i1 true} + !9 = !{i32 1, !"writable_results", i1 true} + "#]].assert_eq(&qir); +} + +#[test] +fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable_updated_in_dynamic_branch() + { + let source = " + operation Main() : Int { + use q0 = Qubit(); + mutable n = 0; + if MResetZ(q0) == One { n = 2; } + let h = n; + if MResetZ(q0) == One { n = 7; } + h + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + @0 = internal constant [4 x i8] c"0_i\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + %var_0 = alloca i64 + %var_4 = alloca i64 + %var_8 = alloca i64 + call void @__quantum__rt__initialize(ptr null) + store i64 0, ptr %var_0 + call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) + %var_1 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) + br i1 %var_1, label %block_1, label %block_2 + block_1: + store i64 2, ptr %var_0 + br label %block_2 + block_2: + %var_10 = load i64, ptr %var_0 + store i64 %var_10, ptr %var_4 + call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 1 to ptr)) + %var_5 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 1 to ptr)) + br i1 %var_5, label %block_3, label %block_4 + block_3: + store i64 7, ptr %var_0 + br label %block_4 + block_4: + %var_12 = load i64, ptr %var_4 + store i64 %var_12, ptr %var_8 + %var_14 = load i64, ptr %var_8 + call void @__quantum__rt__int_record_output(i64 %var_14, ptr @0) + ret i64 0 + } + + declare void @__quantum__rt__initialize(ptr) + + declare void @__quantum__qis__mresetz__body(ptr, ptr) #1 + + declare i1 @__quantum__rt__read_result(ptr) #2 + + declare void @__quantum__rt__int_record_output(i64, ptr) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="2" } + attributes #1 = { "irreversible" } + attributes #2 = { nofree nosync nounwind willreturn memory(argmem: read) } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9} + + !0 = !{i32 1, !"qir_major_version", i32 2} + !1 = !{i32 7, !"qir_minor_version", i32 1} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + !5 = !{i32 5, !"float_computations", !{!"double"}} + !6 = !{i32 7, !"backwards_branching", i2 3} + !7 = !{i32 1, !"arrays", i1 true} + !8 = !{i32 1, !"ir_functions", i1 true} + !9 = !{i32 1, !"writable_results", i1 true} + "#]].assert_eq(&qir); +} + // ---- Cross-package (foreign) IR-function emission ---- /// A reachable, eligible operation that lives in a separate library package diff --git a/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs index 090abf75014..28840b30237 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs @@ -686,3 +686,123 @@ fn preparepurestated_cyclic_library_calls_generate_correct_qir() { !4 = !{i32 5, !"int_computations", !{!"i64"}} "#]].assert_eq(&qir); } + +#[test] +fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable_updated() { + let source = " + operation Main() : Int { + use q0 = Qubit(); + mutable n = 0; + if MResetZ(q0) == One { n = 2; } + let h = n; + n = 7; + h + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + @0 = internal constant [4 x i8] c"0_i\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + %var_1 = call i1 @__quantum__rt__read_result(%Result* inttoptr (i64 0 to %Result*)) + br i1 %var_1, label %block_1, label %block_2 + block_1: + br label %block_2 + block_2: + %var_6 = phi i64 [0, %block_0], [2, %block_1] + call void @__quantum__rt__int_record_output(i64 %var_6, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0)) + ret i64 0 + } + + declare void @__quantum__rt__initialize(i8*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare i1 @__quantum__rt__read_result(%Result*) + + declare void @__quantum__rt__int_record_output(i64, 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, !4} + + !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} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + "#]].assert_eq(&qir); +} + +#[test] +fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable_updated_in_dynamic_branch() + { + let source = " + operation Main() : Int { + use q0 = Qubit(); + mutable n = 0; + if MResetZ(q0) == One { n = 2; } + let h = n; + if MResetZ(q0) == One { n = 7; } + h + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + @0 = internal constant [4 x i8] c"0_i\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + %var_1 = call i1 @__quantum__rt__read_result(%Result* inttoptr (i64 0 to %Result*)) + br i1 %var_1, label %block_1, label %block_2 + block_1: + br label %block_2 + block_2: + %var_9 = phi i64 [0, %block_0], [2, %block_1] + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + %var_5 = call i1 @__quantum__rt__read_result(%Result* inttoptr (i64 1 to %Result*)) + br i1 %var_5, label %block_3, label %block_4 + block_3: + br label %block_4 + block_4: + %var_10 = phi i64 [%var_9, %block_2], [7, %block_3] + call void @__quantum__rt__int_record_output(i64 %var_9, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0)) + ret i64 0 + } + + declare void @__quantum__rt__initialize(i8*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare i1 @__quantum__rt__read_result(%Result*) + + declare void @__quantum__rt__int_record_output(i64, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="2" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4} + + !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} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + "#]].assert_eq(&qir); +} diff --git a/source/compiler/qsc_openqasm_compiler/src/tests/statement/gate_call.rs b/source/compiler/qsc_openqasm_compiler/src/tests/statement/gate_call.rs index f8bb446c465..027328eff08 100644 --- a/source/compiler/qsc_openqasm_compiler/src/tests/statement/gate_call.rs +++ b/source/compiler/qsc_openqasm_compiler/src/tests/statement/gate_call.rs @@ -557,8 +557,10 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( %var_24 = alloca i1 %var_26 = alloca i1 %var_29 = alloca i64 + %var_31 = alloca i64 %var_37 = alloca i64 %var_39 = alloca i64 + %var_40 = alloca i64 %var_9 = icmp sgt i64 %var_8, 53 br i1 %var_9, label %block_4, label %block_5 block_4: @@ -572,49 +574,51 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( store i64 %var_19, ptr %var_20 %var_21 = ashr i64 %var_7, %var_11 store i64 %var_21, ptr %var_22 - %var_65 = load i64, ptr %var_20 - %var_66 = load i64, ptr %var_15 - %var_23 = icmp sgt i64 %var_65, %var_66 + %var_69 = load i64, ptr %var_20 + %var_70 = load i64, ptr %var_15 + %var_23 = icmp sgt i64 %var_69, %var_70 store i1 true, ptr %var_24 br i1 %var_23, label %block_9, label %block_6 block_5: store i64 %var_7, ptr %var_10 br label %block_13 block_6: - %var_68 = load i64, ptr %var_20 - %var_69 = load i64, ptr %var_15 - %var_25 = icmp eq i64 %var_68, %var_69 + %var_72 = load i64, ptr %var_20 + %var_73 = load i64, ptr %var_15 + %var_25 = icmp eq i64 %var_72, %var_73 store i1 false, ptr %var_26 br i1 %var_25, label %block_7, label %block_8 block_7: - %var_80 = load i64, ptr %var_22 - %var_27 = and i64 %var_80, 1 + %var_84 = load i64, ptr %var_22 + %var_27 = and i64 %var_84, 1 %var_28 = icmp eq i64 %var_27, 1 store i1 %var_28, ptr %var_26 br label %block_8 block_8: - %var_71 = load i1, ptr %var_26 - store i1 %var_71, ptr %var_24 + %var_75 = load i1, ptr %var_26 + store i1 %var_75, ptr %var_24 br label %block_9 block_9: - %var_73 = load i1, ptr %var_24 - br i1 %var_73, label %block_10, label %block_11 + %var_77 = load i1, ptr %var_24 + br i1 %var_77, label %block_10, label %block_11 block_10: - %var_78 = load i64, ptr %var_22 - %var_30 = add i64 %var_78, 1 + %var_82 = load i64, ptr %var_22 + %var_30 = add i64 %var_82, 1 store i64 %var_30, ptr %var_29 br label %block_12 block_11: - %var_74 = load i64, ptr %var_22 - store i64 %var_74, ptr %var_29 + %var_78 = load i64, ptr %var_22 + store i64 %var_78, ptr %var_29 br label %block_12 block_12: - %var_76 = load i64, ptr %var_29 - store i64 %var_76, ptr %var_10 + %var_80 = load i64, ptr %var_29 + store i64 %var_80, ptr %var_10 br label %block_13 block_13: %var_56 = load i64, ptr %var_10 - %var_36 = call i64 @MinI(i64 %var_56, i64 9007199254740991) + store i64 %var_56, ptr %var_31 + %var_58 = load i64, ptr %var_31 + %var_36 = call i64 @MinI(i64 %var_58, i64 9007199254740991) store i64 %var_36, ptr %var_37 %var_38 = icmp sgt i64 %var_8, 53 br i1 %var_38, label %block_14, label %block_15 @@ -625,11 +629,13 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( store i64 %var_8, ptr %var_39 br label %block_16 block_16: - %var_59 = load i64, ptr %var_39 - %var_41 = shl i64 1, %var_59 + %var_61 = load i64, ptr %var_39 + store i64 %var_61, ptr %var_40 + %var_63 = load i64, ptr %var_40 + %var_41 = shl i64 1, %var_63 %var_42 = sitofp i64 %var_41 to double - %var_60 = load i64, ptr %var_37 - %var_44 = sitofp i64 %var_60 to double + %var_64 = load i64, ptr %var_37 + %var_44 = sitofp i64 %var_64 to double %var_46 = fdiv double 6.283185307179586, %var_42 %var_48 = fmul double %var_44, %var_46 %var_53 = call double @MinD(double %var_48, double 6.283185307179585) @@ -648,8 +654,8 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( store i64 %var_33, ptr %var_35 br label %block_20 block_20: - %var_83 = load i64, ptr %var_35 - ret i64 %var_83 + %var_87 = load i64, ptr %var_35 + ret i64 %var_87 } define internal double @MinD(double %var_49, double %var_50) { @@ -664,8 +670,8 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( store double %var_50, ptr %var_52 br label %block_24 block_24: - %var_86 = load double, ptr %var_52 - ret double %var_86 + %var_90 = load double, ptr %var_52 + ret double %var_90 } define internal void @Rz(double %var_56, ptr %var_57) { diff --git a/source/compiler/qsc_rir/src/passes/ssa_transform.rs b/source/compiler/qsc_rir/src/passes/ssa_transform.rs index b81c047d82e..55c59d1c4cf 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_transform.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_transform.rs @@ -4,8 +4,10 @@ #[cfg(test)] mod tests; +use std::collections::hash_map::Entry::{Occupied, Vacant}; + use crate::{ - rir::{BlockId, Instruction, Operand, Program, Ty, Variable, VariableId}, + rir::{BlockId, Instruction, Operand, OperandMapping, Program, Ty, Variable, VariableId}, utils::{get_all_block_successors, get_variable_assignments, map_variable_use_in_block}, }; use qsc_data_structures::index_map::IndexMap; @@ -100,26 +102,31 @@ fn transform_body_to_ssa( // predecessor has a different value for the variable, a phi node is needed. let first_pred_map = block_var_map .get(*first_pred) - .expect("block should have variable map"); - 'var_loop: for (var_id, operand) in first_pred_map { + .expect("block should have variable map") + .clone(); + 'var_loop: for (var_id, mapping) in &first_pred_map { + let operand = Into::::into(mapping); let mut phi_nodes = FxHashMap::default(); if rest_preds.iter().any(|pred| { - block_var_map + let pred_map = block_var_map .get(*pred) - .expect("block should have variable map") + .expect("block should have variable map"); + let pred_operand = pred_map .get(var_id) - != Some(operand) + .map(Into::::into) + .map(|op| op.mapped(pred_map)); + pred_operand != Some(operand) }) { // Some predecessors have different values for this variable, so a phi node is needed. // Start with the first predecessor's value and block id, then add the values from the other predecessors. - let mut phi_args = vec![(operand.mapped(first_pred_map), *first_pred)]; + let mut phi_args = vec![(operand.mapped(&first_pred_map), *first_pred)]; for pred in rest_preds { let pred_var_map = block_var_map .get(*pred) .expect("block should have variable map"); let mut pred_operand = match pred_var_map.get(var_id) { - Some(operand) => *operand, + Some(mapping) => Into::::into(mapping), None => { // If the variable is not defined in this predecessor, it does not dominate this block. // Assume it is not used and skip creating a phi node for this variable. If the variable is used, @@ -134,21 +141,45 @@ fn transform_body_to_ssa( } else { // If all predecessors have the same value for this variable, the value can be propagated. // Update the block variable map with the common operand. - var_map_updates.insert(*var_id, *operand); + var_map_updates.insert(*var_id, *mapping); } - // For any phi nodes that need to be inserted, create a new variable and insert - // the phi node at the beginning of the block. The new variable will be used to replace - // the original variable in the block's variable map, which will take care of any orphaned uses. for (variable_id, args) in phi_nodes { - let new_var = Variable { - variable_id: *next_var_id, - ty: operand.get_type(), + let operand_to_map = if let Some((first_arg, rest_args)) = args.split_first() + && rest_args.iter().all(|(arg, _)| *arg == first_arg.0) + { + // All arguments are the same, so no phi node is needed. + // Instead, map the original variable to the common operand. + first_arg.0 + } else { + // For any phi nodes that need to be inserted, create a new variable and insert + // the phi node at the beginning of the block. The new variable will be used to replace + // the original variable in the block's variable map, which will take care of any orphaned uses. + let new_var = Variable { + variable_id: *next_var_id, + ty: operand.get_type(), + }; + let phi_node = Instruction::Phi(args, new_var); + block.0.insert(0, phi_node); + Operand::Variable(new_var) }; - let phi_node = Instruction::Phi(args, new_var); - block.0.insert(0, phi_node); - var_map_updates.insert(variable_id, Operand::Variable(new_var)); + + var_map_updates.insert(variable_id, OperandMapping::Deep(operand_to_map)); *next_var_id = next_var_id.successor(); + + let var_map = block_var_map + .get_mut(block_id) + .expect("block should have variable map"); + + // To make sure calculations of successor blocks get the updated variable mappings, identify any existing mappings + // for the variable being updated and replace them with the new operand mapping. + for mapping in var_map.values_mut() { + if let Operand::Variable(var) = mapping.into() + && var.variable_id == variable_id + { + *mapping = OperandMapping::Deep(operand_to_map); + } + } } } } @@ -156,11 +187,19 @@ fn transform_body_to_ssa( // Now that the block has finished processing, apply any updates to the block and // merge those updates into the stored variable map to propagate to successors. map_variable_use_in_block(block, &mut var_map_updates, &FxHashSet::default()); - for (var_id, operand) in var_map_updates { - let var_map = block_var_map - .get_mut(block_id) - .expect("block should have variable map"); - var_map.entry(var_id).or_insert(operand); + let var_map = block_var_map + .get_mut(block_id) + .expect("block should have variable map"); + for (var_id, mapping) in var_map_updates { + match var_map.entry(var_id) { + Vacant(entry) => { + entry.insert(mapping); + } + Occupied(mut entry) if entry.get().is_shallow() => { + entry.insert(mapping); + } + Occupied(_) => (), + } } } } @@ -187,10 +226,10 @@ fn map_store_to_dominated_ssa( entry: BlockId, input_vars: &[(VariableId, Ty)], preds: &IndexMap>, -) -> IndexMap> { +) -> IndexMap> { let mut block_var_map = IndexMap::default(); for &block_id in body_blocks { - let mut var_map: FxHashMap = match preds.get(block_id) { + let mut var_map: FxHashMap = match preds.get(block_id) { Some(block_preds) if block_preds.len() == 1 => { // Any block with a single predecessor inherits those mapped variables. block_var_map @@ -207,10 +246,10 @@ fn map_store_to_dominated_ssa( var_map .insert( var_id, - Operand::Variable(Variable { + OperandMapping::Deep(Operand::Variable(Variable { variable_id: var_id, ty, - }), + })), ) .is_none(), "input vars should only be initialized once by parameters" diff --git a/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs b/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs index 98cac604ac6..ebe54d37b4e 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs @@ -2759,6 +2759,793 @@ fn ssa_transform_allows_point_in_time_copy_of_dynamic_variable() { "#]].assert_eq(&program.to_string()); } +#[test] +fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks() { + let mut program = new_program(); + program.callables.insert( + CallableId(1), + Callable { + name: "dynamic_bool".to_string(), + input_type: Vec::new(), + output_type: Some(Ty::Prim(Prim::Boolean)), + body: None, + input_vars: Vec::new(), + call_type: CallableType::Regular, + }, + ); + + program.blocks.insert( + BlockId(0), + Block(vec![ + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(0)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(1), + BlockId(2), + None, + ), + ]), + ); + program.blocks.insert( + BlockId(1), + Block(vec![ + Instruction::Store( + Operand::Literal(Literal::Integer(1)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Jump(BlockId(2)), + ]), + ); + program.blocks.insert( + BlockId(2), + Block(vec![ + Instruction::Store( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(2)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Add( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Operand::Variable(Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Return(None), + ]), + ); + + // Before + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(2, Integer) = Store Variable(1, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Integer) = Add Variable(1, Integer), Variable(2, Integer) + Return + config: Config: + capabilities: Base + num_qubits: 0 + num_results: 0 + tags: + "#]] + .assert_eq(&program.to_string()); + + // After + transform_program(&mut program); + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Jump(2) + Block 2: Block: + Variable(4, Integer) = Phi ( [Integer(0), 0], [Integer(1), 1], ) + Variable(3, Integer) = Add Integer(2), Variable(4, Integer) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations) + num_qubits: 0 + num_results: 0 + tags: + "#]].assert_eq(&program.to_string()); +} + +#[test] +fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks_using_load_and_store() +{ + let mut program = new_program(); + program.callables.insert( + CallableId(1), + Callable { + name: "dynamic_bool".to_string(), + input_type: Vec::new(), + output_type: Some(Ty::Prim(Prim::Boolean)), + body: None, + input_vars: Vec::new(), + call_type: CallableType::Regular, + }, + ); + + program.blocks.insert( + BlockId(0), + Block(vec![ + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(0)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(1), + BlockId(2), + None, + ), + ]), + ); + program.blocks.insert( + BlockId(1), + Block(vec![ + Instruction::Store( + Operand::Literal(Literal::Integer(1)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Jump(BlockId(2)), + ]), + ); + program.blocks.insert( + BlockId(2), + Block(vec![ + Instruction::Store( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(2)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Add( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Operand::Variable(Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Return(None), + ]), + ); + + // Before + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(2, Integer) = Store Variable(1, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Integer) = Add Variable(1, Integer), Variable(2, Integer) + Return + config: Config: + capabilities: Base + num_qubits: 0 + num_results: 0 + tags: + "#]] + .assert_eq(&program.to_string()); + + // After + program.config.capabilities = Profile::Adaptive.into(); + check_and_transform(&mut program); + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(1, Integer) = Alloca + Variable(2, Integer) = Alloca + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(5, Integer) = Load Variable(1, Integer) + Variable(2, Integer) = Store Variable(5, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(8, Integer) = Load Variable(1, Integer) + Variable(9, Integer) = Load Variable(2, Integer) + Variable(3, Integer) = Add Variable(8, Integer), Variable(9, Integer) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | StaticSizedArrays | CallSupport) + num_qubits: 0 + num_results: 0 + tags: + "#]].assert_eq(&program.to_string()); +} + +#[test] +fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks_accessed_in_successor_block() + { + let mut program = new_program(); + program.callables.insert( + CallableId(1), + Callable { + name: "dynamic_bool".to_string(), + input_type: Vec::new(), + output_type: Some(Ty::Prim(Prim::Boolean)), + body: None, + input_vars: Vec::new(), + call_type: CallableType::Regular, + }, + ); + + program.blocks.insert( + BlockId(0), + Block(vec![ + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(0)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(1), + BlockId(2), + None, + ), + ]), + ); + program.blocks.insert( + BlockId(1), + Block(vec![ + Instruction::Store( + Operand::Literal(Literal::Integer(1)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Jump(BlockId(2)), + ]), + ); + program.blocks.insert( + BlockId(2), + Block(vec![ + Instruction::Store( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(2)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(3), + BlockId(4), + None, + ), + ]), + ); + program + .blocks + .insert(BlockId(3), Block(vec![Instruction::Jump(BlockId(5))])); + program + .blocks + .insert(BlockId(4), Block(vec![Instruction::Jump(BlockId(5))])); + program.blocks.insert( + BlockId(5), + Block(vec![ + Instruction::Add( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Operand::Variable(Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(4), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Return(None), + ]), + ); + + // Before + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(2, Integer) = Store Variable(1, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Boolean) = Call id(1), args( ) + Branch Variable(3, Boolean), 3, 4 + Block 3: Block: + Jump(5) + Block 4: Block: + Jump(5) + Block 5: Block: + Variable(4, Integer) = Add Variable(1, Integer), Variable(2, Integer) + Return + config: Config: + capabilities: Base + num_qubits: 0 + num_results: 0 + tags: + "#]] + .assert_eq(&program.to_string()); + + // After + transform_program(&mut program); + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Jump(2) + Block 2: Block: + Variable(5, Integer) = Phi ( [Integer(0), 0], [Integer(1), 1], ) + Variable(3, Boolean) = Call id(1), args( ) + Branch Variable(3, Boolean), 3, 4 + Block 3: Block: + Jump(5) + Block 4: Block: + Jump(5) + Block 5: Block: + Variable(4, Integer) = Add Integer(2), Variable(5, Integer) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations) + num_qubits: 0 + num_results: 0 + tags: + "#]].assert_eq(&program.to_string()); +} + +#[test] +fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks_accessed_in_successor_block_using_load_and_store() + { + let mut program = new_program(); + program.callables.insert( + CallableId(1), + Callable { + name: "dynamic_bool".to_string(), + input_type: Vec::new(), + output_type: Some(Ty::Prim(Prim::Boolean)), + body: None, + input_vars: Vec::new(), + call_type: CallableType::Regular, + }, + ); + + program.blocks.insert( + BlockId(0), + Block(vec![ + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(0)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(1), + BlockId(2), + None, + ), + ]), + ); + program.blocks.insert( + BlockId(1), + Block(vec![ + Instruction::Store( + Operand::Literal(Literal::Integer(1)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Jump(BlockId(2)), + ]), + ); + program.blocks.insert( + BlockId(2), + Block(vec![ + Instruction::Store( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(2)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(3), + BlockId(4), + None, + ), + ]), + ); + program + .blocks + .insert(BlockId(3), Block(vec![Instruction::Jump(BlockId(5))])); + program + .blocks + .insert(BlockId(4), Block(vec![Instruction::Jump(BlockId(5))])); + program.blocks.insert( + BlockId(5), + Block(vec![ + Instruction::Add( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Operand::Variable(Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(4), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Return(None), + ]), + ); + + // Before + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(2, Integer) = Store Variable(1, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Boolean) = Call id(1), args( ) + Branch Variable(3, Boolean), 3, 4 + Block 3: Block: + Jump(5) + Block 4: Block: + Jump(5) + Block 5: Block: + Variable(4, Integer) = Add Variable(1, Integer), Variable(2, Integer) + Return + config: Config: + capabilities: Base + num_qubits: 0 + num_results: 0 + tags: + "#]] + .assert_eq(&program.to_string()); + + // After + program.config.capabilities = Profile::Adaptive.into(); + check_and_transform(&mut program); + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(1, Integer) = Alloca + Variable(2, Integer) = Alloca + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(6, Integer) = Load Variable(1, Integer) + Variable(2, Integer) = Store Variable(6, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Boolean) = Call id(1), args( ) + Branch Variable(3, Boolean), 3, 4 + Block 3: Block: + Jump(5) + Block 4: Block: + Jump(5) + Block 5: Block: + Variable(9, Integer) = Load Variable(1, Integer) + Variable(10, Integer) = Load Variable(2, Integer) + Variable(4, Integer) = Add Variable(9, Integer), Variable(10, Integer) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | StaticSizedArrays | CallSupport) + num_qubits: 0 + num_results: 0 + tags: + "#]].assert_eq(&program.to_string()); +} + #[test] fn ssa_transform_propagates_phi_var_to_successor_blocks_across_sequential_branches() { let mut program = new_program(); diff --git a/source/compiler/qsc_rir/src/rir.rs b/source/compiler/qsc_rir/src/rir.rs index 99e2d73adc0..b90f60e391f 100644 --- a/source/compiler/qsc_rir/src/rir.rs +++ b/source/compiler/qsc_rir/src/rir.rs @@ -831,6 +831,46 @@ impl PartialEq for ArrayLiteral { } } +/// Tracks a mapping to a specific operand, indicating whether the mapping is shallow or deep. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum OperandMapping { + /// A shallow mapping to an operand, where the mapped value should be replaced by this operand + /// directly without following any further mappings. + Shallow(Operand), + /// A deep mapping to an operand, where the mapped value should be followed to its root before use. + Deep(Operand), +} + +impl OperandMapping { + pub(crate) fn is_shallow(&self) -> bool { + matches!(self, OperandMapping::Shallow(_)) + } +} + +impl From for Operand { + fn from(val: OperandMapping) -> Self { + match val { + OperandMapping::Shallow(op) | OperandMapping::Deep(op) => op, + } + } +} + +impl From<&OperandMapping> for Operand { + fn from(val: &OperandMapping) -> Self { + match val { + OperandMapping::Shallow(op) | OperandMapping::Deep(op) => *op, + } + } +} + +impl From<&mut OperandMapping> for Operand { + fn from(val: &mut OperandMapping) -> Self { + match val { + OperandMapping::Shallow(op) | OperandMapping::Deep(op) => *op, + } + } +} + fn write_store_array_instruction( f: &mut Formatter, value: &[Operand], diff --git a/source/compiler/qsc_rir/src/utils.rs b/source/compiler/qsc_rir/src/utils.rs index 930a35e2d32..2899b270acb 100644 --- a/source/compiler/qsc_rir/src/utils.rs +++ b/source/compiler/qsc_rir/src/utils.rs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::rir::{Block, BlockId, Instruction, Operand, Program, Variable, VariableId}; +use crate::rir::{ + Block, BlockId, Instruction, Operand, OperandMapping, Program, Variable, VariableId, +}; use qsc_data_structures::index_map::IndexMap; use rustc_hash::{FxHashMap, FxHashSet}; @@ -140,7 +142,7 @@ pub fn get_variable_assignments(program: &Program) -> IndexMap, + var_map: &mut FxHashMap, var_stor_to_keep: &FxHashSet, ) { let instrs = block.0.drain(..).collect::>(); @@ -152,21 +154,19 @@ pub(crate) fn map_variable_use_in_block( if var_stor_to_keep.contains(&var.variable_id) { // Only keep stores to variables that are in the set to keep. *operand = operand.mapped(var_map); + } else if let Operand::Variable(mapped_var) = operand.mapped(var_map) + && var_stor_to_keep.contains(&mapped_var.variable_id) + { + *operand = Operand::Variable(mapped_var); } else { - // Note this uses the mapped operand to make sure this variable points to whatever root literal or variable - // this operand corresponds to at this point in the block. This makes the new variable respect a point-in-time - // copy of the operand. - var_map.insert(var.variable_id, operand.mapped(var_map)); + update_variable_mapping(var_map, operand, var); continue; } } Instruction::StoreArray(operand, var) => { if var_stor_to_keep.contains(&var.variable_id) { // Only keep stores to variables that are in the set to keep. - *operand = operand - .iter() - .map(|op| op.mapped(var_map)) - .collect::>(); + *operand = operand.iter().map(|op| op.mapped(var_map)).collect(); } else { // Otherwise drop the store array by continuing the loop. continue; @@ -202,18 +202,7 @@ pub(crate) fn map_variable_use_in_block( // Replace any arguments with the new values of stored variables. Instruction::Call(_, args, _, _) => { - *args = args - .iter() - .map(|arg| match arg { - Operand::Variable(var) => { - // If the variable is not in the map, it is not something whose value has been updated via store in this block, - // so just fallback to use the `arg` value directly. - // `map_to_operand` does this automatically by returning `self`` when the variable is not in the map. - var.map_to_operand(var_map) - } - Operand::Literal(_) => *arg, - }) - .collect(); + *args = args.iter().map(|arg| arg.mapped(var_map)).collect(); } // Replace the branch condition with the new value of the variable. @@ -262,51 +251,104 @@ pub(crate) fn map_variable_use_in_block( // like the unconditional terminators. Instruction::Phi(..) | Instruction::Jump(..) | Instruction::Return(None) => {} - Instruction::Alloca(..) => { - panic!("alloca not supported in ssa transformation") - } - Instruction::Load(..) => { - panic!("load not supported in ssa transformation") + Instruction::Alloca(..) | Instruction::Load(..) => { + panic!("alloca/load not supported in ssa transformation") } } block.0.push(instr); } } +fn update_variable_mapping( + var_map: &mut FxHashMap, + operand: &mut Operand, + var: &mut Variable, +) { + // Note this uses the mapped operand to make sure this variable points to whatever root literal or variable + // this operand corresponds to at this point in the block. This makes the new variable respect a point-in-time + // copy of the operand. However, it will create a mapping that matches the last mapping of the operand, ensuring + // that a shallow mapping is not incorrectly treated as a deep mapping. + var_map.insert(var.variable_id, operand.last_mapping(var_map)); + + // For all existing deep mappings to this variable, downgrade them to shallow mappings. + // This ensures those previous mappings represent the value at the time they were created, rather than the new value being stored. + for mapping in var_map.values_mut() { + if let OperandMapping::Deep(Operand::Variable(existing)) = mapping + && existing == var + { + *mapping = OperandMapping::Shallow(Operand::Variable(*existing)); + } + } +} + impl Operand { #[must_use] - pub fn mapped(&self, var_map: &FxHashMap) -> Operand { + pub(crate) fn mapped(&self, var_map: &FxHashMap) -> Operand { match self { Operand::Literal(_) => *self, Operand::Variable(var) => var.map_to_operand(var_map), } } + + #[must_use] + pub(crate) fn last_mapping( + &self, + var_map: &FxHashMap, + ) -> OperandMapping { + match self { + Operand::Literal(_) => OperandMapping::Deep(*self), + Operand::Variable(var) => { + let mut var = *var; + while let Some(mapping) = var_map.get(&var.variable_id) { + if let Operand::Variable(new_var) = mapping.into() { + if new_var.variable_id == var.variable_id { + break; + } + var = new_var; + if mapping.is_shallow() { + return *mapping; + } + } else { + return *mapping; + } + } + OperandMapping::Deep(Operand::Variable(var)) + } + } + } } impl Variable { #[must_use] - pub fn map_to_operand(self, var_map: &FxHashMap) -> Operand { + pub(crate) fn map_to_operand(self, var_map: &FxHashMap) -> Operand { let mut var = self; - while let Some(operand) = var_map.get(&var.variable_id) { - if let Operand::Variable(new_var) = operand { + while let Some(mapping) = var_map.get(&var.variable_id) { + if let Operand::Variable(new_var) = mapping.into() { if new_var.variable_id == var.variable_id { // The variable maps to itself, as happens when a live-in parameter is seeded as // its own definition. It is already at its root, so stop following the chain. break; } - var = *new_var; + var = new_var; + if mapping.is_shallow() { + // Stop following the chain for shallow mappings and use the current mapping as is. + break; + } } else { - return *operand; + return mapping.into(); } } Operand::Variable(var) } #[must_use] - pub fn map_to_variable(self, var_map: &FxHashMap) -> Variable { + pub(crate) fn map_to_variable( + self, + var_map: &FxHashMap, + ) -> Variable { let mut var = self; - while let Some(operand) = var_map.get(&var.variable_id) { - let Operand::Variable(new_var) = operand else { + while let Some(mapping) = var_map.get(&var.variable_id) { + let Operand::Variable(new_var) = mapping.into() else { panic!("literal not supported in this context"); }; if new_var.variable_id == var.variable_id { @@ -314,7 +356,11 @@ impl Variable { // own definition. It is already at its root, so stop following the chain. break; } - var = *new_var; + var = new_var; + if mapping.is_shallow() { + // Stop following the chain for shallow mappings and use the current mapping as is. + break; + } } var } diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/ArithmeticOps.ll b/source/qdk_package/tests-integration/resources/adaptive/output/ArithmeticOps.ll index b6a296303e5..ad90252de09 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/ArithmeticOps.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/ArithmeticOps.ll @@ -15,6 +15,10 @@ block_0: %var_6 = alloca i64 %var_13 = alloca i64 %var_24 = alloca i64 + %var_30 = alloca i64 + %var_31 = alloca i64 + %var_32 = alloca i64 + %var_33 = alloca i64 call void @__quantum__rt__initialize(ptr null) store i64 0, ptr %var_1 store i64 0, ptr %var_2 @@ -23,17 +27,17 @@ block_0: store i64 0, ptr %var_6 br label %block_1 block_1: - %var_35 = load i64, ptr %var_6 - %var_7 = icmp slt i64 %var_35, 5 + %var_39 = load i64, ptr %var_6 + %var_7 = icmp slt i64 %var_39, 5 br i1 %var_7, label %block_2, label %block_3 block_2: - %var_59 = load i64, ptr %var_6 - %var_60_offset_chk = icmp slt i64 %var_59, 0 - %var_60_offset = select i1 %var_60_offset_chk, i64 1, i64 0 - %var_60 = getelementptr [5 x ptr], ptr @array0, i64 %var_60_offset, i64 %var_59 - %var_8 = load ptr, ptr %var_60 + %var_71 = load i64, ptr %var_6 + %var_72_offset_chk = icmp slt i64 %var_71, 0 + %var_72_offset = select i1 %var_72_offset_chk, i64 1, i64 0 + %var_72 = getelementptr [5 x ptr], ptr @array0, i64 %var_72_offset, i64 %var_71 + %var_8 = load ptr, ptr %var_72 call void @X(ptr %var_8) - %var_11 = add i64 %var_59, 1 + %var_11 = add i64 %var_71, 1 store i64 %var_11, ptr %var_6 br label %block_1 block_3: @@ -45,63 +49,71 @@ block_3: store i64 0, ptr %var_13 br label %block_4 block_4: - %var_37 = load i64, ptr %var_13 - %var_14 = icmp slt i64 %var_37, 5 + %var_41 = load i64, ptr %var_13 + %var_14 = icmp slt i64 %var_41, 5 br i1 %var_14, label %block_5, label %block_6 block_5: - %var_47 = load i64, ptr %var_13 - %var_48_offset_chk = icmp slt i64 %var_47, 0 - %var_48_offset = select i1 %var_48_offset_chk, i64 1, i64 0 - %var_48 = getelementptr [5 x ptr], ptr @array1, i64 %var_48_offset, i64 %var_47 - %var_15 = load ptr, ptr %var_48 + %var_59 = load i64, ptr %var_13 + %var_60_offset_chk = icmp slt i64 %var_59, 0 + %var_60_offset = select i1 %var_60_offset_chk, i64 1, i64 0 + %var_60 = getelementptr [5 x ptr], ptr @array1, i64 %var_60_offset, i64 %var_59 + %var_15 = load ptr, ptr %var_60 %var_17 = call i1 @__quantum__rt__read_result(ptr %var_15) br i1 %var_17, label %block_7, label %block_9 block_6: store i64 0, ptr %var_24 br label %block_8 block_7: - %var_51 = load i64, ptr %var_1 - %var_19 = add i64 %var_51, 1 + %var_63 = load i64, ptr %var_1 + %var_19 = add i64 %var_63, 1 store i64 %var_19, ptr %var_1 - %var_53 = load i64, ptr %var_2 - %var_20 = add i64 %var_53, 5 + %var_65 = load i64, ptr %var_2 + %var_20 = add i64 %var_65, 5 store i64 %var_20, ptr %var_2 - %var_55 = load i64, ptr %var_3 - %var_21 = sub i64 %var_55, 2 + %var_67 = load i64, ptr %var_3 + %var_21 = sub i64 %var_67, 2 store i64 %var_21, ptr %var_3 - %var_57 = load i64, ptr %var_4 - %var_22 = mul i64 %var_57, 3 + %var_69 = load i64, ptr %var_4 + %var_22 = mul i64 %var_69, 3 store i64 %var_22, ptr %var_4 br label %block_9 block_8: - %var_39 = load i64, ptr %var_24 - %var_25 = icmp slt i64 %var_39, 5 + %var_43 = load i64, ptr %var_24 + %var_25 = icmp slt i64 %var_43, 5 br i1 %var_25, label %block_10, label %block_11 block_9: - %var_49 = load i64, ptr %var_13 - %var_23 = add i64 %var_49, 1 + %var_61 = load i64, ptr %var_13 + %var_23 = add i64 %var_61, 1 store i64 %var_23, ptr %var_13 br label %block_4 block_10: - %var_44 = load i64, ptr %var_24 - %var_45_offset_chk = icmp slt i64 %var_44, 0 - %var_45_offset = select i1 %var_45_offset_chk, i64 1, i64 0 - %var_45 = getelementptr [5 x ptr], ptr @array0, i64 %var_45_offset, i64 %var_44 - %var_26 = load ptr, ptr %var_45 + %var_56 = load i64, ptr %var_24 + %var_57_offset_chk = icmp slt i64 %var_56, 0 + %var_57_offset = select i1 %var_57_offset_chk, i64 1, i64 0 + %var_57 = getelementptr [5 x ptr], ptr @array0, i64 %var_57_offset, i64 %var_56 + %var_26 = load ptr, ptr %var_57 call void @Reset(ptr %var_26) - %var_29 = add i64 %var_44, 1 + %var_29 = add i64 %var_56, 1 store i64 %var_29, ptr %var_24 br label %block_8 block_11: + %var_44 = load i64, ptr %var_1 + store i64 %var_44, ptr %var_30 + %var_46 = load i64, ptr %var_2 + store i64 %var_46, ptr %var_31 + %var_48 = load i64, ptr %var_3 + store i64 %var_48, ptr %var_32 + %var_50 = load i64, ptr %var_4 + store i64 %var_50, ptr %var_33 call void @__quantum__rt__tuple_record_output(i64 4, ptr @0) - %var_40 = load i64, ptr %var_1 - call void @__quantum__rt__int_record_output(i64 %var_40, ptr @1) - %var_41 = load i64, ptr %var_2 - call void @__quantum__rt__int_record_output(i64 %var_41, ptr @2) - %var_42 = load i64, ptr %var_3 - call void @__quantum__rt__int_record_output(i64 %var_42, ptr @3) - %var_43 = load i64, ptr %var_4 - call void @__quantum__rt__int_record_output(i64 %var_43, ptr @4) + %var_52 = load i64, ptr %var_30 + call void @__quantum__rt__int_record_output(i64 %var_52, ptr @1) + %var_53 = load i64, ptr %var_31 + call void @__quantum__rt__int_record_output(i64 %var_53, ptr @2) + %var_54 = load i64, ptr %var_32 + call void @__quantum__rt__int_record_output(i64 %var_54, ptr @3) + %var_55 = load i64, ptr %var_33 + call void @__quantum__rt__int_record_output(i64 %var_55, ptr @4) ret i64 0 } diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/CallReturnMath.ll b/source/qdk_package/tests-integration/resources/adaptive/output/CallReturnMath.ll index 93be3cc176b..4cfd403c8a7 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/CallReturnMath.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/CallReturnMath.ll @@ -14,6 +14,7 @@ define internal i64 @A(ptr %var_2, ptr %var_3) { block_1: %var_10 = alloca i64 %var_13 = alloca i64 + %var_14 = alloca i64 %var_9 = call i64 @B(ptr %var_3) store i64 %var_9, ptr %var_10 call void @X(ptr %var_2) @@ -28,8 +29,10 @@ block_3: br label %block_4 block_4: %var_20 = load i64, ptr %var_13 - %var_21 = load i64, ptr %var_10 - %var_15 = mul i64 %var_20, %var_21 + store i64 %var_20, ptr %var_14 + %var_22 = load i64, ptr %var_14 + %var_23 = load i64, ptr %var_10 + %var_15 = mul i64 %var_22, %var_23 %var_16 = add i64 %var_15, 1 ret i64 %var_16 } @@ -48,8 +51,8 @@ block_7: store i64 3, ptr %var_8 br label %block_8 block_8: - %var_24 = load i64, ptr %var_8 - ret i64 %var_24 + %var_26 = load i64, ptr %var_8 + ret i64 %var_26 } define internal void @X(ptr %var_5) { diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/Doubles.ll b/source/qdk_package/tests-integration/resources/adaptive/output/Doubles.ll index d1d37040f35..029af883852 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/Doubles.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/Doubles.ll @@ -14,19 +14,20 @@ block_0: %var_2 = alloca double %var_3 = alloca i64 %var_5 = alloca i1 + %var_25 = alloca double call void @__quantum__rt__initialize(ptr null) store i1 false, ptr %var_0 store double 0.0, ptr %var_2 store i64 1, ptr %var_3 br label %block_1 block_1: - %var_28 = load i64, ptr %var_3 - %var_4 = icmp sle i64 %var_28, 10 + %var_29 = load i64, ptr %var_3 + %var_4 = icmp sle i64 %var_29, 10 store i1 true, ptr %var_5 br i1 %var_4, label %block_2, label %block_3 block_2: - %var_31 = load i1, ptr %var_5 - br i1 %var_31, label %block_4, label %block_5 + %var_32 = load i1, ptr %var_5 + br i1 %var_32, label %block_4, label %block_5 block_3: store i1 false, ptr %var_5 br label %block_2 @@ -35,20 +36,22 @@ block_4: call void @__quantum__qis__m__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) %var_7 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) store i1 %var_7, ptr %var_0 - %var_34 = load i1, ptr %var_0 - br i1 %var_34, label %block_6, label %block_7 + %var_37 = load i1, ptr %var_0 + br i1 %var_37, label %block_6, label %block_7 block_5: call void @Reset(ptr inttoptr (i64 0 to ptr)) - %var_32 = load double, ptr %var_2 - %var_16 = fptosi double %var_32 to i64 + %var_33 = load double, ptr %var_2 + %var_16 = fptosi double %var_33 to i64 %var_18 = sitofp i64 %var_16 to double - %var_20 = fcmp ogt double %var_32, 5.0 - %var_21 = fcmp olt double %var_32, 5.0 - %var_22 = fcmp oge double %var_32, 10.0 - %var_23 = fcmp oeq double %var_32, 10.0 - %var_24 = fcmp one double %var_32, 10.0 + %var_20 = fcmp ogt double %var_33, 5.0 + %var_21 = fcmp olt double %var_33, 5.0 + %var_22 = fcmp oge double %var_33, 10.0 + %var_23 = fcmp oeq double %var_33, 10.0 + %var_24 = fcmp one double %var_33, 10.0 + store double %var_33, ptr %var_25 call void @__quantum__rt__tuple_record_output(i64 8, ptr @0) - call void @__quantum__rt__double_record_output(double %var_32, ptr @1) + %var_35 = load double, ptr %var_25 + call void @__quantum__rt__double_record_output(double %var_35, ptr @1) call void @__quantum__rt__bool_record_output(i1 %var_20, ptr @2) call void @__quantum__rt__bool_record_output(i1 %var_21, ptr @3) call void @__quantum__rt__bool_record_output(i1 %var_22, ptr @4) @@ -59,25 +62,25 @@ block_5: ret i64 0 block_6: call void @X(ptr inttoptr (i64 0 to ptr)) - %var_37 = load double, ptr %var_2 - %var_9 = fadd double %var_37, 1.0 + %var_40 = load double, ptr %var_2 + %var_9 = fadd double %var_40, 1.0 store double %var_9, ptr %var_2 - %var_39 = load double, ptr %var_2 - %var_10 = fmul double %var_39, 1.0 + %var_42 = load double, ptr %var_2 + %var_10 = fmul double %var_42, 1.0 store double %var_10, ptr %var_2 - %var_41 = load double, ptr %var_2 - %var_11 = fsub double %var_41, 1.0 + %var_44 = load double, ptr %var_2 + %var_11 = fsub double %var_44, 1.0 store double %var_11, ptr %var_2 - %var_43 = load double, ptr %var_2 - %var_12 = fdiv double %var_43, 1.0 + %var_46 = load double, ptr %var_2 + %var_12 = fdiv double %var_46, 1.0 store double %var_12, ptr %var_2 - %var_45 = load double, ptr %var_2 - %var_13 = fadd double %var_45, 1.0 + %var_48 = load double, ptr %var_2 + %var_13 = fadd double %var_48, 1.0 store double %var_13, ptr %var_2 br label %block_7 block_7: - %var_35 = load i64, ptr %var_3 - %var_14 = add i64 %var_35, 1 + %var_38 = load i64, ptr %var_3 + %var_14 = add i64 %var_38, 1 store i64 %var_14, ptr %var_3 br label %block_1 } diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/MeasurementComparison.ll b/source/qdk_package/tests-integration/resources/adaptive/output/MeasurementComparison.ll index 74cdbcaf05a..bcc0776efb2 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/MeasurementComparison.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/MeasurementComparison.ll @@ -8,6 +8,10 @@ define i64 @ENTRYPOINT__main() #0 { block_0: %var_6 = alloca i1 %var_14 = alloca i1 + %var_15 = alloca i1 + %var_16 = alloca i1 + %var_17 = alloca i1 + %var_18 = alloca i1 call void @__quantum__rt__initialize(ptr null) call void @X(ptr inttoptr (i64 0 to ptr)) call void @CNOT(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 1 to ptr)) @@ -32,13 +36,21 @@ block_2: store i1 true, ptr %var_14 br label %block_3 block_3: + %var_21 = load i1, ptr %var_6 + store i1 %var_21, ptr %var_15 + store i1 %var_8, ptr %var_16 + store i1 %var_11, ptr %var_17 + %var_25 = load i1, ptr %var_14 + store i1 %var_25, ptr %var_18 call void @__quantum__rt__tuple_record_output(i64 4, ptr @0) - %var_17 = load i1, ptr %var_6 - call void @__quantum__rt__bool_record_output(i1 %var_17, ptr @1) - call void @__quantum__rt__bool_record_output(i1 %var_8, ptr @2) - call void @__quantum__rt__bool_record_output(i1 %var_11, ptr @3) - %var_18 = load i1, ptr %var_14 - call void @__quantum__rt__bool_record_output(i1 %var_18, ptr @4) + %var_27 = load i1, ptr %var_15 + call void @__quantum__rt__bool_record_output(i1 %var_27, ptr @1) + %var_28 = load i1, ptr %var_16 + call void @__quantum__rt__bool_record_output(i1 %var_28, ptr @2) + %var_29 = load i1, ptr %var_17 + call void @__quantum__rt__bool_record_output(i1 %var_29, ptr @3) + %var_30 = load i1, ptr %var_18 + call void @__quantum__rt__bool_record_output(i1 %var_30, ptr @4) ret i64 0 } diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/ThreeQubitRepetitionCode.ll b/source/qdk_package/tests-integration/resources/adaptive/output/ThreeQubitRepetitionCode.ll index 7e75da3b036..72b9c7a1212 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/ThreeQubitRepetitionCode.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/ThreeQubitRepetitionCode.ll @@ -13,8 +13,11 @@ block_0: %var_12 = alloca i1 %var_13 = alloca i64 %var_23 = alloca i1 + %var_31 = alloca i1 %var_38 = alloca i1 %var_39 = alloca i64 + %var_45 = alloca i1 + %var_46 = alloca i64 call void @__quantum__rt__initialize(ptr null) call void @H(ptr inttoptr (i64 0 to ptr)) call void @Z(ptr inttoptr (i64 0 to ptr)) @@ -24,13 +27,13 @@ block_0: store i64 1, ptr %var_7 br label %block_1 block_1: - %var_47 = load i64, ptr %var_7 - %var_8 = icmp sle i64 %var_47, 5 + %var_49 = load i64, ptr %var_7 + %var_8 = icmp sle i64 %var_49, 5 store i1 true, ptr %var_9 br i1 %var_8, label %block_2, label %block_3 block_2: - %var_50 = load i1, ptr %var_9 - br i1 %var_50, label %block_4, label %block_5 + %var_52 = load i1, ptr %var_9 + br i1 %var_52, label %block_4, label %block_5 block_3: store i1 false, ptr %var_9 br label %block_2 @@ -47,36 +50,40 @@ block_5: store i64 0, ptr %var_39 br label %block_7 block_6: - %var_60 = load i64, ptr %var_10 - %var_11 = icmp sle i64 %var_60, 4 + %var_66 = load i64, ptr %var_10 + %var_11 = icmp sle i64 %var_66, 4 store i1 true, ptr %var_12 br i1 %var_11, label %block_8, label %block_9 block_7: - %var_53 = load i64, ptr %var_39 - %var_40 = icmp slt i64 %var_53, 2 + %var_55 = load i64, ptr %var_39 + %var_40 = icmp slt i64 %var_55, 2 br i1 %var_40, label %block_10, label %block_11 block_8: - %var_63 = load i1, ptr %var_12 - br i1 %var_63, label %block_12, label %block_13 + %var_69 = load i1, ptr %var_12 + br i1 %var_69, label %block_12, label %block_13 block_9: store i1 false, ptr %var_12 br label %block_8 block_10: - %var_56 = load i64, ptr %var_39 - %var_57_offset_chk = icmp slt i64 %var_56, 0 - %var_57_offset = select i1 %var_57_offset_chk, i64 1, i64 0 - %var_57 = getelementptr [2 x ptr], ptr @array1, i64 %var_57_offset, i64 %var_56 - %var_41 = load ptr, ptr %var_57 + %var_62 = load i64, ptr %var_39 + %var_63_offset_chk = icmp slt i64 %var_62, 0 + %var_63_offset = select i1 %var_63_offset_chk, i64 1, i64 0 + %var_63 = getelementptr [2 x ptr], ptr @array1, i64 %var_63_offset, i64 %var_62 + %var_41 = load ptr, ptr %var_63 call void @Reset(ptr %var_41) - %var_44 = add i64 %var_56, 1 + %var_44 = add i64 %var_62, 1 store i64 %var_44, ptr %var_39 br label %block_7 block_11: + %var_56 = load i1, ptr %var_38 + store i1 %var_56, ptr %var_45 + %var_58 = load i64, ptr %var_4 + store i64 %var_58, ptr %var_46 call void @__quantum__rt__tuple_record_output(i64 2, ptr @0) - %var_54 = load i1, ptr %var_38 - call void @__quantum__rt__bool_record_output(i1 %var_54, ptr @1) - %var_55 = load i64, ptr %var_4 - call void @__quantum__rt__int_record_output(i64 %var_55, ptr @2) + %var_60 = load i1, ptr %var_45 + call void @__quantum__rt__bool_record_output(i1 %var_60, ptr @1) + %var_61 = load i64, ptr %var_46 + call void @__quantum__rt__int_record_output(i64 %var_61, ptr @2) ret i64 0 block_12: store i64 0, ptr %var_13 @@ -92,8 +99,8 @@ block_13: %var_24 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) br i1 %var_24, label %block_15, label %block_16 block_14: - %var_72 = load i64, ptr %var_13 - %var_14 = icmp slt i64 %var_72, 3 + %var_80 = load i64, ptr %var_13 + %var_14 = icmp slt i64 %var_80, 3 br i1 %var_14, label %block_17, label %block_18 block_15: %var_26 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 1 to ptr)) @@ -102,18 +109,18 @@ block_16: %var_29 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 1 to ptr)) br i1 %var_29, label %block_21, label %block_22 block_17: - %var_75 = load i64, ptr %var_13 - %var_76_offset_chk = icmp slt i64 %var_75, 0 - %var_76_offset = select i1 %var_76_offset_chk, i64 1, i64 0 - %var_76 = getelementptr [3 x ptr], ptr @array0, i64 %var_76_offset, i64 %var_75 - %var_15 = load ptr, ptr %var_76 + %var_83 = load i64, ptr %var_13 + %var_84_offset_chk = icmp slt i64 %var_83, 0 + %var_84_offset = select i1 %var_84_offset_chk, i64 1, i64 0 + %var_84 = getelementptr [3 x ptr], ptr @array0, i64 %var_84_offset, i64 %var_83 + %var_15 = load ptr, ptr %var_84 call void @Rx(double 1.5707963267948966, ptr %var_15) - %var_19 = add i64 %var_75, 1 + %var_19 = add i64 %var_83, 1 store i64 %var_19, ptr %var_13 br label %block_14 block_18: - %var_73 = load i64, ptr %var_10 - %var_20 = add i64 %var_73, 1 + %var_81 = load i64, ptr %var_10 + %var_20 = add i64 %var_81, 1 store i64 %var_20, ptr %var_10 br label %block_6 block_19: @@ -133,16 +140,18 @@ block_23: block_24: br label %block_25 block_25: - %var_66 = load i1, ptr %var_23 - br i1 %var_66, label %block_26, label %block_27 + %var_72 = load i1, ptr %var_23 + store i1 %var_72, ptr %var_31 + %var_74 = load i1, ptr %var_31 + br i1 %var_74, label %block_26, label %block_27 block_26: - %var_69 = load i64, ptr %var_4 - %var_32 = add i64 %var_69, 1 + %var_77 = load i64, ptr %var_4 + %var_32 = add i64 %var_77, 1 store i64 %var_32, ptr %var_4 br label %block_27 block_27: - %var_67 = load i64, ptr %var_7 - %var_33 = add i64 %var_67, 1 + %var_75 = load i64, ptr %var_7 + %var_33 = add i64 %var_75, 1 store i64 %var_33, ptr %var_7 br label %block_1 } diff --git a/source/samples_test/src/tests/OpenQASM.rs b/source/samples_test/src/tests/OpenQASM.rs index 15526632c8c..d21b774fc41 100644 --- a/source/samples_test/src/tests/OpenQASM.rs +++ b/source/samples_test/src/tests/OpenQASM.rs @@ -32,7 +32,7 @@ pub const RANDOMNUMBER_EXPECT: Expect = expect!["9"]; pub const RANDOMNUMBER_EXPECT_DEBUG: Expect = expect!["9"]; pub const RANDOMNUMBER_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 3559"]; pub const RANDOMNUMBER_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 4064"]; -pub const RANDOMNUMBER_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 2911"]; +pub const RANDOMNUMBER_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 3001"]; pub const SIMPLE1DISINGORDER1_EXPECT: Expect = expect!["[Zero, One, One, Zero, Zero, One, One, One, One]"]; pub const SIMPLE1DISINGORDER1_EXPECT_DEBUG: Expect = @@ -41,9 +41,9 @@ pub const SIMPLE1DISINGORDER1_EXPECT_CIRCUIT: Expect = expect!["generated circui pub const SIMPLE1DISINGORDER1_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 18979"]; pub const SIMPLE1DISINGORDER1_EXPECT_QIR_ADAPTIVE: Expect = - expect!["generated QIR of length 12178"]; + expect!["generated QIR of length 12362"]; pub const TELEPORTATION_EXPECT: Expect = expect!["Zero"]; pub const TELEPORTATION_EXPECT_DEBUG: Expect = expect!["Zero"]; pub const TELEPORTATION_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 2086"]; pub const TELEPORTATION_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 3062"]; -pub const TELEPORTATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 7402"]; +pub const TELEPORTATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 7584"]; diff --git a/source/samples_test/src/tests/algorithms.rs b/source/samples_test/src/tests/algorithms.rs index e777b4834a9..9005ec5de9a 100644 --- a/source/samples_test/src/tests/algorithms.rs +++ b/source/samples_test/src/tests/algorithms.rs @@ -11,7 +11,7 @@ pub const BERNSTEINVAZIRANI_EXPECT_DEBUG: Expect = expect!["[127, 238, 512]"]; pub const BERNSTEINVAZIRANI_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 45044"]; pub const BERNSTEINVAZIRANI_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 20277"]; -pub const BERNSTEINVAZIRANI_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 15769"]; +pub const BERNSTEINVAZIRANI_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 15863"]; pub const BERNSTEINVAZIRANINISQ_EXPECT: Expect = expect!["[One, Zero, One, Zero, One]"]; pub const BERNSTEINVAZIRANINISQ_EXPECT_DEBUG: Expect = expect!["[One, Zero, One, Zero, One]"]; pub const BERNSTEINVAZIRANINISQ_EXPECT_CIRCUIT: Expect = @@ -43,7 +43,7 @@ pub const DEUTSCHJOZSA_EXPECT: Expect = expect!["[true, false, true, false]"]; pub const DEUTSCHJOZSA_EXPECT_DEBUG: Expect = expect!["[true, false, true, false]"]; pub const DEUTSCHJOZSA_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 197703"]; pub const DEUTSCHJOZSA_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 82661"]; -pub const DEUTSCHJOZSA_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 31588"]; +pub const DEUTSCHJOZSA_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 32042"]; pub const DEUTSCHJOZSANISQ_EXPECT: Expect = expect!["([One, Zero, Zero, Zero, Zero], [Zero, Zero, Zero, Zero, Zero])"]; pub const DEUTSCHJOZSANISQ_EXPECT_DEBUG: Expect = @@ -69,7 +69,7 @@ pub const DOTPRODUCTVIAPHASEESTIMATION_EXPECT_CIRCUIT: Expect = pub const DOTPRODUCTVIAPHASEESTIMATION_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 139362"]; pub const DOTPRODUCTVIAPHASEESTIMATION_EXPECT_QIR_ADAPTIVE: Expect = - expect!["generated QIR of length 21960"]; + expect!["generated QIR of length 22245"]; pub const GROVER_EXPECT: Expect = expect![[r#" Number of iterations: 4 Reflecting about marked state... @@ -99,7 +99,7 @@ pub const HIDDENSHIFT_EXPECT_DEBUG: Expect = expect![[r#" [170, 512, 999]"#]]; pub const HIDDENSHIFT_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 42131"]; pub const HIDDENSHIFT_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 25479"]; -pub const HIDDENSHIFT_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 29807"]; +pub const HIDDENSHIFT_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 29902"]; pub const HIDDENSHIFTNISQ_EXPECT: Expect = expect!["[One, Zero, Zero, Zero, Zero, One]"]; pub const HIDDENSHIFTNISQ_EXPECT_DEBUG: Expect = expect!["[One, Zero, Zero, Zero, Zero, One]"]; pub const HIDDENSHIFTNISQ_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 4379"]; @@ -110,7 +110,7 @@ pub const PHASEESTIMATION_EXPECT_DEBUG: Expect = expect!["1.0799224746714913"]; pub const PHASEESTIMATION_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 249358"]; pub const PHASEESTIMATION_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 95778"]; -pub const PHASEESTIMATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 16387"]; +pub const PHASEESTIMATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 16491"]; pub const PHASEFLIPCODE_EXPECT: Expect = expect![[r#" STATE: |000⟩: 0.4743+0.0000𝑖 @@ -158,7 +158,7 @@ pub const QRNG_EXPECT: Expect = expect!["7568811972615905454"]; pub const QRNG_EXPECT_DEBUG: Expect = expect!["7568811972615905454"]; pub const QRNG_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 232827"]; pub const QRNG_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 36067"]; -pub const QRNG_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 16532"]; +pub const QRNG_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 16627"]; pub const SHOR_EXPECT: Expect = expect![[r#" *** Factorizing 187, attempt 1. Estimating period of 182. @@ -290,4 +290,4 @@ pub const THREEQUBITREPETITIONCODE_EXPECT_CIRCUIT: Expect = pub const THREEQUBITREPETITIONCODE_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 18122"]; pub const THREEQUBITREPETITIONCODE_EXPECT_QIR_ADAPTIVE: Expect = - expect!["generated QIR of length 7555"]; + expect!["generated QIR of length 7819"]; diff --git a/source/samples_test/src/tests/getting_started.rs b/source/samples_test/src/tests/getting_started.rs index 71881ab5946..ec9dd817dce 100644 --- a/source/samples_test/src/tests/getting_started.rs +++ b/source/samples_test/src/tests/getting_started.rs @@ -89,7 +89,7 @@ pub const SIMPLETELEPORTATION_EXPECT_DEBUG: Expect = expect![[r#" pub const SIMPLETELEPORTATION_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 1463"]; pub const SIMPLETELEPORTATION_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 3118"]; -pub const SIMPLETELEPORTATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 4291"]; +pub const SIMPLETELEPORTATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 4378"]; pub const ENTANGLEMENT_EXPECT: Expect = expect![[r#" STATE: |00⟩: 0.7071+0.0000𝑖